Linux TCP Socket 基础:从监听到收发
梳理 TCP 服务端和客户端的系统调用顺序,并说明字节流、错误处理和资源释放这些最容易被忽略的边界。
- 问题
- 梳理 TCP 服务端和客户端的系统调用顺序,并说明字节流、错误处理和资源释放这些最容易被忽略的边界。
- 环境
- Linux · Socket · TCP · 网络编程 · C
- 状态
- 待发布
- 首次发布
- 2025年10月17日
- 最后更新
- 2026年8月10日
- 预计阅读
- 3 分钟
生命周期说明(2026-08-10):本文由 2025 年的 TCP 学习笔记重写。原笔记中的内网地址、一次性示例和不完整错误路径均未公开保留;本文只说明稳定的接口顺序与实现边界。具体 API 细节以当前系统手册页为准。
Socket 在程序中的位置
Socket 是操作系统提供的通信接口。TCP 程序通常以客户端—服务端的方式建立连接:服务端等待连接,客户端主动连接;连接建立后,双方都可以读取和写入字节流。
TCP 提供的是有序字节流,不是“按一次 send() 对应一次 recv()”的消息队列。因此,应用协议必须自行定义消息边界,例如固定长度头、长度字段或明确分隔符。
服务端的基本顺序
一个最小 TCP 服务端通常按下列顺序运行:
socket()创建套接字;bind()绑定本地地址和端口;listen()将套接字置为监听状态;accept()取得一条已建立连接对应的新文件描述符;- 对新描述符调用
recv()/read()和send()/write(); - 使用完毕后分别
close()已连接描述符和监听描述符。
监听套接字只负责接受连接;accept() 返回的描述符才对应某个客户端。服务端要同时处理多个客户端时,可以选择线程、进程、事件循环或异步 I/O,但每种方案都需要明确并发与关闭策略。
客户端的基本顺序
客户端的路径更短:
socket()创建套接字;- 准备服务端地址;
connect()发起连接;- 读写数据;
close()释放描述符。
在可移植程序中,优先使用 getaddrinfo() 解析主机和端口,再根据返回结果建立连接;这样可避免把 IPv4 地址或局部网络参数写死在源码中。
收发与关闭的关键边界
recv()返回正数表示读到的字节数;返回0表示对端已正常关闭写端;返回负数才需要根据errno判断失败原因。send()或write()可以只写入部分数据。需要完整发送一条消息时,应循环写入直到全部字节完成或发生不可恢复错误。- 网络数据不保证以字符串结尾。若要打印或解析,应以实际返回的长度为界,不要假设缓冲区自动带有
\0。 - 端口号与地址应使用网络字节序转换:端口使用
htons()/ntohs(),IPv4 地址按所用接口转换或打印。 - 任何提前返回路径都应关闭已取得的文件描述符;连接较多时还应限制缓冲区、超时和并发数。
本地验证建议
先只在回环地址上测试:服务端监听一个未被占用的高位端口,客户端连接 127.0.0.1 或 ::1。依次验证连接失败、正常收发、对端关闭、超长输入和服务端重启。确认消息边界与错误日志符合预期后,再考虑局域网监听或更复杂的协议。
这类程序会处理来自网络的输入。公开示例或生产代码应增加长度限制、超时、错误日志和协议校验,而不是直接把接收缓冲区交给字符串或业务逻辑使用。