ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C语言网络编程:从TCP聊天室到HTTP服务器实战

C语言网络编程:从TCP聊天室到HTTP服务器实战 简介这是一份面向1–3年经验C语言开发者的网络编程实战指南聚焦TCP与HTTP协议的底层实现帮助读者从零构建聊天室和轻量HTTP服务器。资源为单文件PDF1.89MB共35页支持目录跳转与左侧大纲导航内容涵盖套接字编程、三次握手与四次挥手机制、多线程并发处理、HTTP请求解析与响应构造、缓冲区溢出防护等关键实践环节每章均配可运行代码示例及编译运行说明。已有215人学习下载适合分阶段精读先掌握socket函数族与TCP状态机原理再动手实现双端通信逻辑最后通过HTTP服务器项目整合协议理解与工程优化能力。文档结构清晰含8大模块、30子节从引言到性能安全展望层层递进兼顾原理深度与落地可行性。1. 为什么用 C 写 TCP 聊天室和 HTTP 服务器不是“复古情怀”而是为了看清网络栈的每一层毛细血管你手头这份《C语言网络编程从TCP聊天室到HTTP服务器搭建指南.pdf》不是一本教你怎么调libcurl或mongoose的速成手册而是一份用纯 POSIX socket 标准 C 实现网络服务的底层施工图。它解决的不是“怎么发个 GET 请求”而是当connect()返回成功时内核到底完成了哪些状态迁移accept()为什么必须在阻塞/非阻塞模式下配不同的事件循环策略HTTP 响应头里Connection: keep-alive真的能复用 TCP 连接吗——还是说你写的read()没处理好\r\n\r\n后的残留字节导致下一次请求直接卡死这类问题在 Python 的Flask、Node.js 的http.createServer甚至 Rust 的hyper里都被层层封装遮蔽了。但当你用socket(AF_INET, SOCK_STREAM, 0)创建套接字、用setsockopt()设置SO_REUSEADDR、用select()或epoll_wait()监听多个连接时你面对的是 Linux 网络栈最真实的接口——没有魔法只有系统调用、缓冲区边界、字节序、TIME_WAIT 状态和 SIGPIPE 信号。适合谁三类人绕不开它嵌入式设备上要写轻量通信模块的工程师比如 ESP32-S3 接收 TCP 消息但不用 Arduino 封装后端新人想真正理解 Nginx/Apache 的 accept 模型和连接池原理还有安全研究者——所有协议解析漏洞如 HTTP 请求走私、TCP 序列号预测都藏在这些基础代码的边界判断里。这不是“学完就能跳槽涨薪”的课但它是你调试unexpected status 502 bad gateway或the specified http method is not allowed时唯一能让你翻出strace -e tracenetwork对照着看的底气。2. 从零实现 TCP 聊天室阻塞模型打底再切到多路复用2.1 为什么先写阻塞式服务器不是偷懒是建立状态直觉很多教程一上来就甩epoll结果新手连accept()返回的client_fd和listen_fd区别都搞混。我们反其道而行先用最朴素的阻塞模型跑通一个单线程、单客户端的 echo 服务再逐步加料。这样你能亲眼看到recv()在对端关闭连接时返回 0send()遇到对端崩溃时触发 SIGPIPE以及close()调用后文件描述符如何真正进入 TIME_WAIT 状态——这些细节全被epoll的事件抽象层吃掉了。// server_simple.c最简 TCP 服务器单连接 #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h int main() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket); return 1; } // 关键允许地址重用避免重启时报 Address already in use int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in serv_addr {0}; serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr INADDR_ANY; serv_addr.sin_port htons(8080); if (bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1) { perror(bind); close(listen_fd); return 1; } if (listen(listen_fd, 5) -1) { perror(listen); close(listen_fd); return 1; } printf(Server listening on port 8080...\n); struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int client_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (client_fd -1) { perror(accept); close(listen_fd); return 1; } char buffer[1024]; while (1) { ssize_t n recv(client_fd, buffer, sizeof(buffer)-1, 0); if (n 0) { buffer[n] \0; printf(Received: %s, buffer); send(client_fd, buffer, n, 0); // 回显 } else if (n 0) { printf(Client disconnected.\n); break; } else { perror(recv); break; } } close(client_fd); close(listen_fd); return 0; }逻辑说明这段代码只处理一个客户端accept()会阻塞直到有连接到来recv()也会阻塞直到数据到达或连接关闭。关键点在于SO_REUSEADDR的设置——没有它服务器崩溃后重启会因端口处于 TIME_WAIT 状态而失败这是新手第一个真实踩坑点不是玄学是 TCP 协议栈强制要求。参数说明listen(sockfd, 5)中的5是已完成连接队列completed queue长度不是最大并发数。Linux 内核实际维护两个队列SYN 队列未完成三次握手和 accept 队列已完成握手等待accept()。5只控制后者过小会导致高并发时连接被丢弃Connection refused过大则浪费内存。生产环境常设为128或1024。2.2 用 select() 支持多客户端理解 fd_set 的位操作本质阻塞模型只能服务一个用户现实聊天室需同时处理几十上百连接。select()是最兼容的多路复用方案Windows/Linux/macOS 全支持它不依赖特定内核机制但要求你亲手管理fd_set——这恰恰帮你理解“文件描述符本质是整数索引”这一底层事实。// server_select.c支持多客户端的 select 模型 #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/time.h #define MAX_CLIENTS 100 int main() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket); return 1; } int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in serv_addr {0}; serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr INADDR_ANY; serv_addr.sin_port htons(8080); if (bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1) { perror(bind); close(listen_fd); return 1; } if (listen(listen_fd, 5) -1) { perror(listen); close(listen_fd); return 1; } fd_set read_fds, temp_fds; FD_ZERO(read_fds); FD_SET(listen_fd, read_fds); int max_fd listen_fd; int client_fds[MAX_CLIENTS]; for (int i 0; i MAX_CLIENTS; i) { client_fds[i] -1; } printf(Select-based server running on port 8080...\n); while (1) { temp_fds read_fds; // 每次 select 前必须重置 struct timeval timeout {5, 0}; // 5秒超时避免永久阻塞 int activity select(max_fd 1, temp_fds, NULL, NULL, timeout); if (activity 0) { perror(select); continue; } else if (activity 0) { // 超时可做心跳检测或日志轮转 continue; } // 检查 listen_fd 是否就绪 if (FD_ISSET(listen_fd, temp_fds)) { struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int client_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (client_fd 0) { printf(New connection from %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 将新连接加入 client_fds 数组并添加到 read_fds for (int i 0; i MAX_CLIENTS; i) { if (client_fds[i] -1) { client_fds[i] client_fd; FD_SET(client_fd, read_fds); if (client_fd max_fd) max_fd client_fd; break; } } } } // 检查所有已连接客户端 for (int i 0; i MAX_CLIENTS; i) { int client_fd client_fds[i]; if (client_fd ! -1 FD_ISSET(client_fd, temp_fds)) { char buffer[1024]; ssize_t n recv(client_fd, buffer, sizeof(buffer)-1, 0); if (n 0) { buffer[n] \0; printf(From client %d: %s, client_fd, buffer); // 广播给其他所有客户端简化版实际需遍历 for (int j 0; j MAX_CLIENTS; j) { if (client_fds[j] ! -1 client_fds[j] ! client_fd) { send(client_fds[j], buffer, n, 0); } } } else if (n 0) { printf(Client %d disconnected.\n, client_fd); FD_CLR(client_fd, read_fds); close(client_fd); client_fds[i] -1; } else { perror(recv); FD_CLR(client_fd, read_fds); close(client_fd); client_fds[i] -1; } } } } close(listen_fd); return 0; }逻辑说明select()的核心是fd_set——一个按位存储的整数数组。FD_SET(fd, set)就是把fd对应的 bit 置 1FD_ISSET(fd, set)则检查该 bit 是否为 1。select()返回后temp_fds中仅保留就绪的 fd所以必须每次循环前temp_fds read_fds复制一份。max_fd是为了告诉select()扫描范围0到max_fd否则它不知道要查多少个 fd。参数说明struct timeval timeout {5, 0}设定 5 秒超时这是关键设计。若设为NULLselect()会永久阻塞若设为{0,0}立即返回则变成轮询CPU 占用飙升。生产环境常用 100ms~1s 超时平衡响应与 CPU 开销。2.3 用 epoll 替代 select突破 1024 限制与性能拐点select()最大缺陷是fd_set默认大小为 1024FD_SETSIZE且每次调用都要遍历整个位图。当连接数超 1000select()性能断崖下跌。epoll是 Linux 特有方案它用红黑树管理 fd就绪列表用链表时间复杂度从 O(n) 降到 O(1)。但注意epoll不是万能银弹——它只在连接数 500 且活跃连接比例 10% 时才显著胜出select()对于百以内连接select()更简单可靠。// server_epoll.cepoll 多路复用实现 #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/epoll.h #include fcntl.h #define MAX_EVENTS 100 #define MAX_CLIENTS 1000 int set_nonblocking(int fd) { int flags fcntl(fd, F_GETFL, 0); if (flags -1) return -1; return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int main() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket); return 1; } int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in serv_addr {0}; serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr INADDR_ANY; serv_addr.sin_port htons(8080); if (bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1) { perror(bind); close(listen_fd); return 1; } if (listen(listen_fd, 5) -1) { perror(listen); close(listen_fd); return 1; } // 设置非阻塞 if (set_nonblocking(listen_fd) -1) { perror(set_nonblocking listen_fd); close(listen_fd); return 1; } int epoll_fd epoll_create1(0); if (epoll_fd -1) { perror(epoll_create1); close(listen_fd); return 1; } struct epoll_event ev, events[MAX_EVENTS]; ev.events EPOLLIN; ev.data.fd listen_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, ev) -1) { perror(epoll_ctl: listen_fd); close(listen_fd); close(epoll_fd); return 1; } printf(Epoll-based server running on port 8080...\n); while (1) { int nfds epoll_wait(epoll_fd, events, MAX_EVENTS, 5000); // 5秒超时 if (nfds -1) { perror(epoll_wait); continue; } else if (nfds 0) { // 超时可做清理 continue; } for (int i 0; i nfds; i) { if (events[i].data.fd listen_fd) { // 新连接 struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int client_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (client_fd 0) { printf(New connection from %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (set_nonblocking(client_fd) -1) { perror(set_nonblocking client_fd); close(client_fd); continue; } ev.events EPOLLIN | EPOLLET; // 边缘触发 ev.data.fd client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, ev) -1) { perror(epoll_ctl: client_fd); close(client_fd); } } } else { // 已连接客户端数据到达 char buffer[1024]; ssize_t n; while ((n recv(events[i].data.fd, buffer, sizeof(buffer)-1, 0)) 0) { buffer[n] \0; printf(From client %d: %s, events[i].data.fd, buffer); // 广播逻辑同前此处省略 } if (n 0) { printf(Client %d disconnected.\n, events[i].data.fd); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); close(events[i].data.fd); } else if (n -1) { if (errno EAGAIN || errno EWOULDBLOCK) { // 非阻塞读完正常退出循环 continue; } else { perror(recv); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL); close(events[i].data.fd); } } } } } close(listen_fd); close(epoll_fd); return 0; }逻辑说明epoll分三步epoll_create1()创建实例epoll_ctl()添加/修改/删除监听 fdepoll_wait()等待事件。关键区别在于EPOLLET边缘触发模式——它只在 fd 状态变化时通知一次之后必须循环recv()直到返回EAGAIN否则会漏掉数据。而EPOLLIN水平触发会持续通知更易用但效率略低。本例用 ET 模式体现高性能场景的真实写法。参数说明epoll_wait(epoll_fd, events, MAX_EVENTS, 5000)的5000是毫秒级超时。MAX_EVENTS是每次最多返回的事件数设为 100 足够应对千级连接。注意epoll_ctl()的EPOLL_CTL_DEL必须显式调用否则 fd 泄漏。3. 手撕 HTTP 服务器从解析请求行到实现 Keep-Alive3.1 HTTP 请求解析为什么不能用fgets()读取一行HTTP 协议规定请求以\r\n\r\n结束头部但fgets()以\n为界遇到\r\n会截断为\r导致后续解析错乱。更糟的是HTTP 请求可能分片到达TCP 粘包一次recv()可能只收到部分 header或一次收到多个请求。因此必须手动缓冲、查找\r\n\r\n并处理不完整数据。// http_parser.c简易 HTTP 请求解析器 #include stdio.h #include stdlib.h #include string.h #include ctype.h typedef struct { char method[16]; char path[256]; char version[16]; char headers[4096]; // 简化存原始 header 字符串 char body[8192]; size_t body_len; } http_request_t; // 查找 \r\n\r\n返回 header 结束位置含 \r\n\r\n static size_t find_header_end(const char* buf, size_t len) { for (size_t i 0; i 3 len; i) { if (buf[i] \r buf[i1] \n buf[i2] \r buf[i3] \n) { return i 4; } } return 0; // 未找到 } // 解析请求行GET /path HTTP/1.1 static int parse_request_line(const char* line, http_request_t* req) { const char* p line; // 方法 int i 0; while (*p *p ! i 15) { req-method[i] *p; } req-method[i] \0; if (*p ! ) return -1; p; // 路径 i 0; while (*p *p ! i 255) { req-path[i] *p; } req-path[i] \0; if (*p ! ) return -1; p; // 版本 i 0; while (*p *p ! \r *p ! \n i 15) { req-version[i] *p; } req-version[i] \0; return 0; } // 解析单个 header 行key: value static void parse_header_line(const char* line, char* headers_buf, size_t* offset) { const char* colon strchr(line, :); if (!colon) return; size_t key_len colon - line; size_t val_len strlen(line) - key_len - 1; // -1 for : if (*offset key_len val_len 4 4096) return; // 防溢出 // 复制 key加 : memcpy(headers_buf *offset, line, key_len); *offset key_len; memcpy(headers_buf *offset, : , 2); *offset 2; // 复制 value跳过空格 const char* val_start colon 1; while (*val_start || *val_start \t) val_start; size_t copy_len strlen(val_start); if (copy_len 0) { memcpy(headers_buf *offset, val_start, copy_len); *offset copy_len; } // 加 \r\n headers_buf[*offset] \r; headers_buf[*offset1] \n; *offset 2; } // 主解析函数 int parse_http_request(const char* buf, size_t len, http_request_t* req) { if (!buf || !req || len 0) return -1; size_t header_end find_header_end(buf, len); if (header_end 0) return 0; // 不完整等待更多数据 // 解析请求行第一行 const char* line_start buf; const char* line_end memchr(buf, \n, header_end); if (!line_end) return -1; char line_buf[512]; size_t line_len line_end - line_start; if (line_len 511) return -1; memcpy(line_buf, line_start, line_len); line_buf[line_len] \0; if (parse_request_line(line_buf, req) ! 0) return -1; // 解析 headers从第二行开始 size_t headers_offset 0; const char* ptr line_end 1; while (ptr buf header_end) { line_end memchr(ptr, \n, buf header_end - ptr); if (!line_end) break; size_t h_len line_end - ptr; if (h_len 0 h_len 511) { char h_buf[512]; memcpy(h_buf, ptr, h_len); h_buf[h_len] \0; parse_header_line(h_buf, req-headers, headers_offset); } ptr line_end 1; } req-headers[headers_offset] \0; // body req-body_len len - header_end; if (req-body_len 0 req-body_len 8192) { memcpy(req-body, buf header_end, req-body_len); } return 1; // 成功 }逻辑说明find_header_end()手动扫描\r\n\r\n这是 HTTP 解析的基石。parse_request_line()按空格分割方法、路径、版本严格遵循 RFC 7230。parse_header_line()提取key: value对自动跳过冒号后的空格。整个解析器不依赖任何外部库可嵌入任意 socket 服务中。参数说明http_request_t结构体中body_len记录正文长度因为 HTTP POST 的 body 可能为空如Content-Length: 0不能仅靠\r\n\r\n后是否还有数据判断。headers缓冲区大小 4096 覆盖常见 header如带长 Cookie 的请求超长则截断——这是生产环境必须做的防御性编程。3.2 构造 HTTP 响应状态行、Header、Body 的严格格式HTTP 响应必须严格遵循\r\n换行且Content-Length必须精确匹配 body 字节数否则客户端尤其是浏览器会卡死。Connection: keep-alive的实现关键在于不关闭 socket而是继续等待下一个请求。// http_response.c构造标准 HTTP 响应 #include stdio.h #include stdlib.h #include string.h #include time.h char* get_current_time_rfc1123() { static char time_str[128]; time_t now time(NULL); struct tm* tm_info gmtime(now); strftime(time_str, sizeof(time_str), %a, %d %b %Y %H:%M:%S GMT, tm_info); return time_str; } // 构造响应字符串动态分配 char* build_http_response(int status_code, const char* status_text, const char* content_type, const char* body, size_t body_len, int keep_alive) { char status_line[128]; snprintf(status_line, sizeof(status_line), HTTP/1.1 %d %s\r\n, status_code, status_text); char date_header[256]; snprintf(date_header, sizeof(date_header), Date: %s\r\n, get_current_time_rfc1123()); char server_header[128]; snprintf(server_header, sizeof(server_header), Server: C-HTTP/1.0\r\n); char content_length_header[128]; snprintf(content_length_header, sizeof(content_length_header), Content-Length: %zu\r\n, body_len); char connection_header[128]; if (keep_alive) { snprintf(connection_header, sizeof(connection_header), Connection: keep-alive\r\n); } else { snprintf(connection_header, sizeof(connection_header), Connection: close\r\n); } char content_type_header[256]; if (content_type) { snprintf(content_type_header, sizeof(content_type_header), Content-Type: %s\r\n, content_type); } else { snprintf(content_type_header, sizeof(content_type_header), Content-Type: text/plain\r\n); } size_t total_len strlen(status_line) strlen(date_header) strlen(server_header) strlen(content_length_header) strlen(connection_header) strlen(content_type_header) 2 // \r\n 分隔 header 与 body body_len; char* response malloc(total_len 1); if (!response) return NULL; char* ptr response; ptr sprintf(ptr, %s, status_line); ptr sprintf(ptr, %s, date_header); ptr sprintf(ptr, %s, server_header); ptr sprintf(ptr, %s, content_length_header); ptr sprintf(ptr, %s, connection_header); ptr sprintf(ptr, %s, content_type_header); ptr sprintf(ptr, \r\n); // header 结束 if (body body_len 0) { memcpy(ptr, body, body_len); ptr body_len; } *ptr \0; return response; } // 示例处理 GET / 的响应 void handle_root_get(int client_fd) { const char* html htmlbodyh1Hello from C HTTP Server!/h1/body/html; char* response build_http_response(200, OK, text/html; charsetutf-8, html, strlen(html), 1); if (response) { send(client_fd, response, strlen(response), 0); free(response); } }逻辑说明build_http_response()动态计算响应总长度避免栈溢出。get_current_time_rfc1123()生成符合 HTTP 标准的 GMT 时间strftime的%a, %d %b %Y %H:%M:%S GMT格式。Connection: keep-alive的keep_alive参数由上层逻辑决定——例如解析请求 header 发现Connection: keep-alive且HTTP/1.1则传1。参数说明content_type必须包含字符集声明如text/html; charsetutf-8否则中文会乱码。Content-Length严格等于body_len这是keep-alive正常工作的前提客户端靠它知道 body 结束位置才能发起下一个请求。若body_len错误客户端将永远等待。3.3 实现 HTTP/1.1 Keep-Alive状态机驱动的请求循环keep-alive不是“不关 socket”而是在发送完一个响应后不调用close()而是重置缓冲区重新进入请求解析循环。难点在于如何区分同一个 socket 上的多个请求答案是每个请求独立解析parse_http_request()返回1表示完整请求0表示不完整需等待-1表示错误。// server_http.c支持 keep-alive 的 HTTP 服务器主循环 #include stdio.h #include stdlib.h #include string.h #include unistd.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include sys/epoll.h #include fcntl.h #include http_parser.c // 包含前述解析器 #include http_response.c // 包含前述响应生成器 #define MAX_EVENTS 100 #define BUFFER_SIZE 8192 typedef struct { int fd; char buffer[BUFFER_SIZE]; size_t buf_len; int keep_alive; // 当前连接是否启用 keep-alive } client_context_t; int set_nonblocking(int fd) { int flags fcntl(fd, F_GETFL, 0); if (flags -1) return -1; return fcntl(fd, F_SETFL, flags | O_NONBLOCK); } int main() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); if (listen_fd -1) { perror(socket); return 1; } int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in serv_addr {0}; serv_addr.sin_family AF_INET; serv_addr.sin_addr.s_addr INADDR_ANY; serv_addr.sin_port htons(8080); if (bind(listen_fd, (struct sockaddr*)serv_addr, sizeof(serv_addr)) -1) { perror(bind); close(listen_fd); return 1; } if (listen(listen_fd, 5) -1) { perror(listen); close(listen_fd); return 1; } if (set_nonblocking(listen_fd) -1) { perror(set_nonblocking listen_fd); close(listen_fd); return 1; } int epoll_fd epoll_create1(0); if (epoll_fd -1) { perror(epoll_create1); close(listen_fd); return 1; } struct epoll_event ev, events[MAX_EVENTS]; ev.events EPOLLIN; ev.data.fd listen_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, ev) -1) { perror(epoll_ctl: listen_fd); close(listen_fd); close(epoll_fd); return 1; } // 客户端上下文数组 client_context_t clients[1000]; for (int i 0; i 1000; i) { clients[i].fd -1; clients[i].buf_len 0; clients[i].keep_alive 0; } printf(HTTP/1.1 Keep-Alive Server running on port 8080...\n); while (1) { int nfds epoll_wait(epoll_fd, events, MAX_EVENTS, 5000); if (nfds -1) { perror(epoll_wait); continue; } else if (nfds 0) { continue; } for (int i 0; i nfds; i) { if (events[i].data.fd listen_fd) { // 新连接 struct sockaddr_in client_addr; socklen_t client_len sizeof(client_addr); int client_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); if (client_fd 0) { printf(New HTTP connection from %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (set_nonblocking(client_fd) -1) { perror(set_nonblocking client_fd); close(client_fd); continue; } // 查找空闲 context int ctx_idx -1; for (int j 0; j 1000; j) { if (clients[j].fd -1) { ctx_idx j; break; } } if (ctx_idx -1) { close(client_fd); continue; } clients[ctx_idx].fd client_fd; clients[ctx_idx].buf_len 0; clients[ctx_idx].keep_alive 1; // 默认启用 ev.events EPOLLIN | EPOLLET; ev.data.ptr clients[ctx_idx]; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, ev) -1) { perror(epoll_ctl: client_fd); close(client_fd); clients[ctx_idx].fd -1; } } } else { // 已连接客户端 client_context_t* ctx (client_context_t*)events[i].data.ptr; char* buf ctx-buffer ctx-buf_len; size_t remaining BUFFER_SIZE - ctx-buf_len; ssize_t n recv(ctx-fd, buf, remaining, 0); if (n 0) { ctx-buf_len n; // 循 p a hrefhttps://download.csdn.net/download/ashyyyy/90400312 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表