本文共 6290 字,大约阅读时间需要 20 分钟。
总的来说 Linux C 网络编程是一块比较难的内容,且有一些枯燥,
基础内容涉及Linux基础,Linux系统编程和Linux网络编程三部分内容。
Linux基础包括最简单的基本操作,vim的使用,值得注意的是,在VS2017以上版本,可以直接用VS跨平台编写Linux程序,这样可以提高开发效率。
Linux系统编程主要涉及一些Linux系统调用API的使用,特别是进程和线程,进程间通信,与线程同步,这一块内容是最重要的。为后期高并发Web服务器编程作铺垫。
Linux网络编程的基础内容就是TCP/IP SOCKET 编程,基本内容就是《TCP/IP网络编程》中的所有内容。
深入内容有高性能服务器的实现,以及开源库的使用,以及源码的阅读,这一部分难度较大,属于项目部分的内容。
高性能服务器首先要解决的是IO问题
Reactor的概念特别重要
Proactor模式
这一部分涉及高并发服务器底层实现问题中,难度比较大
最后,总结分析一下,一些开源库的使用和源码的阅读
webbench的原理是:
webbench首先fork出多个子进程,每个子进程都循环做web访问测试。子进程把访问的结果通过pipe告诉父进程,父进程做最终的统计结果。
webbench需要统计的量是查询率, 每秒响应的请求数,这个量衡量了服务器的并发能力。
上,经常用每秒查询率来衡量服务器的机器的性能,其即为QPS。
对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。所以webbench的核心逻辑是下面这个图
具体的子进程进行单元测试的时候,逻辑是下面这样的
因此这个程序就存在一些细节,就是模拟发送一个HTTP请求头的部分,因为这是一个命令行工具程序,还涉及到终端输入参数的解析。所以这个程序就比较复杂。
tinyHttpd是一个简易的Web服务器,在TCP传输层中,直接启动一个线程接收TCP连接
直接看accept_request函数
void accept_request(int client){ char buf[1024]; int numchars; char method[255]; char url[255]; char path[512]; size_t i, j; struct stat st; int cgi = 0; /* becomes true if server decides this is a CGI program */ char *query_string = NULL; /*得到请求的第一行*/ numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; /*把客户端的请求方法存到 method 数组*/ while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = '\0'; /*如果既不是 GET 又不是 POST 则无法处理 */ if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) { unimplemented(client); return; } /* POST 的时候开启 cgi */ if (strcasecmp(method, "POST") == 0) cgi = 1; /*读取 url 地址*/ i = 0; while (ISspace(buf[j]) && (j < sizeof(buf))) j++; while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) { /*存下 url */ url[i] = buf[j]; i++; j++; } url[i] = '\0'; /*处理 GET 方法*/ if (strcasecmp(method, "GET") == 0) { /* 待处理请求为 url */ query_string = url; while ((*query_string != '?') && (*query_string != '\0')) query_string++; /* GET 方法特点,? 后面为参数*/ if (*query_string == '?') { /*开启 cgi */ cgi = 1; *query_string = '\0'; query_string++; } } /*格式化 url 到 path 数组,html 文件都在 htdocs 中*/ sprintf(path, "htdocs%s", url); /*默认情况为 index.html */ if (path[strlen(path) - 1] == '/') strcat(path, "index.html"); /*根据路径找到对应文件 */ if (stat(path, &st) == -1) { /*把所有 headers 的信息都丢弃*/ while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); /*回应客户端找不到*/ not_found(client); } else { /*如果是个目录,则默认使用该目录下 index.html 文件*/ if ((st.st_mode & S_IFMT) == S_IFDIR) strcat(path, "/index.html"); if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1; /*不是 cgi,直接把服务器文件返回,否则执行 cgi */ if (!cgi) serve_file(client, path); else execute_cgi(client, path, method, query_string); } /*断开与客户端的连接(HTTP 特点:无连接)*/ close(client);}
和处理cgi函数
void execute_cgi(int client, const char *path, const char *method, const char *query_string){ char buf[1024]; int cgi_output[2]; int cgi_input[2]; pid_t pid; int status; int i; char c; int numchars = 1; int content_length = -1; buf[0] = 'A'; buf[1] = '\0'; if (strcasecmp(method, "GET") == 0) /*把所有的 HTTP header 读取并丢弃*/ while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); else /* POST */ { /* 对 POST 的 HTTP 请求中找出 content_length */ numchars = get_line(client, buf, sizeof(buf)); while ((numchars > 0) && strcmp("\n", buf)) { /*利用 \0 进行分隔 */ buf[15] = '\0'; /* HTTP 请求的特点*/ if (strcasecmp(buf, "Content-Length:") == 0) content_length = atoi(&(buf[16])); numchars = get_line(client, buf, sizeof(buf)); } /*没有找到 content_length */ if (content_length == -1) { /*错误请求*/ bad_request(client); return; } } /* 正确,HTTP 状态码 200 */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); send(client, buf, strlen(buf), 0); /* 建立管道*/ if (pipe(cgi_output) < 0) { /*错误处理*/ cannot_execute(client); return; } /*建立管道*/ if (pipe(cgi_input) < 0) { /*错误处理*/ cannot_execute(client); return; } if ((pid = fork()) < 0 ) { /*错误处理*/ cannot_execute(client); return; } if (pid == 0) /* child: CGI script */ { char meth_env[255]; char query_env[255]; char length_env[255]; /* 把 STDOUT 重定向到 cgi_output 的写入端 */ dup2(cgi_output[1], 1); /* 把 STDIN 重定向到 cgi_input 的读取端 */ dup2(cgi_input[0], 0); /* 关闭 cgi_input 的写入端 和 cgi_output 的读取端 */ close(cgi_output[0]); close(cgi_input[1]); /*设置 request_method 的环境变量*/ sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if (strcasecmp(method, "GET") == 0) { /*设置 query_string 的环境变量*/ sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { /* POST */ /*设置 content_length 的环境变量*/ sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } /*用 execl 运行 cgi 程序*/ execl(path, path, NULL); exit(0); } else { /* parent */ /* 关闭 cgi_input 的读取端 和 cgi_output 的写入端 */ close(cgi_output[1]); close(cgi_input[0]); if (strcasecmp(method, "POST") == 0) /*接收 POST 过来的数据*/ for (i = 0; i < content_length; i++) { recv(client, &c, 1, 0); /*把 POST 数据写入 cgi_input,现在重定向到 STDIN */ write(cgi_input[1], &c, 1); } /*读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT */ while (read(cgi_output[0], &c, 1) > 0) send(client, &c, 1, 0); /*关闭管道*/ close(cgi_output[0]); close(cgi_input[1]); /*等待子进程*/ waitpid(pid, &status, 0); }}
基于xinetd库可以实现一个简单的xhttp服务器程序
当然还有更复杂的libevent等库的使用。