资讯详情

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

📅 2026/10/30 4:51:49 | 华诺云谱 👁 阅读
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断开看看服务端是不是打印了“客户端断开”。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑