【C/C++】从 socket 到多线程 TCP Echo 服务器:fd、accept 与 recv/send 全流程
2026/6/26 5:29:45 网站建设 项目流程

【C/C++】从 socket 到多线程 TCP Echo 服务器:fd、accept 与 recv/send 全流程

1. 这篇文章解决什么问题

在学习网络编程时,第一道坎不是epoll,而是搞清楚服务端从“监听端口”到“处理客户端数据”到底经历了什么。这个项目里的tcp_server_threads.c是一个非常直接的 TCP Echo Server:客户端发什么,服务端就原样回什么。

它覆盖了 TCP 服务端最核心的 5 步:

  1. socket()创建监听套接字。
  2. bind()绑定 IP 和端口。
  3. listen()让端口进入监听状态。
  4. accept()从已建立连接队列中取出一个客户端连接。
  5. recv()/send()对客户端 fd 读写数据。

2. fd 和连接不是一回事

README 中有一个很关键的点:fd 是进程级的 IO 句柄,而 TCP 连接状态在内核协议栈里维护。

accept()之前,三次握手可能已经完成,内核里已经有连接状态;但应用层还没有拿到可读写的客户端 fd。accept()返回之后,内核才给应用分配一个新的 fd,之后应用才能对这个 fd 调用recv()send()

项目里的服务端监听端口是 8080:

intserverfd=socket(AF_INET,SOCK_STREAM,0);structsockaddr_inserver_addr;server_addr.sin_family=AF_INET;server_addr.sin_addr.s_addr=htonl(INADDR_ANY);server_addr.sin_port=htons(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_len=sizeof(client_addr);intclientfd=accept(serverfd,(structsockaddr*)&client_addr,&client_len);if(clientfd<0){perror("accept");continue;}pthread_ttid;int*pclient=malloc(sizeof(int));*pclient=clientfd;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((n=recv(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|grep8080

7. 小结

多线程 TCP Echo Server 是网络编程的第一块积木。理解它之后,再看selectpollepoll就会更自然:后面的模型本质上都是在解决同一个问题,如何不用“每个连接一个线程”的方式管理大量 fd。

学习链接: https://github.com/0voice

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询