欢迎来到 Python 学习计划的第 70 天!🎉
恭喜你完成了 进阶阶段:类型提示与工程化(第 51-69 天) 的全部学习!从今天开始,我们将进入 网络编程模块:Socket 编程基础(第 70-73 天)。
这是 Python 网络编程的核心基础!掌握 Socket 编程,你将能够:
- 理解网络通信的底层原理
- 构建 TCP/UDP 网络应用
- 实现客户端 - 服务器架构
- 为学习 Web 开发、分布式系统打下坚实基础
📚 什么是 Socket?
Socket(套接字)是操作系统提供的网络编程接口,它允许应用程序通过网络进行通信。Python 的 socket 模块封装了底层的 BSD Socket API,使我们可以方便地进行网络编程。
import socket# 创建一个 TCP Sockettcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 创建一个 UDP Socketudp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
Socket 就像一个"电话",通过它你可以:
- 建立连接(像拨号一样)
- 发送数据(像说话一样)
- 接收数据(像听音一样)
- 关闭连接(像挂电话一样)
🔑 核心概念
Socket 基础组件
1. 地址族(Address Family)
指定 Socket 使用的网络协议版本
地址族 | 常量 | 说明 |
|---|
IPv4 | AF_INET
| 使用 IPv4 地址(最常用) |
IPv6 | AF_INET6
| 使用 IPv6 地址 |
Unix | AF_UNIX
| 本地进程间通信 |
2. Socket 类型
指定通信方式:
类型 | 常量 | 说明 |
|---|
TCP | SOCK_STREAM
| 流式套接字,面向连接,可靠传输 |
UDP | SOCK_DGRAM
| 数据报套接字,无连接,不保证可靠 |
Raw | SOCK_RAW
| 原始套接字,直接访问网络层 |
3. 网络地址表示
网络通信需要知道目标的两个信息:
# 主机地址 + 端口号 = 完整网络地址address = ('127.0.0.1', 8888)# └─ 主机 └─ 端口
常见主机地址:
127.0.0.10.0.0.0192.168.1.1
端口号范围:
🖥️ TCP 编程:面向连接的通信
TCP(Transmission Control Protocol)是面向连接的协议,通信前需要建立连接,通信完成后需要关闭连接。
TCP 三次握手
在建立 TCP 连接时,客户端和服务器需要通过三次握手来同步状态:
握手过程:
- 第一次握手:客户端发送 SYN包,告诉服务器"我要连接你"
- 第二次握手:服务器回复 SYN-ACK包,表示"我收到了,我也同意接"
- 第三次握手:客户端发送 ACK包,表示"我收到了你的同意"
三次握手完成后,连接建立,双方可以开始传输数据。
TCP 通信流程

TCP 服务器实现
TCP 服务器的基本步骤:socket() → bind() → listen() → accept() → recv()/send() → close()
完整示例:
import socketdef tcp_server(host='127.0.0.1', port=8888): # 1. 创建 Socket server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 设置地址重用,避免"Address already in use"错误 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 3. 绑定地址和端口 server.bind((host, port)) print(f"服务器启动,监听 {host}:{port}") # 4. 开始监听(参数是等待队列的最大长度) server.listen(5) try: while True: # 5. 接受客户端连接 client_socket, client_addr = server.accept() print(f"✅ 客户端连接: {client_addr}") try: while True: # 6. 接收数据 data = client_socket.recv(1024) if not data: print(f"客户端 {client_addr} 关闭连接") break message = data.decode('utf-8') print(f"📨 收到: {message}") # 7. 发送响应 response = f"服务器已收到: {message}" client_socket.send(response.encode('utf-8')) finally: # 8. 关闭客户端连接 client_socket.close() except KeyboardInterrupt: print("\n服务器关闭") finally: server.close()if __name__ == "__main__": tcp_server()
关键点解释:
socket()bind()listen() - 将 Socket 设置为监听状态,参数 5 表示等待队列最多容纳 5 个连接accept() - 阻塞等待客户端连接,返回连接后的 Socket 和客户端地址recv(n)send()
TCP 客户端实现
TCP 客户端的基本步骤:
socket() → connect() → send()/recv() → close()
完整示例:
import socketdef tcp_client(host='127.0.0.1', port=8888): # 1. 创建 Socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: # 2. 连接到服务器 client.connect((host, port)) print(f"✅ 已连接到服务器 {host}:{port}") # 3. 发送数据 message = "Hello, Server!" client.send(message.encode('utf-8')) print(f"📤 发送: {message}") # 4. 接收响应 response = client.recv(1024) print(f"📥 收到响应: {response.decode('utf-8')}") except ConnectionRefusedError: print("❌ 连接被拒绝,请检查服务器是否运行") except Exception as e: print(f"❌ 错误: {e}") finally: # 5. 关闭 Socket client.close()if __name__ == "__main__": tcp_client()
使用上下文管理器(推荐写法)
Python 的 with 语句可以自动管理 Socket 资源:
import socket# 服务器端def tcp_server_context(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(('127.0.0.1', 8888)) server.listen(5) conn, addr = server.accept() with conn: print(f"连接来自: {addr}") data = conn.recv(1024) conn.sendall(data) # sendall 确保发送完整数据# 客户端def tcp_client_context(): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client: client.connect(('127.0.0.1', 8888)) client.sendall(b"Hello") data = client.recv(1024) print(f"收到: {data}")
🌐 UDP 编程:无连接的通信
UDP(User Datagram Protocol)是无连接协议,不需要建立连接即可直接发送数据。特点是快速但不保证可靠。
UDP 通信模式

UDP 的特点:
UDP 服务器实现
import socketdef udp_server(host='127.0.0.1', port=8888): # 1. 创建 UDP Socket server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 绑定地址 server.bind((host, port)) print(f"UDP 服务器启动,监听 {host}:{port}") try: while True: # 3. 接收数据和发送方地址(无需 accept) data, client_addr = server.recvfrom(1024) message = data.decode('utf-8') print(f"📨 收到来自 {client_addr} 的数据: {message}") # 4. 发送响应回给客户端 response = f"收到: {message}" server.sendto(response.encode('utf-8'), client_addr) except KeyboardInterrupt: print("\nUDP 服务器关闭") finally: server.close()if __name__ == "__main__": udp_server()
关键点:
UDP 客户端实现
import socketdef udp_client(host='127.0.0.1', port=8888): # 1. 创建 UDP Socket client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: # 2. 发送数据(无需先连接) message = "Hello, UDP Server!" client.sendto(message.encode('utf-8'), (host, port)) print(f"📤 发送: {message}") # 3. 接收响应 data, addr = client.recvfrom(1024) print(f"📥 收到响应: {data.decode('utf-8')}") except Exception as e: print(f"❌ 错误: {e}") finally: client.close()if __name__ == "__main__": udp_client()
📊 TCP vs UDP 对比

特性 | TCP | UDP |
|---|
连接方式 | 面向连接(需要三次握手) | 无连接(直接发送) |
可靠性 | 保证数据到达且有序 | 不保证可靠,可能丢包 |
传输速度 | 较慢(需要握手和确认) | 较快(无握手开销) |
数据流 | 字节流,无消息边界 | 数据报,有消息边界 |
资源消耗 | 较高(连接状态维护) | 较低(无状态) |
应用场景 | 需要可靠传输 | 需要实时性 |
适用场景
TCP 适用于:
UDP 适用于:
🛠️ Socket 常用方法

创建和基础操作
import socket# 创建 Socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 绑定地址sock.bind(('127.0.0.1', 8888))# 开始监听sock.listen(5)# 接受连接conn, addr = sock.accept()# 连接到服务器sock.connect(('127.0.0.1', 8888))# 关闭 Socketsock.close()
数据传输
# TCP 发送sock.send(b"data") # 返回实际发送的字节数sock.sendall(b"data") # 确保发送所有数据# TCP 接收data = sock.recv(1024) # 接收最多 1024 字节# UDP 发送sock.sendto(b"data", ('127.0.0.1', 8888))# UDP 接收data, addr = sock.recvfrom(1024)
Socket 选项
import socketsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 地址重用(避免 TIME_WAIT 导致的端口占用)sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)# 禁用 Nagle 算法(减少延迟)sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)# 设置发送缓冲区大小sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)# 设置接收缓冲区大小sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)# 获取选项值value = sock.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
📖 完整示例:Echo 服务
一个完整的 Echo 服务示例,演示 TCP 通信的完整流程。
服务器(echo_server.py)
import socketdef echo_server(host='127.0.0.1', port=8888): """ Echo 服务器:接收客户端消息并原样返回 """ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: # 设置选项 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定和监听 server.bind((host, port)) server.listen(5) print(f"🚀 Echo 服务器运行在 {host}:{port}") print("等待客户端连接...\n") try: while True: # 接受连接 conn, addr = server.accept() print(f"✅ 客户端连接: {addr[0]}:{addr[1]}") with conn: while True: # 接收数据 data = conn.recv(1024) if not data: print(f"❌ 客户端 {addr[0]}:{addr[1]} 断开连接\n") break # 解码并显示 message = data.decode('utf-8') print(f" 📨 收到: {message}") # 原样返回 conn.sendall(data) print(f" 📤 回显: {message}") except KeyboardInterrupt: print("\n👋 服务器关闭")if __name__ == "__main__": echo_server()
客户端(echo_client.py)
import socketimport timedef echo_client(host='127.0.0.1', port=8888): """ Echo 客户端:发送消息并接收回显 """ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client: try: # 连接到服务器 client.connect((host, port)) print(f"✅ 已连接到服务器 {host}:{port}\n") # 发送多条消息 messages = ["Hello", "Socket", "Programming", "Python"] for msg in messages: # 发送 client.sendall(msg.encode('utf-8')) print(f"📤 发送: {msg}") # 接收回显 response = client.recv(1024) print(f"📥 收到: {response.decode('utf-8')}\n") time.sleep(0.5) print("✅ 所有消息已发送") except ConnectionRefusedError: print("❌ 连接被拒绝,请先启动服务器") except Exception as e: print(f"❌ 错误: {e}")if __name__ == "__main__": echo_client()
运行方式:
# 终端1:启动服务器python echo_server.py# 终端2:启动客户端python echo_client.py
⚠️ 常见问题解答
Q1:为什么出现 "Address already in use" 错误?
答: 这是因为之前的连接处于 TIME_WAIT 状态,端口还没有释放。解决方法:
# 设置地址重用sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)sock.bind(('127.0.0.1', 8888))
或者等待几分钟后重试。
Q2:send() 和 sendall() 有什么区别?
答:
# send() - 可能只发送部分数据,返回实际发送的字节数sent = sock.send(b"Hello")print(sent) # 可能 < 5# sendall() - 确保发送所有数据,无返回值sock.sendall(b"Hello") # 一定发送完整 5 字节# sendall() 的原理:def sendall_logic(sock, data): total_sent = 0 while total_sent < len(data): sent = sock.send(data[total_sent:]) if sent == 0: raise RuntimeError("连接已断开") total_sent += sent
Q3:如何判断对方已断开连接?
答:
data = sock.recv(1024)if not data: print("对方已断开连接") sock.close()
Q4:TCP 的消息边界问题如何处理?
答: TCP 是字节流协议,无消息边界。需要自己定义协议来划分消息。一个常见方法是使用消息长度前缀:
import structdef send_message(sock, msg): """发送消息(带长度前缀)""" msg_bytes = msg.encode('utf-8') # 先发送 4 字节长度 sock.sendall(struct.pack('>I', len(msg_bytes))) # 再发送实际数据 sock.sendall(msg_bytes)def recv_message(sock): """接收消息(带长度前缀)""" # 先接收 4 字节长度 raw_len = sock.recv(4) if not raw_len: return None msg_len = struct.unpack('>I', raw_len)[0] # 再接收指定长度的数据 return sock.recv(msg_len).decode('utf-8')
💡 最佳实践
使用 with 语句管理资源
with socket.socket() as sock: # Socket 会自动关闭 pass
设置地址重用
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
正确处理编码
# 发送时编码sock.send(message.encode('utf-8'))# 接收时解码message = data.decode('utf-8')
检查连接状态
data = sock.recv(1024)if not data: print("连接已断开")
合理设置缓冲区大小
🎯 小结
- Socket 是网络编程的基础接口,分为 TCP 和 UDP 两类
- TCP 是面向连接的,需要三次握手建立连接,保证可靠传输
- UDP 是无连接的,直接发送数据包,快速但不保证可靠
- TCP 适合:需要可靠传输的应用(HTTP、FTP、Email)
- UDP 适合:需要实时性的应用(视频直播、游戏、DNS)
- 使用 with 语句:可以自动管理 Socket 资源
- TCP 消息边界:需要自己处理,可以使用长度前缀
🎉 模块学习路线
┌─────────────────────────────────────────────────────────┐│ Socket 编程基础模块(第 70-73 天) │├─────────────────────────────────────────────────────────┤│ ││ 第 70 天 ✓ Socket API 基础:创建 TCP 与 UDP 连接 ││ │ • Socket 概念与原理 ││ │ • TCP vs UDP 对比 ││ │ • 服务器与客户端实现 ││ │ ││ ▼ ││ 第 71 天 Python 3.14 中的异步 Socket 编程 ││ │ • asyncio 与 Socket 结合 ││ │ • 高并发服务器实现 ││ │ ││ ▼ ││ 第 72 天 Socket 错误处理与超时机制 ││ │ • 网络异常处理 ││ │ • 超时设置与重连 ││ │ ││ ▼ ││ 第 73 天 基于 Socket 的简单聊天应用实现 ││ • 综合实战项目 ││ • 多客户端聊天室 ││ │└─────────────────────────────────────────────────────────┘
📌 明日预告:Python 3.14 中的异步 Socket 编程
明天我们将进入 Socket 编程模块第二天!
- 主题:Python 3.14 中的异步 Socket 编程(asyncio 与 Socket)
- 核心问题:
- 如何将 Socket 与 asyncio 结合?
- 如何实现高并发 Socket 服务器?
asyncio.start_server() 怎么用?- 异步 Socket 与同步 Socket 有什么区别?
- 性能对比如何?
💡 提前思考:
- 同步 Socket 处理 1000 个客户端需要什么?
- 异步 Socket 为什么能处理更多并发?
await reader.read() 和 recv() 有什么区别?