💡 一、 核心概念:网络编程到底在做什么?
很多人觉得网络编程难,是因为被 IP、端口、Socket、TCP、UDP、阻塞、非阻塞等名词吓到了。其实核心逻辑只有一句话:
让两个进程按照约定,在网络中可靠或高效地交换字节。
这里包含三个关键词:
🗺️ 二、 建立网络编程的“地图”
在写代码前,我们需要理解几个基础模型:
1. 客户端与服务端模型
- 服务端:绑定 IP 和端口 → 监听 → 接收连接 → 处理业务 → 返回结果。
- 客户端:知道服务端地址 → 发起连接 → 发送请求 → 接收响应。
2. TCP 与 UDP 的抉择
表格
下载为表格
导出为图片
| 连接性 | 面向连接(需三次握手) | 无连接 |
| 可靠性 | 可靠、有序、不丢失 | 不可靠、可能丢包、乱序 |
| 数据形式 | 字节流(无边界) | 数据报(有边界) |
| 典型场景 | HTTP、文件传输、聊天 | 视频直播、DNS、语音通话 |
3. Socket 是什么?
Socket 是操作系统提供的网络通信接口。在 Python 中,socket 对象负责建立连接、收发数据和关闭连接。
python
编辑
1import socket
2
3# 创建 IPv4 TCP Socket
4sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
🛠️ 三、 实战:写出第一个 TCP 服务端
这是最经典的“回显服务”(Echo Server):客户端发什么,服务端回什么。
1. 服务端代码 (tcp_server.py)
python
编辑
1import socket
2
3HOST = "127.0.0.1"
4PORT = 5000
5BUFFER_SIZE = 4096
6
7def main():
8 # 使用上下文管理器,自动关闭资源
9 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
10 # 允许端口重用,防止重启报错 Address already in use
11 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
12
13 server.bind((HOST, PORT))
14 server.listen()
15 print(f"服务端已启动:{HOST}:{PORT}")
16
17 # 等待客户端连接
18 conn, address = server.accept()
19 with conn:
20 print(f"客户端已连接:{address}")
21 while True:
22 data = conn.recv(BUFFER_SIZE)
23 if not data: # 客户端断开
24 break
25 print(f"收到:{data!r}")
26 conn.sendall(data) # 原样发回
27
28if __name__ == "__main__":
29 main()
2. 客户端代码 (tcp_client.py)
python
编辑
1import socket
2
3def main():
4 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
5 client.connect(("127.0.0.1", 5000))
6 message = "你好,Python 网络编程!"
7 client.sendall(message.encode("utf-8"))
8
9 response = client.recv(4096)
10 print("服务端返回:", response.decode("utf-8"))
11
12if __name__ == "__main__":
13 main()
关键点解析:
- with socket.socket(…):推荐使用上下文管理器,确保异常时也能自动关闭 Socket。
- send() vs sendall():send 可能只发送了部分数据,sendall 会确保数据全部写入缓冲区,业务代码优先用 sendall。
⚠️ 四、 避坑指南:TCP 的“粘包”与消息边界
TCP 是没有消息边界的字节流。
客户端 send 两次 "Hello" 和 "World",服务端 recv 可能会一次性收到 "HelloWorld",也可能分三次收到 "Hel", "loW", "orld"。
解决方案:定义应用层协议
最常用的是 长度前缀协议:先发 4 个字节表示长度,再发具体内容。
通用协议工具 (protocol.py)
python
编辑
1import socket
2import struct
3
4HEADER_SIZE = 4
5
6def recv_exact(sock, size):
7 """确保读取指定长度的字节"""
8 chunks = []
9 remaining = size
10 while remaining > 0:
11 chunk = sock.recv(remaining)
12 if not chunk:
13 raise ConnectionError("连接关闭")
14 chunks.append(chunk)
15 remaining -= len(chunk)
16 return b"".join(chunks)
17
18def send_message(sock, payload):
19 """发送:4字节长度 + 内容"""
20 header = struct.pack("!I", len(payload)) # !I 表示网络字节序无符号整数
21 sock.sendall(header + payload)
22
23def recv_message(sock):
24 """接收:先读长度,再读内容"""
25 header = recv_exact(sock, HEADER_SIZE)
26 (msg_len,) = struct.unpack("!I", header)
27 return recv_exact(sock, msg_len)
🚀 五、 进阶:并发模型的选择
单线程服务端一次只能服务一个客户端。如何解决高并发?
1. 多线程模型 (Threading)
适合连接数几百到几千,逻辑简单的场景。
python
编辑
1from concurrent.futures import ThreadPoolExecutor
2
3# 在线程池中处理连接
4with ThreadPoolExecutor(max_workers=20) as pool:
5 while True:
6 conn, addr = server.accept()
7 pool.submit(handle_client, conn, addr) # 提交任务
2. I/O 多路复用 (selectors)
这是高性能网络库的底层原理。一个线程管理多个连接,操作系统告诉程序“哪个 Socket 可读/可写”。
3. 异步 I/O (asyncio)
Python 官方推荐的现代高并发方案。基于协程,单线程即可处理数万连接。
Asyncio 服务端示例:
python
编辑
1import asyncio
2
3async def handle_client(reader, writer):
4 peer = writer.get_extra_info("peername")
5 print(f"连接:{peer}")
6 try:
7 while True:
8 # 读取一行数据(自动处理边界)
9 data = await reader.readline()
10 if not data: break
11
12 # 业务逻辑
13 writer.write(b"echo: " + data)
14 await writer.drain() # 重要:背压控制,防止内存溢出
15 except Exception as e:
16 print(e)
17 finally:
18 writer.close()
19 await writer.wait_closed()
20
21async def main():
22 server = await asyncio.start_server(handle_client, '127.0.0.1', 5005)
23 async with server:
24 await server.serve_forever()
25
26if __name__ == "__main__":
27 asyncio.run(main())
注意: await writer.drain() 非常重要,它实现了背压(Backpressure),当发送缓冲区满时暂停协程,防止内存暴涨。
🏆 六、 终极实战:异步多人聊天室
结合上述知识,我们可以实现一个支持多人的聊天室。
核心逻辑 (chat_server.py):
python
编辑
1import asyncio
2
3clients = {} # 存储 {writer: username}
4
5async def broadcast(message, exclude=None):
6 """广播消息给所有客户端"""
7 dead_clients = []
8 for writer in clients:
9 if writer is exclude: continue
10 try:
11 writer.write(message.encode('utf-8') + b'\\n')
12 await writer.drain()
13 except ConnectionError:
14 dead_clients.append(writer)
15
16 # 清理失效连接
17 for writer in dead_clients:
18 clients.pop(writer, None)
19 writer.close()
20
21async def handle_client(reader, writer):
22 # 1. 获取用户名
23 await send_line(writer, "请输入用户名:")
24 raw_name = await reader.readline()
25 username = raw_name.decode().strip()
26 clients[writer] = username
27
28 await broadcast(f"[系统] {username} 加入聊天室")
29
30 # 2. 循环接收消息
31 try:
32 while True:
33 raw = await reader.readline()
34 if not raw: break
35 msg = raw.decode().strip()
36 if msg == '/quit': break
37 await broadcast(f"[{username}] {msg}")
38 finally:
39 # 3. 清理资源
40 if writer in clients:
41 clients.pop(writer)
42 await broadcast(f"[系统] {username} 离开")
43 writer.close()
44 await writer.wait_closed()
45
46async def send_line(writer, msg):
47 writer.write(msg.encode('utf-8') + b'\\n')
48 await writer.drain()
49
50# 启动服务
51asyncio.run(asyncio.start_server(handle_client, '127.0.0.1', 6000).serve_forever())
🛡️ 七、 生产环境注意事项
从“能跑”到“上线”,你还需要考虑:
- 限制消息最大长度,防止内存溢出攻击。
- 不要直接 pickle.loads 不可信数据。
- 生产环境务必使用 TLS/SSL 加密。

📌 总结
Python 网络编程的学习路径:
网硕互联帮助中心





评论前必须登录!
注册