实现高并发http 服务器

简介: 实现高并发http 服务器


需求分析

实现一个http 服务器项目,服务器启动后监听80端口的tcp 连接,当用户通过任意一款浏览器(IE、火狐和腾讯浏览器等)访问我们的http服务器,http服务器会查找用户访问的html页面是否存在,如果存在则通过http 协议响应客户端的请求,把页面返回给浏览器,浏览器显示html页面;如果页面不存在,则按照http 协议的规定,通知浏览器此页面不存在(404 NOT FOUND)

何为Html 页面

html,全称Hypertext Markup Language,也就是“超文本链接标示语言”。HTML文本是由 HTML命令组成的描述性文本,HTML 命令可以说明文字、 图形、动画、声音、表格、链接等。 即平常上网所看到的的网页。

<html lang=\"zh-CN\">
<head>
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">
<title>This is a test</title>
</head>
<body>
<div align=center height=\"500px\" >
<br/><br/><br/>
<h2>大家好</h2><br/><br/>
<form action="commit" method="post">
尊姓大名: <input type="text" name="name" />
<br/>芳龄几何: <input type="password" name="age" />
<br/><br/><br/><input type="submit" value="提交" />
<input type="reset" value="重置" />
</form>
</div>
</body>
</html>

何为http 协议

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

请求格式:

客户端请求

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。

服务端响应

服务器响应客户端的HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

实现Mini型http 服务器

接收http请求

实现按行读取请求头部

//返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char *buf, int size){
  int count = 0;
  char ch = '\0';
  int len = 0;
  while( (count<size - 1) && ch!='\n'){
    len = read(sock, &ch, 1);
    if(len == 1){
      if(ch == '\r'){
        continue;
      }else if(ch == '\n'){
        //buf[count] = '\0';
        break;
      }
      //这里处理一般的字符
      buf[count] = ch;
      count++;
    }else if( len == -1 ){//读取出错
      perror("read failed");
      count = -1;
      break;
    }else {// read 返回0,客户端关闭sock 连接.
      fprintf(stderr, "client close.\n");
      count = -1;
      break;
    }
  }
  if(count >= 0) buf[count] = '\0';
  return count;
}

如果碰到两个连续的回车换行,即,意味着请求头部结束

// 1.读取请求行
void do_http_request(int client_sock)
{
    int len = 0;
    char buf[256];
    char method[64];
    char url[256];
    char path[256];
    /*读取客户端发送的http 请求*/
    // 1.读取请求行
    len = get_line(client_sock, buf, sizeof(buf));
    if (len > 0)
    { //读到了请求行
        int i = 0, j = 0;
        while (!isspace(buf[j]) && (i < sizeof(method) - 1))
        {
            method[i] = buf[j];
            i++;
            j++;
        }
        method[i] = '\0';
        if (debug)
            printf("request method: %s\n", method);
        if (strncasecmp(method, "GET", i) == 0)
        { //只处理get请求
            if (debug)
                printf("method = GET\n");
            //获取url
            while (isspace(buf[j++]))
                ; //跳过白空格
            i = 0;
            while (!isspace(buf[j]) && (i < sizeof(url) - 1))
            {
                url[i] = buf[j];
                i++;
                j++;
            }
            url[i] = '\0';
            if (debug)
                printf("url: %s\n", url);
            //继续读取http 头部
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug)
                    printf("read: %s\n", buf);
            } while (len > 0);
            //***定位服务器本地的html文件***
            //处理url 中的?
            {
                char *pos = strchr(url, '?');
                if (pos)
                {
                    *pos = '\0';
                    printf("real url: %s\n", url);
                }
            }
            sprintf(path, "./html_docs/%s", url);
            if (debug)
                printf("path: %s\n", path);
            //执行http 响应
        }
        else
        {
            //非get请求, 读取http 头部,并响应客户端 501  Method Not Implemented
            fprintf(stderr, "warning! other request [%s]\n", method);
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug)
                    printf("read: %s\n", buf);
            } while (len > 0);
            // unimplemented(client_sock);   //在响应时再实现
        }
    }
    else
    {   //请求格式有问题,出错处理
        // bad_request(client_sock);   //在响应时再实现
    }
}

解析请求

响应http 请求

void do_http_request(int client_sock)
{
    int len = 0;
    char buf[256];
    char method[16];
    char url[256];
    /*读取客户端发送的http请求*/
    // 1.读取请求行
    len = get_line(client_sock, buf, sizeof(buf));
    if (len > 0)
    {
        int i, j;
        while (!isspace(buf[j]) && (i < sizeof(method) - 1))
        {
            method[i] = buf[j];
            i++;
            j++;
        }
        method[i] = '\0';
        //判断方法是否合法
        if (strncasecmp(method, "GET", i) == 0)
        { // GET 方法
            printf("requst = %s\n", method);
            //获取url
            while (isspace(buf[++j]))
                ;
            i = -1;
            while (!isspace(buf[j]) && (i < sizeof(url) - 1))
            {
                url[i] = buf[j];
                i++;
                j++;
            }
            url[i] = '\0';
            printf("url: %s\n", url);
            //读取http 头部,不做任何处理
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                printf("read line: %s\n", buf);
            } while (len > 0);
            do_http_response(client_sock);
        }
        else
        {
            printf("other requst = %s\n", method);
            //读取http 头部,不做任何处理
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                printf("read line: %s\n", buf);
            } while (len > 0);
        }
    }
    else
    { //出错的处理
    }
}
void do_http_response(int client_sock)
{
    const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
    const char *welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>大家好</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
尊姓大名: <input type=\"text\" name=\"name\" />\n\
<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";
    char send_buf[64];
    int wc_len = strlen(welcome_content);
    int len = write(client_sock, main_header, strlen(main_header));
    if (debug)
        fprintf(stdout, "... do_http_response...\n");
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, main_header);
    len = snprintf(send_buf, 64, "Content-Length: %d\r\n\r\n", wc_len);
    len = write(client_sock, send_buf, len);
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, send_buf);
    len = write(client_sock, welcome_content, wc_len);
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, welcome_content);
}

读取文件

文件概念简介

inode - “索引节点”,储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。每个inode都有一个号码,操作系统用inode号码来识别不同的文件。ls -i 查看inode 号

stat函数

作用:返回文件的状态信息

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

int stat(const char *path, struct stat *buf);
   int fstat(int fd, struct stat *buf);
   int lstat(const char *path, struct stat *buf);

path:

文件的路径

buf:

传入的保存文件状态的指针,用于保存文件的状态

返回值:

成功返回0,失败返回-1,设置errno

struct stat {

dev_t st_dev; /* ID of device containing file /
ino_t st_ino; /
inode number /
mode_t st_mode; /
S_ISREG(st_mode) 是一个普通文件 S_ISDIR(st_mode) 是一个目录*/

nlink_t   st_nlink;   /* number of hard links */
           uid_t     st_uid;     /* user ID of owner */
           gid_t     st_gid;     /* group ID of owner */
           dev_t     st_rdev;    /* device ID (if special file) */
           off_t     st_size;    /* total size, in bytes */
           blksize_t st_blksize; /* blocksize for filesystem I/O */
           blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
           time_t    st_atime;   /* time of last access */
           time_t    st_mtime;   /* time of last modification */
           time_t    st_ctime;   /* time of last status change */
       };
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#define SERVER_PORT 80
static int debug = 1;
int get_line(int sock, char *buf, int size);
void do_http_request(int client_sock);
void do_http_response(int client_sock, const char *path);
int headers(int client_sock, FILE *resource);
void cat(int client_sock, FILE *resource);
void not_found(int client_sock);
void inner_error(int client_sock);
int main(void)
{
    int sock; //代表信箱
    struct sockaddr_in server_addr;
    // 1.美女创建信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);
    // 2.清空标签,写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;                //选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);       //绑定端口号
    //实现标签贴到收信得信箱上
    bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
    //把信箱挂置到传达室,这样,就可以接收信件了
    listen(sock, 128);
    //万事俱备,只等来信
    printf("等待客户端的连接\n");
    int done = 1;
    while (done)
    {
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];
        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
               inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
               ntohs(client.sin_port));
        /*处理http 请求,读取客户端发送的数据*/
        do_http_request(client_sock);
        close(client_sock);
    }
    close(sock);
    return 0;
}
void do_http_request(int client_sock)
{
    int len = 0;
    char buf[256];
    char method[64];
    char url[256];
    char path[256];
    struct stat st;
    /*读取客户端发送的http 请求*/
    // 1.读取请求行
    len = get_line(client_sock, buf, sizeof(buf));
    if (len > 0)
    { //读到了请求行
        int i = 0, j = 0;
        while (!isspace(buf[j]) && (i < sizeof(method) - 1))
        {
            method[i] = buf[j];
            i++;
            j++;
        }
        method[i] = '\0';
        if (debug)
            printf("request method: %s\n", method);
        if (strncasecmp(method, "GET", i) == 0)
        { //只处理get请求
            if (debug)
                printf("method = GET\n");
            //获取url
            while (isspace(buf[j++]))
                ; //跳过白空格
            i = 0;
            while (!isspace(buf[j]) && (i < sizeof(url) - 1))
            {
                url[i] = buf[j];
                i++;
                j++;
            }
            url[i] = '\0';
            if (debug)
                printf("url: %s\n", url);
            //继续读取http 头部
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug)
                    printf("read: %s\n", buf);
            } while (len > 0);
            //***定位服务器本地的html文件***
            //处理url 中的?
            {
                char *pos = strchr(url, '?');
                if (pos)
                {
                    *pos = '\0';
                    printf("real url: %s\n", url);
                }
            }
            sprintf(path, "./html_docs/%s", url);
            if (debug)
                printf("path: %s\n", path);
            //执行http 响应
            //判断文件是否存在,如果存在就响应200 OK,同时发送相应的html 文件,如果不存在,就响应 404 NOT FOUND.
            if (stat(path, &st) == -1)
            { //文件不存在或是出错
                fprintf(stderr, "stat %s failed. reason: %s\n", path, strerror(errno));
                not_found(client_sock);
            }
            else
            { //文件存在
                if (S_ISDIR(st.st_mode))
                {
                    strcat(path, "/index.html");
                }
                do_http_response(client_sock, path);
            }
        }
        else
        { //非get请求, 读取http 头部,并响应客户端 501  Method Not Implemented
            fprintf(stderr, "warning! other request [%s]\n", method);
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug)
                    printf("read: %s\n", buf);
            } while (len > 0);
            // unimplemented(client_sock);   //在响应时再实现
        }
    }
    else
    {   //请求格式有问题,出错处理
        // bad_request(client_sock);   //在响应时再实现
    }
}
void do_http_response(int client_sock, const char *path)
{
    int ret = 0;
    FILE *resource = NULL;
    resource = fopen(path, "r");
    if (resource == NULL)
    {
        not_found(client_sock);
        return;
    }
    // 1.发送http 头部
    ret = headers(client_sock, resource);
    // 2.发送http body .
    if (!ret)
    {
        cat(client_sock, resource);
    }
    fclose(resource);
}
/****************************
 *返回关于响应文件信息的http 头部
 *输入:
 *     client_sock - 客服端socket 句柄
 *     resource    - 文件的句柄
 *返回值: 成功返回0 ,失败返回-1
 ******************************/
int headers(int client_sock, FILE *resource)
{
    struct stat st;
    int fileid = 0;
    char tmp[64];
    char buf[1024] = {0};
    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    strcat(buf, "Server: Martin Server\r\n");
    strcat(buf, "Content-Type: text/html\r\n");
    strcat(buf, "Connection: Close\r\n");
    fileid = fileno(resource);
    if (fstat(fileid, &st) == -1)
    {
        inner_error(client_sock);
        return -1;
    }
    snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);
    strcat(buf, tmp);
    if (debug)
        fprintf(stdout, "header: %s\n", buf);
    if (send(client_sock, buf, strlen(buf), 0) < 0)
    {
        fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
        return -1;
    }
    return 0;
}
/****************************
 *说明:实现将html文件的内容按行
        读取并送给客户端
 ****************************/
void cat(int client_sock, FILE *resource)
{
    char buf[1024];
    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        int len = write(client_sock, buf, strlen(buf));
        if (len < 0)
        { //发送body 的过程中出现问题,怎么办?1.重试? 2.
            fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
            break;
        }
        if (debug)
            fprintf(stdout, "%s", buf);
        fgets(buf, sizeof(buf), resource);
    }
}
void do_http_response1(int client_sock)
{
    const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
    const char *welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>大家好</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
尊姓大名: <input type=\"text\" name=\"name\" />\n\
<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";
    // 1. 发送main_header
    int len = write(client_sock, main_header, strlen(main_header));
    if (debug)
        fprintf(stdout, "... do_http_response...\n");
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, main_header);
    // 2. 生成Content-Length
    char send_buf[64];
    int wc_len = strlen(welcome_content);
    len = snprintf(send_buf, 64, "Content-Length: %d\r\n\r\n", wc_len);
    len = write(client_sock, send_buf, len);
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, send_buf);
    len = write(client_sock, welcome_content, wc_len);
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, welcome_content);
}
//返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char *buf, int size)
{
    int count = 0;
    char ch = '\0';
    int len = 0;
    while ((count < size - 1) && ch != '\n')
    {
        len = read(sock, &ch, 1);
        if (len == 1)
        {
            if (ch == '\r')
            {
                continue;
            }
            else if (ch == '\n')
            {
                // buf[count] = '\0';
                break;
            }
            //这里处理一般的字符
            buf[count] = ch;
            count++;
        }
        else if (len == -1)
        { //读取出错
            perror("read failed");
            count = -1;
            break;
        }
        else
        { // read 返回0,客户端关闭sock 连接.
            fprintf(stderr, "client close.\n");
            count = -1;
            break;
        }
    }
    if (count >= 0)
        buf[count] = '\0';
    return count;
}
void not_found(int client_sock)
{
    const char *reply = "HTTP/1.0 404 NOT FOUND\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>NOT FOUND</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
  <P>文件不存在!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
</BODY>\r\n\
</HTML>";
    int len = write(client_sock, reply, strlen(reply));
    if (debug)
        fprintf(stdout, reply);
    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}
void inner_error(int client_sock)
{
    const char *reply = "HTTP/1.0 500 Internal Sever Error\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>Inner Error</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
    <P>服务器内部出错.\r\n\
</BODY>\r\n\
</HTML>";
    int len = write(client_sock, reply, strlen(reply));
    if (debug)
        fprintf(stdout, reply);
    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}

并发处理

并发概述

通俗的并发通常是指同时能并行的处理多个任务。

并发

同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替的换入或者换出内存,这些线程是同时“存在”的。

每个线程都处于执行过程中的某个状态,如果运行在多核处理器上,此时,程序中的每个线程都将分配到一个处理器核上,因此可以同时运行。

高并发

高并发是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够 同时并行处理 很多请求

pthread_create函数

创建一个新线程,并行的执行任务。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

返回值:成功:0; 失败:错误号。

参数:

pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;

参数1:传出参数,保存系统为我们分配好的线程ID

参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。

参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。

参数4:线程主函数执行期间所使用的参数。

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void *,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值,以后再详细介绍pthread_join。

pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。

attr参数表示线程属性,本节不深入讨论线程属性,所有代码例子都传NULL给attr参数,表示线程属性取缺省值。

并发回声服务器改造

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include <errno.h>
#define SERVER_PORT 666
void *thread_handleRequest(void *arg);
int main(void)
{
    int sock; //代表信箱
    struct sockaddr_in server_addr;
    // 1.美女创建信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);
    // 2.清空标签,写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;                //选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);       //绑定端口号
    //实现标签贴到收信得信箱上
    bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
    //把信箱挂置到传达室,这样,就可以接收信件了
    listen(sock, 128);
    //万事俱备,只等来信
    printf("等待客户端的连接\n");
    int done = 1;
    while (done)
    {
        struct sockaddr_in client;
        int client_sock, len;
        char client_ip[64];
        // char buf[256];
        // int  i=0;
        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
               inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
               ntohs(client.sin_port));
        //启动线程,完成和和客户端的交互
        {
            pthread_t tid;
            int *ptr_int = NULL;
            int err = 0;
            ptr_int = (int *)malloc(sizeof(int));
            *ptr_int = client_sock;
            if (err = pthread_create(&tid, NULL, thread_handleRequest, (void *)ptr_int))
            {
                fprintf(stderr, "Can't create thread, reason: %s\n", strerror(errno));
                if (ptr_int)
                    free(ptr_int);
            }
        }
        /*读取客户端发送的数据*/
        /*len = read(client_sock, buf, sizeof(buf)-1);
        buf[len] = '\0';
        printf("receive[%d]: %s\n", len, buf);
        //转换成大写
        for(i=0; i<len; i++){
            //if(buf[i]>='a' && buf[i]<='z'){
            //    buf[i] = buf[i] - 32;
            //}
            buf[i] = toupper(buf[i]);
        }
        len = write(client_sock, buf, len);
        printf("finished. len: %d\n", len);
        close(client_sock);
        */
    }
    close(sock);
    return 0;
}
void *thread_handleRequest(void *arg)
{
    int client_sock = *(int *)arg;
    char buf[256];
    int i = 0;
    int len = read(client_sock, buf, sizeof(buf) - 1);
    buf[len] = '\0';
    printf("receive[%d]: %s\n", len, buf);
    //转换成大写
    for (i = 0; i < len; i++)
    {
        // if(buf[i]>='a' && buf[i]<='z'){
        //     buf[i] = buf[i] - 32;
        // }
        buf[i] = toupper(buf[i]);
    }
    len = write(client_sock, buf, len);
    printf("finished. len: %d\n", len);
    close(client_sock);
    if (arg)
        free(arg);
}

并发Mini http 服务器改造

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>
#define SERVER_PORT 80
static int debug = 1;
int get_line(int sock, char *buf, int size);
void *do_http_request(void *client_sock);
void do_http_response(int client_sock, const char *path);
int headers(int client_sock, FILE *resource);
void cat(int client_sock, FILE *resource);
void not_found(int client_sock);     // 404
void unimplemented(int client_sock); // 500
void bad_request(int client_sock);   // 400
void inner_error(int client_sock);
int main(void)
{
    int sock; //代表信箱
    struct sockaddr_in server_addr;
    // 1.美女创建信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);
    // 2.清空标签,写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;                //选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //监听本地所有IP地址
    server_addr.sin_port = htons(SERVER_PORT);       //绑定端口号
    //实现标签贴到收信得信箱上
    bind(sock, (struct sockaddr *)&server_addr, sizeof(server_addr));
    //把信箱挂置到传达室,这样,就可以接收信件了
    listen(sock, 128);
    //万事俱备,只等来信
    printf("等待客户端的连接\n");
    int done = 1;
    while (done)
    {
        struct sockaddr_in client;
        int client_sock, len, i;
        char client_ip[64];
        char buf[256];
        pthread_t id;
        int *pclient_sock = NULL;
        socklen_t client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr *)&client, &client_addr_len);
        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
               inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
               ntohs(client.sin_port));
        /*处理http 请求,读取客户端发送的数据*/
        // do_http_request(client_sock);
        //启动线程处理http 请求
        pclient_sock = (int *)malloc(sizeof(int));
        *pclient_sock = client_sock;
        pthread_create(&id, NULL, do_http_request, (void *)pclient_sock);
        // close(client_sock);
    }
    close(sock);
    return 0;
}
void *do_http_request(void *pclient_sock)
{
    int len = 0;
    char buf[256];
    char method[64];
    char url[256];
    char path[256];
    int client_sock = *(int *)pclient_sock;
    struct stat st;
    /*读取客户端发送的http 请求*/
    // 1.读取请求行
    len = get_line(client_sock, buf, sizeof(buf));
    if (len > 0)
    { //读到了请求行
        int i = 0, j = 0;
        while (!isspace(buf[j]) && (i < sizeof(method) - 1))
        {
            method[i] = buf[j];
            i++;
            j++;
        }
        method[i] = '\0';
        if (debug)
            printf("request method: %s\n", method);
        if (strncasecmp(method, "GET", i) == 0)
        { //只处理get请求
            if (debug)
                printf("method = GET\n");
            //获取url
            while (isspace(buf[j++]))
                ; //跳过白空格
            i = 0;
            while (!isspace(buf[j]) && (i < sizeof(url) - 1))
            {
                url[i] = buf[j];
                i++;
                j++;
            }
            url[i] = '\0';
            if (debug)
                printf("url: %s\n", url);
            //继续读取http 头部
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug)
                    printf("read: %s\n", buf);
            } while (len > 0);
            //***定位服务器本地的html文件***
            //处理url 中的?
            {
                char *pos = strchr(url, '?');
                if (pos)
                {
                    *pos = '\0';
                    printf("real url: %s\n", url);
                }
            }
            sprintf(path, "./html_docs/%s", url);
            if (debug)
                printf("path: %s\n", path);
            //执行http 响应
            //判断文件是否存在,如果存在就响应200 OK,同时发送相应的html 文件,如果不存在,就响应 404 NOT FOUND.
            if (stat(path, &st) == -1)
            { //文件不存在或是出错
                fprintf(stderr, "stat %s failed. reason: %s\n", path, strerror(errno));
                not_found(client_sock);
            }
            else
            { //文件存在
                if (S_ISDIR(st.st_mode))
                {
                    strcat(path, "/index.html");
                }
                do_http_response(client_sock, path);
            }
        }
        else
        { //非get请求, 读取http 头部,并响应客户端 501  Method Not Implemented
            fprintf(stderr, "warning! other request [%s]\n", method);
            do
            {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug)
                    printf("read: %s\n", buf);
            } while (len > 0);
            unimplemented(client_sock); //请求未实现
        }
    }
    else
    {                             //请求格式有问题,出错处理
        bad_request(client_sock); //在响应时再实现
    }
    close(client_sock);
    if (pclient_sock)
        free(pclient_sock); //释放动态分配的内存
    return NULL;
}
void do_http_response(int client_sock, const char *path)
{
    int ret = 0;
    FILE *resource = NULL;
    resource = fopen(path, "r");
    if (resource == NULL)
    {
        not_found(client_sock);
        return;
    }
    // 1.发送http 头部
    ret = headers(client_sock, resource);
    // 2.发送http body .
    if (!ret)
    {
        cat(client_sock, resource);
    }
    fclose(resource);
}
/****************************
 *返回关于响应文件信息的http 头部
 *输入:
 *     client_sock - 客服端socket 句柄
 *     resource    - 文件的句柄
 *返回值: 成功返回0 ,失败返回-1
 ******************************/
int headers(int client_sock, FILE *resource)
{
    struct stat st;
    int fileid = 0;
    char tmp[64];
    char buf[1024] = {0};
    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    strcat(buf, "Server: Martin Server\r\n");
    strcat(buf, "Content-Type: text/html\r\n");
    strcat(buf, "Connection: Close\r\n");
    fileid = fileno(resource);
    if (fstat(fileid, &st) == -1)
    {
        inner_error(client_sock);
        return -1;
    }
    snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);
    strcat(buf, tmp);
    if (debug)
        fprintf(stdout, "header: %s\n", buf);
    if (send(client_sock, buf, strlen(buf), 0) < 0)
    {
        fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
        return -1;
    }
    return 0;
}
/****************************
 *说明:实现将html文件的内容按行
        读取并送给客户端
 ****************************/
void cat(int client_sock, FILE *resource)
{
    char buf[1024];
    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        int len = write(client_sock, buf, strlen(buf));
        if (len < 0)
        { //发送body 的过程中出现问题,怎么办?1.重试? 2.
            fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
            break;
        }
        if (debug)
            fprintf(stdout, "%s", buf);
        fgets(buf, sizeof(buf), resource);
    }
}
void do_http_response1(int client_sock)
{
    const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
    const char *welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>大家好</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
尊姓大名: <input type=\"text\" name=\"name\" />\n\
<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";
    // 1. 发送main_header
    int len = write(client_sock, main_header, strlen(main_header));
    if (debug)
        fprintf(stdout, "... do_http_response...\n");
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, main_header);
    // 2. 生成Content-Length
    char send_buf[64];
    int wc_len = strlen(welcome_content);
    len = snprintf(send_buf, 64, "Content-Length: %d\r\n\r\n", wc_len);
    len = write(client_sock, send_buf, len);
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, send_buf);
    len = write(client_sock, welcome_content, wc_len);
    if (debug)
        fprintf(stdout, "write[%d]: %s", len, welcome_content);
}
//返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char *buf, int size)
{
    int count = 0;
    char ch = '\0';
    int len = 0;
    while ((count < size - 1) && ch != '\n')
    {
        len = read(sock, &ch, 1);
        if (len == 1)
        {
            if (ch == '\r')
            {
                continue;
            }
            else if (ch == '\n')
            {
                // buf[count] = '\0';
                break;
            }
            //这里处理一般的字符
            buf[count] = ch;
            count++;
        }
        else if (len == -1)
        { //读取出错
            perror("read failed");
            count = -1;
            break;
        }
        else
        { // read 返回0,客户端关闭sock 连接.
            fprintf(stderr, "client close.\n");
            count = -1;
            break;
        }
    }
    if (count >= 0)
        buf[count] = '\0';
    return count;
}
void not_found(int client_sock)
{
    const char *reply = "HTTP/1.0 404 NOT FOUND\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>NOT FOUND</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
  <P>文件不存在!\r\n\
    <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
</BODY>\r\n\
</HTML>";
    int len = write(client_sock, reply, strlen(reply));
    if (debug)
        fprintf(stdout, reply);
    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}
void unimplemented(int client_sock)
{
    const char *reply = "HTTP/1.0 501 Method Not Implemented\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML>\r\n\
<HEAD>\r\n\
<TITLE>Method Not Implemented</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
    <P>HTTP request method not supported.\r\n\
</BODY>\r\n\
</HTML>";
    int len = write(client_sock, reply, strlen(reply));
    if (debug)
        fprintf(stdout, reply);
    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}
void bad_request(client_sock)
{
    const char *reply = "HTTP/1.0 400 BAD REQUEST\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML>\r\n\
<HEAD>\r\n\
<TITLE>BAD REQUEST</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
    <P>Your browser sent a bad request!\r\n\
</BODY>\r\n\
</HTML>";
    int len = write(client_sock, reply, strlen(reply));
    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}
void inner_error(int client_sock)
{
    const char *reply = "HTTP/1.0 500 Internal Sever Error\r\n\
Content-Type: text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>Inner Error</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
    <P>服务器内部出错.\r\n\
</BODY>\r\n\
</HTML>";
    int len = write(client_sock, reply, strlen(reply));
    if (debug)
        fprintf(stdout, reply);
    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}
相关文章
|
13天前
|
搜索推荐 安全 网络安全
服务器支持HTTPS的时机和条件
【10月更文挑战第23天】服务器支持HTTPS的时机和条件
11 5
|
1月前
使用Netty实现文件传输的HTTP服务器和客户端
本文通过详细的代码示例,展示了如何使用Netty框架实现一个文件传输的HTTP服务器和客户端,包括服务端的文件处理和客户端的文件请求与接收。
35 1
使用Netty实现文件传输的HTTP服务器和客户端
|
7天前
|
存储 Oracle 关系型数据库
oracle服务器存储过程中调用http
通过配置权限、创建和调用存储过程,您可以在Oracle数据库中使用UTL_HTTP包发起HTTP请求。这使得Oracle存储过程可以与外部HTTP服务进行交互,从而实现更复杂的数据处理和集成。在实际应用中,根据具体需求调整请求类型和错误处理逻辑,以确保系统的稳定性和可靠性。
9 0
|
1月前
|
缓存 NoSQL Ubuntu
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
大数据-39 Redis 高并发分布式缓存 Ubuntu源码编译安装 云服务器 启动并测试 redis-server redis-cli
53 3
|
2月前
HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
文章介绍了如何配置HAProxy以支持HTTPS协议和实现服务器的动态上下线。
133 8
HAProxy的高级配置选项-配置haproxy支持https协议及服务器动态上下线
|
2月前
|
开发者
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
31 1
|
7天前
|
弹性计算
阿里云2核16G服务器多少钱一年?亲测价格查询1个月和1小时收费标准
阿里云2核16G服务器提供多种ECS实例规格,内存型r8i实例1年6折优惠价为1901元,按月收费334.19元,按小时收费0.696221元。更多规格及详细报价请访问阿里云ECS页面。
39 9
|
3天前
|
监控 Ubuntu Linux
使用VSCode通过SSH远程登录阿里云Linux服务器异常崩溃
通过 VSCode 的 Remote - SSH 插件远程连接阿里云 Ubuntu 22 服务器时,会因高 CPU 使用率导致连接断开。经排查发现,VSCode 连接根目录 ".." 时会频繁调用"rg"(ripgrep)进行文件搜索,导致 CPU 负载过高。解决方法是将连接目录改为"root"(或其他具体的路径),避免不必要的文件检索,从而恢复正常连接。
|
7天前
|
弹性计算 异构计算
2024年阿里云GPU服务器多少钱1小时?亲测价格查询方法
2024年阿里云GPU服务器每小时收费因实例规格不同而异。可通过阿里云GPU服务器页面选择“按量付费”查看具体价格。例如,NVIDIA A100的gn7e实例为34.742元/小时,NVIDIA A10的gn7i实例为12.710156元/小时。更多详情请访问阿里云官网。
40 2
|
12天前
|
存储 弹性计算 NoSQL
"从入门到实践,全方位解析云服务器ECS的秘密——手把手教你轻松驾驭阿里云的强大计算力!"
【10月更文挑战第23天】云服务器ECS(Elastic Compute Service)是阿里云提供的基础云计算服务,允许用户在云端租用和管理虚拟服务器。ECS具有弹性伸缩、按需付费、简单易用等特点,适用于网站托管、数据库部署、大数据分析等多种场景。本文介绍ECS的基本概念、使用场景及快速上手指南。
49 3
下一篇
无影云桌面