目录
- 一、 网络通信的“通行证”:什么是 TCP 协议?
- 1. 面向连接
- 2. 可靠传输
- 3. 数据流传输
- TCP 与 UDP 的技术对比
- 二、 核心组件:认识 Python 中的 Socket 模块
- 三、 服务端(Server)开发流程:
- 四、 客户端 (Client) 开发流程:
- 五、 实战演练:一个简单的“回声”服务器 (Echo Server)
- 1. 服务端代码 (`server.py`)
- 2. 客户端代码 (`client.py`)
- 六、 避坑指南:TCP 开发中的常见问题
- 1. 字符串 vs 字节流 (bytes)
- 2. 端口占用与地址复用
- 3. 阻塞 (Blocking) 现象
- 4. 端口选择建议
一、 网络通信的“通行证”:什么是 TCP 协议?
TCP 是互联网协议族中最核心的协议之一。如果说 IP 协议负责把数据包送到指定的地址,那么TCP 协议则负责确保这些数据包是完整、正确且有序的。
1. 面向连接
TCP 要求在传输任何应用层数据之前,通信双方必须先建立一个逻辑上的双向通道。
- 实现方式:三次握手
- SYN: 客户端发送同步序列号,请求建立连接。
- SYN + ACK: 服务端确认收到请求,并发送自己的同步信号。
- ACK: 客户端确认收到服务端的信号。
- 目的:确保双方都具备发送和接收数据的能力。
2. 可靠传输
TCP 提供了一套复杂的机制来修补网络层(IP 层)可能出现的各种问题:
- 序列号与确认号:每一个发出的字节都有编号。接收方收到后必须回复一个确认号。如果发送方在规定时间内没收到确认,就会自动重发。
- **校验和 **:检测数据在传输过程中是否发生了损坏。如果数据不对,直接丢弃并请求重传。
- 流量控制:通过“滑动窗口”机制,服务端会告诉客户端:“我现在只能处理 5KB 数据,请不要发太快”,防止接收端缓冲区溢出。
3. 数据流传输
TCP 是基于字节流的。这意味着它不保留记录边界(即它不关心你一次send了多少)。
- 特性:发送方发送的 100 字节,接收方可以分两次接收(每次 50 字节),也可以一次性接收。
- 后果:这导致了开发中常见的“粘包”问题,开发者需要自己通过协议头(如先发送长度)来解析完整的数据包。
TCP 与 UDP 的技术对比
在开发选型时,理解这两者的区别至关重要:
| 特性 | TCP | UDP |
|---|---|---|
| 连接性 | 必须建立连接 | 无连接,直接发送 |
| 可靠性 | 保证交付(重传机制) | 不保证交付,可能丢包 |
| 顺序性 | 保证按序到达 | 可能乱序 |
| 速度 | 较慢(因为有确认机制) | 极快(无额外开销) |
| Python 参数 | socket.SOCK_STREAM | socket.SOCK_DGRAM |
总结:在不能有一点错误的情况下,就要用tcp协议来传输数据
二、 核心组件:认识 Python 中的 Socket 模块
在 Python 中,实现网络通信的核心工具是socket模块。你可以把它想象成一个“插座”或者“通信口”。
importsocket# 创建一个 TCP Socket# AF_INET 表示使用 IPv4 地址# SOCK_STREAM 表示使用 TCP 协议server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)三、 服务端(Server)开发流程:
服务端就像是一个全天候营业的客服中心,它的动作分为五步:
- 创建插座:准备好电话机。
- 绑定地址 (bind):给客服中心挂个牌子(告诉别人你的 IP 和 端口)。
- 开启监听 (listen):把电话线插好,等待电话铃响。
- 接受连接 (accept):听到铃响,拿起电话说“喂,您好”。
- 数据收发 (send/recv):开始沟通。
四、 客户端 (Client) 开发流程:
客户端就像是拨打电话的客户,流程非常直接:
- 初始化:拿出手机。
- 主动出击 (connect):拨打客服中心的号码。
- 信息交流:说话或者听对方说话。
五、 实战演练:一个简单的“回声”服务器 (Echo Server)
我们来写一段代码:客户端发什么,服务端就原样返回什么。
1. 服务端代码 (server.py)
importsocket# 1. 创建 socketserver=socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 2. 绑定 IP 和 端口 (127.0.0.1 指向本机),注意这里传的是元组server.bind(('127.0.0.1',8888))# 3. 监听server.listen(5)print("服务器已启动,等待连接...")# 4. 等待客户端连接 (会阻塞在这里),直到客户端发来链接请求conn,addr=server.accept()print(f"连接成功!客户端地址:{addr}")# 5. 接收并发送数据data=conn.recv(1024)# 接收 1024 字节,也可以是其他,如果传文件一般用8kbprint(f"收到消息:{data.decode('utf-8')}")conn.send("收到!内容已备份。".encode('utf-8'))# 关闭连接conn.close()#一般关闭连接,这个连接相当于店里的服务员,客户服务结束后这个服务员就休息#但是服务器一般不会只有一个客户,所以一般不在这个时候关#server.close()2. 客户端代码 (client.py)
importsocket# 1. 创建 socketclient=socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 2. 连接服务器client.connect(('127.0.0.1',8888))# 3. 发送数据client.send("你好,我是 Python 初学者!".encode('utf-8'))# 4. 接收回馈response=client.recv(1024)print(f"服务器回复:{response.decode('utf-8')}")# 关闭client.close()六、 避坑指南:TCP 开发中的常见问题
在实际开发中,你一定会遇到以下几个“坑”:
1. 字符串 vs 字节流 (bytes)
- 现象:直接调用
send("hello")会抛出TypeError。 - 原理:TCP 协议传输的是底层的二进制流。Python 3 中的字符串是 Unicode 字符,必须转换。
- 对策:
- 发送前编码:
data.encode('utf-8') - 接收后解码:
data.decode('utf-8') - 如果没有中文时,可以用语法糖 b’转换的内容’ 直接转成二进制
- 发送前编码:
注:在 Python 中,
encode()和decode()默认使用utf-8。虽然也可以指定为gbk,但编解码规则必须严格统一。
- 原因:不同编码对字符占用的空间不同。例如,UTF-8 处理中文通常占 3 个字节,而 GBK 占 2 个字节。
- 后果:如果编码和解码方式不匹配,解析时就会找错字节位置,导致中文乱码。
- 特例:由于两者都兼容 ASCII 编码,如果传输内容仅包含英文和数字,通常不会触发乱码问题,但处理中文时一定要格外小心。
2. 端口占用与地址复用
- 现象:报错
OSError: [Errno 13] Permission denied或Address already in use。 - 原理:
- 身份标识:IP 地址决定了数据包发往哪台设备(公网 IP 全球唯一,局域网 IP 仅在内网唯一);而端口决定了数据包交给哪个程序(范围 0-65535)。
- 残留锁定:当你停止程序后,操作系统为了确保数据传输的安全完整,会将端口锁定一段时间(TIME_WAIT 状态),导致无法立即重启。
- 对策:在
bind()之前,通过代码设置地址复用选项:
importsocket server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 【核心代码】解决端口占用问题# 设置 SO_REUSEADDR 为 1,允许程序立即重启并使用处于 TIME_WAIT 状态的端口server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)server.bind(('127.0.0.1',8888))3. 阻塞 (Blocking) 现象
- 现象:程序运行到
accept()或recv()时,“卡住”不动了。 - 原理:这是 TCP 套接字的默认行为。
accept()会一直等待,直到有客户端拨号进来。recv()会一直等待,直到对方发送数据或关闭连接。
- 建议:在调试时,可以在这些代码前后加上
print("正在等待连接...")之类的提示语,这样你就知道程序是在正常工作还是真的死机了。
4. 端口选择建议
- 避开知名端口:不要使用 0-1023 端口(这些通常需要系统管理员权限,且已被系统服务占用)。
- 推荐范围:开发练习时,建议使用8000-10000之间的端口号,冲突概率较低。