TCP客户端与服务器端的核心写法,就是通过Socket套接字建立连接、收发数据、关闭连接三个步骤。客户端用connect()发起请求,服务器端用bind()、listen()、accept()三部曲等待并接受连接,之后双方基于同一个Socket句柄做双向数据读写,这套逻辑在Python、Java、C++、Go等主流语言中大同小异,掌握一次,处处通用。
先搞懂TCP通信的本质
三次握手和四次挥手是地基
TCP是面向连接的可靠传输协议,所谓“可靠”,靠的就是每次通信前先握手,通信后主动挥手。
- 三次握手:客户端发SYN,服务器回SYN+ACK,客户端再回ACK,连接建立。
- 四次挥手:主动方发FIN,被动方回ACK,被动方再发FIN,主动方回ACK,连接关闭。
- 这两个过程发生在操作系统内核协议栈里,不需要应用层代码介入,但理解它对排查问题至关重要。
Socket是编程界的门牌号
Socket不是协议,而是一组编程接口,它把TCP的复杂细节封装成文件描述符,让开发者像读写文件一样操作网络,每个连接由IP地址 + 端口号唯一标识,服务器端监听固定端口,客户端通过IP和端口找上门。
python编写tcp客户端与服务器端:socket模块实操指南
Python的socket模块是学习TCP编程最直观的入口,下面这段代码覆盖了最基础的通信逻辑,也是不少小型项目的真实写法。
服务器端代码框架
import socket
# 1. 创建socket对象
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 解决端口复用问题(TIME_WAIT状态下重启不报错)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 3. 绑定IP和端口
server.bind(('0.0.0.0', 8080))
# 4. 开启监听,backlog指定等待队列长度
server.listen(5)
print('服务器已启动,等待客户端连接...')
while True:
# 5. 接受客户端连接,返回新的socket和地址
client_sock, client_addr = server.accept()
print(f'客户端 {client_addr} 已连接')
# 6. 接收数据(最大4096字节)
data = client_sock.recv(4096)
if data:
print(f'收到消息:{data.decode("utf-8")}')
# 7. 回发数据
client_sock.send(('服务器已收到:' + data.decode("utf-8")).encode('utf-8'))
# 8. 关闭连接
client_sock.close()
客户端代码框架
import socket
# 1. 创建socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务器(换成本机用127.0.0.1)
client.connect(('127.0.0.1', 8080))
# 3. 发送数据
client.send('你好,服务器!'.encode('utf-8'))
# 4. 接收服务器响应
response = client.recv(4096)
print(f'服务器回复:{response.decode("utf-8")}')
# 5. 关闭连接
client.close()
运行顺序是先启动服务器端,再运行客户端,终端里就能看到数据互通。
这个小案例里的三个关键细节
recv()是阻塞的,没有数据时程序会卡在这一行,这是正常现象。send()不保证一次发完,大文件传输需要循环发送并判断返回值。accept()是循环调用的,每次只能处理一个客户端,处理完一个才能接下一个。
tcp长连接与短连接的区别,高并发场景下怎么选
这是工程师面试常客,也是实际设计系统时必须做的选择题。短连接每次通信都走完整三次握手和四次挥手,长连接则在一段时间内复用同一条TCP通道。
短连接的适用场景
- 低频请求,比如查询一次天气、发送一条验证码。
- 连接空闲时间远大于通信时间,省去维护成本。
- 典型业务:HTTP/1.0的网页请求、数据库连接池(用完即归还销毁)。
长连接的适用场景
- 高频双向通信,比如即时聊天、消息推送、股票行情。
- 需要服务器主动推数据给客户端,短连接做不到。
- 典型业务:WebSocket底层、MQTT物联网通信、游戏服务器。
选型决策清单
| 维度 | 短连接 | 长连接 |
|---|---|---|
| 连接开销 | 每次握手+挥手,耗时高 | 只在建立时握手一次 |
| 服务器资源 | 释放快,压力小 | 需要维护每个连接状态 |
| 实时性 | 差,依赖客户端轮询 | 好,服务端可主动推送 |
| 实现难度 | 低 | 高,需处理心跳和粘包 |
| 典型场景 | 普通API接口 | IM、推送、实时游戏 |
行业共识认为,大多数后台业务系统是长短连接混合使用:HTTP短连接处理普通请求,内部系统间用长连接做数据同步。
处理粘包、拆包和心跳检测
写过一段时间TCP代码后你会发现,数据对不上号的问题最让人头疼,这背后是TCP的流式传输特性TCP没有消息边界,就像水管里的水,你倒进去几杯,对方接到的可能是半杯或几杯混合的。
粘包和拆包的解决方案
- 固定长度消息:协议固定每次发100字节,不够就补空格,实现简单但浪费带宽。
- 特殊分隔符:每条消息以
或rn
n像HTTP的头部那样,需要注意内容里不能出现分隔符,需要转义。 - 长度前缀法:每个包前4字节用无符号整数标识消息长度,这是最通用、最推荐的做法,Netty、gRPC等框架都在用这个思路。
代码里用长度前缀法的核心逻辑:
# 发送时:先转4字节长度,再接正文
msg = '深度学习TCP编程'.encode('utf-8')
header = len(msg).to_bytes(4, byteorder='big')
client.send(header + msg)
# 接收时:先读4字节长度,再按长度读正文
import struct
header_data = recv_exact(client, 4)
msg_len = struct.unpack('>I', header_data)[0]
msg = recv_exact(client, msg_len)
心跳包机制是长连接的体检仪
一条TCP连接平时发发的信号只能证明网络通,但不能证明对端进程还活着。心跳包的本质是定期发送一个极小的数据包,对方收到后回复确认,以此证明连接健康。
- 客户端每30秒发送一次心跳,服务端连续3次没收到就判定掉线。
- 心跳间隔通常设为30-60秒,太频繁浪费流量,太稀疏延迟感知。
- 物联网领域常用MQTT的Keep Alive字段来实现,TCP层也可以直接开启
SO_KEEPALIVE选项,但默认2小时才探测一次,实际业务中都需要应用层自己做心跳。
TCP服务器最大连接数和性能调优
“TCP服务器能同时支持多少连接?”这是搭建服务前必须回答的问题。理论上限是65535个端口乘以IP数量,但在实际工程中,瓶颈往往不是系统限制,而是代码写得好不好。
对高并发连接的三个正确认知
- 文件描述符限制:Linux默认单进程最多1024个连接,修改
ulimit -n 65535即可突破,这是最常踩的坑。 - 内存开销:每条TCP连接在内核里大约占用几十KB内存,1万条连接就意味着约1GB内存,需要扩容或优化IO模型。
- 多线程与线程池:为每个连接创建线程虽然简单,但3万线程切换消耗极大,更好的方案是使用非阻塞IO和事件驱动,Python里可以用
selectors或者asyncio,Java里用Netty,Go语言用goroutine天然支持数十万并发。
业内专家指出,绝大多数中小型业务的TCP并发需求在几千到几万这个量级,使用成熟的网络框架(Netty、gRPC、Go标准库)完全足够,不需要自研协议栈。
TCP编程常见问题排查清单
把TCP里最容易翻车的六件事列成以下清单对照即可:
- 端口被占用:服务器启动报
Address already in use,用lsof -i:8080查看占用进程,加解决开发时重启问题。SO_REUSEADDR
- 客户端连接超时:先确认服务器IP能否ping通,再检查防火墙端口是否开放,最后确认服务器是否真的在监听(
netstat -tlnp)。 - 数据发出去但收不到:可能编码不一致,发送端用UTF-8发送,接收端用GBK解码,中文百分百乱码。
- recv()返回空字节:说明对端已正常关闭连接,此时应该退出循环,不要继续读。
- 服务器只能处理一个客户端:
accept()后的业务逻辑不要放在主循环里,开线程或进程去处理。 - 客户端怎么判断服务器不在线:给
connect()设置超时时间,比如client.settimeout(5),5秒连不上就报错重试。
图片或文件传输的TCP实现要点
文本传输搞定了,文件传输只是换一层包装。核心思路是把文件内容读取成二进制,分包循环发送,接收端写盘。
- 先发文件元信息(文件名、文件大小),格式用JSON。
- 按每包1024字节循环读取并发送,接收端持续写入文件。
- 每发一包可以等待对方ACK,实现可靠性;为了提速,可以采用滑动窗口批量发送。
- 进度计算:已发送字节数除以总字节数乘以100。
TCP客户端连不上服务器的常见原因有哪些
这个问题在实际开发中被问得最多,也是百度和各类技术社区里高频搜索的词,多数情况下不是代码问题,而是环境问题:
- 服务器防火墙没放行端口:云服务器需要在控制台安全组里配置入站规则,本地服务器需要firewalld或iptables放行。
- 服务器端没启用监听:监听地址写成了
0.0.1,外部客户端自然访问不了,需要改成0.0.0。 - IP写错或域名解析异常:在客户端机器上
ping一下服务器IP,排障第一步。 - 跨网段不通:公司内网和外部网络之间需要路由策略,VPC和物理机之间用内网IP互联。
四步写完TCP通信的收尾清单
- 第一步:创建Socket实例,绑定端口和地址。
- 第二步:服务器端监听+接受连接,客户端发起连接。
- 第三步:按约定协议收发数据,处理粘包和心跳。
- 第四步:连接结束后优雅关闭,释放文件描述符。
TCP编程的入门和进阶路径很清晰,核心还是多写多调,从本机的0.0.1:8080开始,跑通一次通信,再逐步加上心跳、重连、多线程,你就已经超越了很大比例的新手程序员。TCP没有魔法,每一行代码都是和操作系统协议栈的对话。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/739598.html





