Linux 学习笔记(四十):TCP 编程——从理论到 EchoServer 实战

发布时间:2026/10/30 4:51:59
Linux 学习笔记(四十):TCP 编程——从理论到 EchoServer 实战 核心结论TL;DRTCP 核心特性面向连接三次握手、可靠传输、面向字节流。服务端五步曲socket()-bind()-listen()-accept()-read()/write()。极其重要accept()成功后返回的是一个全新的文件描述符connfd专门用于和该客户端通信原来的listenfd继续监听。断线感知TCP 中read()返回 0代表对端客户端主动关闭了连接而不是出错。踩坑防范服务端重启常遇Address already in use需用setsockopt()设置SO_REUSEADDR端口复用。一、 TCP 核心流程解析TCP 就像打电话必须先建立连接。服务器端socket创建电话机bind插卡绑定固定号码listen开机待机accept接听。客户端socket创建电话机connect直接拨号。(注客户端不需要手动bindconnect时操作系统会自动分配随机端口)二、 为什么 accept 要返回新的套接字listen_fd是负责监听是否有新连接进来的只负责拉客而accept返回的conn_fd才是真正用来和当前客户端通信的。如果不这样做服务器就无法同时为多个客户端服务。三、 实战单进程 TCP Echo 服务器(这里只展示核心逻辑完整代码见文末)// 端口复用解决重启报错问题 int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); // 接收连接 int conn_fd accept(listen_fd, (struct sockaddr*)client_addr, client_len); // 通信与断线判断 int n read(conn_fd, buffer, BUF_SIZE); if (n 0) { // 正常收到数据 write(conn_fd, buffer, n); } else if (n 0) { // 重点n 0 代表客户端关闭了连接 (收到了 FIN 包) printf(客户端已断开连接。\n); break; }四、 踩坑记录Address already in use现象强制CtrlC结束服务器后立刻重新运行报错bind error: Address already in use。原因TCP 连接断开时主动断开方会进入TIME_WAIT状态占用端口一段时间通常是 2MSL约 1 分钟防止最后一个 ACK 丢失导致连接混乱。解决在bind前调用setsockopt设置SO_REUSEADDR允许端口复用。五、 局限性当前的服务器是单进程、单线程的。一旦accept成功服务器就陷入了while(read)的阻塞。此时第二个客户端连都连不进来。自我实战操作cd ~/linux_study/network_test服务端tcp_server.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include arpa/inet.h #define PORT 9999 #define BUF_SIZE 1024 int main() { int listen_fd socket(AF_INET, SOCK_STREAM, 0); // 端口复用防止重启时 Address already in use int opt 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, opt, sizeof(opt)); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); server_addr.sin_addr.s_addr INADDR_ANY; bind(listen_fd, (struct sockaddr*)server_addr, sizeof(server_addr)); listen(listen_fd, 5); printf(TCP 服务器启动监听端口 %d...\n, PORT); struct sockaddr_in client_addr; socklen_t len sizeof(client_addr); // 核心阻塞等待客户端连接 int conn_fd accept(listen_fd, (struct sockaddr*)client_addr, len); printf(接到客户端 %s:%d\n, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); char buffer[BUF_SIZE]; while (1) { int n read(conn_fd, buffer, BUF_SIZE - 1); if (n 0) { buffer[n] \0; printf(收到消息: %s\n, buffer); write(conn_fd, buffer, n); // Echo回显 } else if (n 0) { // 核心客户端断开 printf(客户端断开了连接。\n); break; } } close(conn_fd); close(listen_fd); return 0; }客户端tcp_client.c#include stdio.h #include stdlib.h #include string.h #include unistd.h #include arpa/inet.h #define SERVER_IP 127.0.0.1 #define PORT 9999 #define BUF_SIZE 1024 int main() { int sock_fd socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in server_addr; memset(server_addr, 0, sizeof(server_addr)); server_addr.sin_family AF_INET; server_addr.sin_port htons(PORT); inet_pton(AF_INET, SERVER_IP, server_addr.sin_addr); if (connect(sock_fd, (struct sockaddr*)server_addr, sizeof(server_addr)) 0) { perror(connect); return 1; } char buffer[BUF_SIZE]; while (1) { printf(请输入: ); fgets(buffer, BUF_SIZE, stdin); buffer[strcspn(buffer, \n)] 0; if (strcmp(buffer, quit) 0) break; write(sock_fd, buffer, strlen(buffer)); int n read(sock_fd, buffer, BUF_SIZE - 1); if (n 0) { buffer[n] \0; printf(回显: %s\n, buffer); } } close(sock_fd); return 0; }编译运行开两个终端跑起服务端和客户端发消息测试。测试完在客户端按CtrlC断开看看服务端是不是打印了“客户端断开”。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询