Socket(套接字)是网络编程的基石,Python内置的socket模块无需安装即可实现TCP和UDP通信。本文从零开始,详细讲解TCP与UDP客户端和服务端的完整实现、高频配置选项、粘包问题解决方案及三个实战案例,帮助你快速掌握Python网络编程。
一、Socket对象创建
导入模块后,通过socket.socket(family, type)创建套接字,family指定地址族(通常为socket.AF_INET代表IPv4),type指定协议类型:socket.SOCK_STREAM为TCP,socket.SOCK_DGRAM为UDP。- import socket
- # TCP套接字
- tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # UDP套接字
- udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
复制代码
二、TCP通信全套API
TCP是面向连接的可靠协议,通信前需建立三次握手。
1. TCP客户端
客户端需依次执行:创建套接字、设置超时(可选)、连接服务器、发送/接收数据、关闭连接。- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.settimeout(5) # 5秒无响应超时
- sock.connect(("127.0.0.1", 8080))
- sock.send("你好服务端".encode("utf-8"))
- data = sock.recv(1024)
- print(data.decode("utf-8"))
- sock.close()
复制代码 2. TCP服务端
服务端创建后需绑定IP和端口、监听、等待客户端连接,然后处理收发。注意启用端口复用避免重启时报错。- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 端口复用
- server.bind(("0.0.0.0", 8080))
- server.listen(5)
- print("服务端已启动,等待连接...")
- conn, client_addr = server.accept()
- print("客户端地址:", client_addr)
- msg = conn.recv(1024)
- print("收到:", msg.decode())
- conn.send("收到消息".encode("utf-8"))
- conn.close()
复制代码
三、UDP通信全套API
UDP无连接,直接发数据包。核心方法为sendto和recvfrom。
1. UDP客户端(只发数据)- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.sendto(b"心跳包", ("127.0.0.1", 9999))
- sock.close()
复制代码 2. UDP服务端接收数据- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- sock.bind(("0.0.0.0", 9999))
- while True:
- data, addr = sock.recvfrom(1024)
- print(f"来自{addr}: {data.decode()}")
- sock.sendto(b"ok", addr)
复制代码 3. UDP广播设置(局域网设备发现)- sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
- sock.sendto(b"online", ("255.255.255.255", 9999))
复制代码
四、高频通用配置选项
1. 端口复用:避免服务端重启时提示Address already in use。- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
复制代码 2. 设置阻塞超时:recv/accept默认永久阻塞,务必加超时。- sock.settimeout(3)
- try:
- data = sock.recv(1024)
- except socket.timeout:
- print("接收超时")
复制代码 3. 关闭Nagle算法:小包立即发送,适合实时性场景。- sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
复制代码 4. 获取本机IP:socket.gethostbyname(socket.gethostname())
5. 域名解析为IP:socket.gethostbyname("www.baidu.com")
五、TCP粘包问题及解决方案
TCP是字节流,多次send的数据可能被合并接收。常用方案:固定长度、前置数据长度、使用分隔符。
示例:发送端添加分隔符"|||",接收端按分隔符切割。- # 发送端
- sock.send(b"hello|||")
- # 接收端按分隔符切割数据
复制代码
六、实战案例
案例1:TCP端口存活检测- def port_check(host, port, timeout=2):
- try:
- sock = socket.socket()
- sock.settimeout(timeout)
- sock.connect((host, port))
- sock.close()
- return True
- except:
- return False
复制代码 案例2:裸Socket发送HTTP请求- sock = socket.socket()
- sock.connect(("www.baidu.com", 80))
- http_header = b"GET / HTTP/1.1\r\nHost:www.baidu.com\r\nConnection:close\r\n\r\n"
- sock.send(http_header)
- print(sock.recv(4096).decode("utf-8", errors="ignore"))
- sock.close()
复制代码
七、避坑总结
1. 所有收发数据必须是bytes,字符串需.encode("utf-8")。
2. TCP必须先连接再收发,UDP直接sendto。
3. recv缓冲区合理设置(如1024),太小会读不全。
4. 必须设置超时,避免无限阻塞。
5. 服务端绑定0.0.0.0才能对外网与局域网开放,绑定127.0.0.1仅本机。
6. 短连接收发完毕及时close,避免大量TIME_WAIT占用端口。
掌握以上内容,你已具备Python socket编程的核心能力。如需多线程TCP服务端、asyncio异步socket、自定义TCP分包协议等高级内容,欢迎探讨。 |