【C/C】从 socket 到多线程 TCP Echo 服务器fd、accept 与 recv/send 全流程1. 这篇文章解决什么问题在学习网络编程时第一道坎不是epoll而是搞清楚服务端从“监听端口”到“处理客户端数据”到底经历了什么。这个项目里的tcp_server_threads.c是一个非常直接的 TCP Echo Server客户端发什么服务端就原样回什么。它覆盖了 TCP 服务端最核心的 5 步socket()创建监听套接字。bind()绑定 IP 和端口。listen()让端口进入监听状态。accept()从已建立连接队列中取出一个客户端连接。recv()/send()对客户端 fd 读写数据。2. fd 和连接不是一回事README 中有一个很关键的点fd 是进程级的 IO 句柄而 TCP 连接状态在内核协议栈里维护。accept()之前三次握手可能已经完成内核里已经有连接状态但应用层还没有拿到可读写的客户端 fd。accept()返回之后内核才给应用分配一个新的 fd之后应用才能对这个 fd 调用recv()和send()。项目里的服务端监听端口是 8080intserverfdsocket(AF_INET,SOCK_STREAM,0);structsockaddr_inserver_addr;server_addr.sin_familyAF_INET;server_addr.sin_addr.s_addrhtonl(INADDR_ANY);server_addr.sin_porthtons(8080);bind(serverfd,(structsockaddr*)server_addr,sizeof(server_addr));listen(serverfd,5);这里的serverfd是监听 socket只负责接收新连接不负责承载某个客户端的数据收发。真正和客户端通信的是accept()返回的clientfd。3. accept 之后创建线程多线程版的思路非常朴素主线程只负责accept()每来一个客户端就创建一个线程处理。while(1){structsockaddr_inclient_addr;socklen_tclient_lensizeof(client_addr);intclientfdaccept(serverfd,(structsockaddr*)client_addr,client_len);if(clientfd0){perror(accept);continue;}pthread_ttid;int*pclientmalloc(sizeof(int));*pclientclientfd;pthread_create(tid,NULL,handle_client,pclient);pthread_detach(tid);}这里把clientfd放到堆内存里传给线程是因为局部变量在下一轮循环可能被覆盖。线程函数处理完以后会free(arg)。pthread_detach(tid)的作用是让线程结束后自动回收资源主线程不需要再pthread_join()。这对 echo server 这种“连接来了就独立处理”的模型比较方便。4. recv/send 实现 Echo线程函数的逻辑就是持续收数据、打印、再写回客户端void*handle_client(void*arg){intclientfd*(int*)arg;charbuffer[1024];ssize_tn;while((nrecv(clientfd,buffer,sizeof(buffer)-1,0))0){buffer[n]\0;printf(Received from client: %s\n,buffer);send(clientfd,buffer,n,0);}close(clientfd);free(arg);returnNULL;}几个细节值得注意recv()返回大于 0表示读到了数据。recv()返回 0通常表示对端正常关闭连接。recv()返回小于 0表示发生错误。send()这里直接把收到的n字节写回去所以它是一个 Echo Server。5. 编译和测试编译gcc tcp_server_threads.c-othreads-pthread启动服务端./threads另开一个终端用nc测试nc127.0.0.18080hello tcp客户端输入hello tcp后服务端会打印收到的数据客户端也会收到同样的响应。6. 多线程模型的优缺点优点很明显代码直观符合“一个连接一个处理流程”的思维。阻塞式recv()/send()也容易理解。很适合作为 TCP 服务端入门代码。缺点也很明显每个连接都创建线程线程栈和调度成本都不低。并发连接一多系统会被线程数量拖垮。适合 C10、C100 级别学习不适合作为 C10K/C100K 的核心模型。如果你在本地测试时遇到bind failed大概率是端口已经被占用或者上一次进程还没完全退出。可以用ss-lntp|grep80807. 小结多线程 TCP Echo Server 是网络编程的第一块积木。理解它之后再看select、poll、epoll就会更自然后面的模型本质上都是在解决同一个问题如何不用“每个连接一个线程”的方式管理大量 fd。学习链接: https://github.com/0voice