Socket(套接字)是实现网络通信的核心技术,不管是做客户端/服务端交互、物联网设备通信,还是简单的跨程序数据传输,都离不开Socket编程。
一、先搞懂:Socket、TCP、UDP到底是什么?
1. Socket的核心作用
Socket就像网络通信的“接口”,是连接应用程序和网络协议的桥梁。两台设备要通信,必须先创建Socket对象,通过它完成“数据发送、接收、连接管理”等所有操作——你可以把它理解为“网络版的文件句柄”,只不过读写的是网络数据而非本地文件。
2. TCP vs UDP:两种通信方式的核心差异
TCP和UDP是Socket编程最常用的两种传输层协议,适用场景完全不同,新手必须先分清:
| 特性 | TCP | UDP |
|---|---|---|
| 连接类型 | 面向连接(需先建立连接) | 无连接(直接发数据) |
| 可靠性 | 可靠传输(丢包重传、顺序保证) | 不可靠(可能丢包、乱序) |
| 速度 | 较慢(有握手/确认机制) | 极快(无额外开销) |
| 适用场景 | 文件传输、登录认证、HTTP通信 | 视频直播、游戏、实时监控 |
| 核心特点 | 三次握手、四次挥手、拥塞控制 | 无握手、无确认、轻量级 |
简单来说:需要确保数据100%送达用TCP,追求速度和实时性用UDP。
二、TCP通信实战:可靠的客户端-服务端交互
TCP通信遵循“先建连接,再传数据”的原则,核心流程是:服务端绑定端口并监听 → 客户端发起连接 → 双方建立通道 → 数据双向传输 → 关闭连接。
1. TCP服务端代码(基础版)
import socket
# 1. 创建TCP Socket对象(AF_INET=IPv4,SOCK_STREAM=TCP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定IP和端口(''表示监听所有本机IP,端口选1024以上未占用的)
server_addr = ('', 8888)
server_socket.bind(server_addr)
# 3. 开始监听(backlog=5,表示最大等待连接数)
server_socket.listen(5)
print("TCP服务端已启动,监听端口8888...")
try:
# 4. 接受客户端连接(阻塞等待,有连接时返回新的socket和客户端地址)
conn, client_addr = server_socket.accept()
print(f"已连接客户端:{client_addr}")
# 5. 接收客户端数据(bufsize=1024,表示每次最多接收1024字节)
data = conn.recv(1024)
# 解码二进制数据为字符串(指定编码,避免乱码)
print(f"收到客户端消息:{data.decode('utf-8')}")
# 6. 向客户端发送响应
response = "已收到你的消息,TCP通信正常!"
conn.send(response.encode('utf-8'))
finally:
# 7. 关闭连接(先关通信socket,再关监听socket)
conn.close()
server_socket.close()
print("连接已关闭")
2. TCP客户端代码(基础版)
import socket
# 1. 创建TCP Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务端(指定服务端IP和端口)
server_addr = ('127.0.0.1', 8888) # 本地测试用127.0.0.1,远程用服务端实际IP
client_socket.connect(server_addr)
print("已连接TCP服务端")
try:
# 3. 向服务端发送数据(需编码为二进制)
message = "Hello TCP Server!"
client_socket.send(message.encode('utf-8'))
# 4. 接收服务端响应
response = client_socket.recv(1024)
print(f"收到服务端响应:{response.decode('utf-8')}")
finally:
# 5. 关闭socket
client_socket.close()
print("客户端已关闭")
3. 运行步骤与关键说明
- 运行顺序:先启动服务端(监听端口),再启动客户端(发起连接);
-
核心函数解读:
-
socket.socket():创建套接字,AF_INET指定IPv4,SOCK_STREAM指定TCP; -
bind():服务端绑定IP和端口,IP为空表示监听所有本机网卡; -
listen():服务端开始监听,参数为最大等待连接数; -
accept():服务端阻塞等待客户端连接,返回新的conn(用于通信)和客户端地址; -
connect():客户端主动连接服务端; -
recv(bufsize):接收数据,返回二进制字节串,需decode()解码; -
send(data):发送数据,需先encode()编码为二进制。
-
4. TCP进阶:处理多客户端连接(循环监听)
基础版只能处理一个客户端,实际开发需循环监听,用while实现:
# TCP服务端(多客户端版)
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(('', 8888))
server_socket.listen(5)
print("TCP服务端已启动,等待客户端连接...")
while True:
# 循环接受新连接
conn, client_addr = server_socket.accept()
print(f"\n新客户端连接:{client_addr}")
try:
# 接收数据(循环接收,直到客户端关闭)
while True:
data = conn.recv(1024)
if not data: # 客户端关闭连接时,recv返回空
print(f"客户端{client_addr}已断开")
break
print(f"收到{client_addr}:{data.decode('utf-8')}")
# 回复客户端
conn.send(f"已收到:{data.decode('utf-8')}".encode('utf-8'))
finally:
conn.close()
三、UDP通信实战:快速的无连接传输
UDP无需建立连接,直接发送数据报,核心流程:服务端绑定端口 → 客户端直接发送数据 → 服务端接收并响应(可选)。
1. UDP服务端代码
import socket
# 1. 创建UDP Socket对象(SOCK_DGRAM=UDP)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定IP和端口
server_addr = ('', 9999)
server_socket.bind(server_addr)
print("UDP服务端已启动,监听端口9999...")
try:
# 3. 接收客户端数据(UDP无需连接,recvfrom返回数据和客户端地址)
data, client_addr = server_socket.recvfrom(1024)
print(f"收到客户端{client_addr}:{data.decode('utf-8')}")
# 4. 向客户端发送响应(需指定客户端地址)
response = "已收到你的UDP消息!"
server_socket.sendto(response.encode('utf-8'), client_addr)
finally:
# 5. 关闭socket
server_socket.close()
print("UDP服务端已关闭")
2. UDP客户端代码
import socket
# 1. 创建UDP Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 直接发送数据(无需连接,指定服务端地址)
server_addr = ('127.0.0.1', 9999)
message = "Hello UDP Server!"
client_socket.sendto(message.encode('utf-8'), server_addr)
print("已发送UDP消息")
try:
# 3. 接收服务端响应
data, server_addr = client_socket.recvfrom(1024)
print(f"收到服务端响应:{data.decode('utf-8')}")
finally:
# 4. 关闭socket
client_socket.close()
print("UDP客户端已关闭")
3. UDP核心差异说明
-
无连接特性:客户端无需
connect(),服务端无需listen()和accept(); -
核心函数:
-
recvfrom():UDP专用,接收数据并获取发送方地址; -
sendto(data, addr):UDP专用,发送数据到指定地址;
-
- 可靠性:UDP不保证数据送达,若需可靠传输,需自己实现“确认重传”逻辑(如物联网常用的MQTT协议基于UDP扩展了可靠性)。
四、Socket编程常见问题与避坑技巧
1. 端口被占用报错(OSError: [Errno 98] Address already in use)
- 原因:指定的端口已被其他程序占用;
- 解决方案:
- 换一个端口(如8888→8889);
- 服务端设置
SO_REUSEADDR,允许端口快速复用:server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
2. 连接超时/拒绝连接(Connection refused)
- 排查步骤:
- 检查服务端是否已启动;
- 检查服务端IP和端口是否正确;
- 检查防火墙是否放行该端口(远程通信需开放端口);
- 本地测试用
127.0.0.1,不要用本机公网IP。
3. 数据传输乱码
- 原因:编码/解码格式不一致;
- 解决方案:统一使用
utf-8编码,避免混用gbk/ascii:# 发送时 data.encode('utf-8') # 接收时 data.decode('utf-8')
4. TCP粘包问题(数据粘在一起接收)
- 原因:TCP是流式传输,多次发送的小数据可能被合并;
- 解决方案:
- 固定数据长度(如每次发送1024字节);
- 加分隔符(如
\n),接收时按分隔符拆分; - 数据头部加长度标识(专业做法)。
五、实战场景:Socket编程的典型应用
- 本地程序通信:同一台电脑上的两个Python程序互传数据;
- 跨设备控制:树莓派(服务端)接收PC(客户端)的控制指令,控制GPIO设备;
- 简易聊天工具:基于TCP实现一对一文字聊天(循环收发消息);
- 实时数据采集:基于UDP采集传感器数据(追求实时性,允许少量丢包)。