3、Python Socket编程入门:创建Socket服务端、创建Socket客户端、数据发送与接收、异常处理
好,咱们进入实战环节。前面聊了那么多理论,说白了,Socket就是操作系统给应用层开的一扇门。今天这章,我带你亲手把这扇门推开,写一个能跑起来的服务端和客户端。
我个人习惯,学任何网络编程,先写一个“回声”程序——客户端发什么,服务端原样返回什么。别看简单,它能把整个数据流跑通,后面再往上加业务逻辑就顺了。
3.1 一个最简的Socket服务端
先看服务端。Python标准库里的 socket 模块,开箱即用。
import socket
# 1. 创建 socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定地址和端口
server_socket.bind(('0.0.0.0', 8888))
# 3. 开始监听
server_socket.listen(5)
print('服务端已启动,等待客户端连接...')
# 4. 接受客户端连接
client_socket, client_addr = server_socket.accept()
print(f'客户端 {client_addr} 已连接')
# 5. 接收数据
data = client_socket.recv(1024)
print(f'收到: {data.decode()}')
# 6. 发送响应
client_socket.send(b'Hello from server!')
# 7. 关闭连接
client_socket.close()
server_socket.close()
这里有几个关键点,我当年踩过坑:
AF_INET:指定IPv4协议。别写成AF_INET6,除非你确定客户端也走IPv6。SOCK_STREAM:TCP协议。UDP用SOCK_DGRAM,但咱们日志系统走TCP,保证不丢包。bind(('0.0.0.0', 8888)):0.0.0.0表示监听所有网卡。如果只允许本机访问,可以写'127.0.0.1'。listen(5):这个5是连接等待队列的长度。不是最大连接数,是“还没被accept处理的连接”的上限。
注意: 端口号小于1024需要root权限。开发调试时,建议用 8000-9999 之间的端口。我曾经在客户现场用 bind(('0.0.0.0', 80)) 没加sudo,程序直接崩了,排查了半天才发现是权限问题。
3.2 对应的Socket客户端
服务端写好了,客户端更简单。说白了就是“连上去,说话,听回话,断开”。
import socket
# 1. 创建 socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务端
client_socket.connect(('127.0.0.1', 8888))
# 3. 发送数据
client_socket.send(b'Hello from client!')
# 4. 接收响应
response = client_socket.recv(1024)
print(f'服务端响应: {response.decode()}')
# 5. 关闭
client_socket.close()
嗯,这里要注意:connect 的地址必须和服务端 bind 的地址能通。如果服务端在另一台机器,把 '127.0.0.1' 换成它的IP。
3.3 数据发送与接收的底层逻辑
很多新手会问:recv(1024) 里的1024是什么意思?是不是一次最多收1024字节?
对,但不全对。1024是缓冲区大小。如果对方发了2000字节,第一次 recv 只能拿到前1024,剩下的需要第二次 recv 才能拿到。这就是TCP的“流”特性——它不帮你分消息边界。
核心概念: TCP是流协议,不是消息协议。你发两次 send,对方可能一次 recv 就全收了;你发一次 send,对方也可能分两次 recv 才能收完。这就是所谓的“粘包”和“拆包”。
我在项目中遇到过一个问题:客户端发了一条JSON日志,服务端 recv 只收到一半,解析JSON直接报错。后来怎么解决的?加了一个简单的长度头——先发4字节的消息长度,再发消息体。服务端先收4字节算出长度,再收对应长度的数据。这个模式后面章节会详细讲。
3.4 异常处理——别让程序裸奔
网络编程里,异常是常态。客户端突然断网、服务端重启、防火墙掐连接……任何一个环节出问题,你的程序都得兜住。
我建议至少捕获这几类异常:
| 异常类型 | 触发场景 | 处理建议 |
|---|---|---|
ConnectionRefusedError |
服务端没启动,客户端就去连 | 重试或报错,不要静默失败 |
TimeoutError |
网络延迟,连接或收发超时 | 设置超时时间,避免线程卡死 |
ConnectionResetError |
对方突然关闭连接 | 清理资源,关闭socket |
BrokenPipeError |
向已关闭的连接写数据 | 写之前检查连接状态 |
来看一个带异常处理的服务端示例:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.settimeout(30) # 设置超时,避免死等
server_socket.bind(('0.0.0.0', 8888))
server_socket.listen(5)
try:
while True:
try:
client_socket, addr = server_socket.accept()
with client_socket:
data = client_socket.recv(1024)
if not data:
break # 客户端关闭连接
client_socket.send(b'ACK: ' + data)
except socket.timeout:
print('等待客户端连接超时,继续监听...')
continue
except ConnectionResetError:
print('客户端异常断开')
continue
except KeyboardInterrupt:
print('服务端手动停止')
finally:
server_socket.close()
小技巧: 用 with client_socket: 可以自动关闭连接,不用手动调 close()。但 server_socket 建议手动管理,因为它的生命周期通常贯穿整个程序。
3.5 本章知识体系总览
下面这张图,把服务端和客户端的核心流程串起来了。你写代码的时候,脑子里可以过一遍这个流程。
这张图左边是服务端流程,右边是客户端流程。虚线箭头表示TCP三次握手建立连接。注意看,服务端多了一个 bind 和 listen,客户端多了一个 connect。其他步骤基本对称。
3.6 避坑指南
最后,分享几个我亲手踩过的坑:
- 地址已被占用:服务端崩溃后立即重启,有时会报
Address already in use。这是因为操作系统还没释放端口。解决方案:在bind之前加server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)。 - send 不一定发完:
send返回的是实际发送的字节数。如果网络拥堵,可能只发了一半。严谨的做法是循环发送,直到所有数据发完。 - recv 返回空字节:如果
recv返回b'',说明对方已经优雅关闭了连接。这时候应该退出接收循环,不要再调recv。
调试小技巧: 用 telnet 127.0.0.1 8888 可以快速测试服务端是否正常。如果telnet能连上,说明服务端没问题,问题大概率在客户端代码里。