跳到主要内容

Socket 编程

问题

Python 中如何使用 socket 进行网络编程?TCP 和 UDP 的区别是什么?

答案

TCP 服务器/客户端

import socket

# TCP 服务器
def tcp_server(host: str = "127.0.0.1", port: int = 8888):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))
s.listen(5)
print(f"Listening on {host}:{port}")

while True:
conn, addr = s.accept()
with conn:
print(f"Connected: {addr}")
while True:
data = conn.recv(4096)
if not data:
break
conn.sendall(data.upper()) # Echo 回大写

异步 Socket(asyncio)

import asyncio

async def handle_client(reader: asyncio.StreamReader, writer: asyncio.StreamWriter):
addr = writer.get_extra_info("peername")
print(f"Connected: {addr}")

while True:
data = await reader.read(4096)
if not data:
break
writer.write(data.upper())
await writer.drain()

writer.close()
await writer.wait_closed()

async def main():
server = await asyncio.start_server(handle_client, "127.0.0.1", 8888)
async with server:
await server.serve_forever()

asyncio.run(main())

TCP 粘包处理

TCP 是字节流协议,没有消息边界。常用方案:

import struct

# 方案:长度前缀(4 字节表示消息长度)
def send_message(sock: socket.socket, data: bytes):
length = struct.pack("!I", len(data)) # 大端 4 字节无符号整数
sock.sendall(length + data)

def recv_message(sock: socket.socket) -> bytes:
# 先读 4 字节长度
raw_length = recv_exact(sock, 4)
length = struct.unpack("!I", raw_length)[0]
# 再读指定长度的数据
return recv_exact(sock, length)

def recv_exact(sock: socket.socket, n: int) -> bytes:
"""确保接收到恰好 n 字节"""
data = bytearray()
while len(data) < n:
chunk = sock.recv(n - len(data))
if not chunk:
raise ConnectionError("Connection closed")
data.extend(chunk)
return bytes(data)

常见面试问题

Q1: TCP 和 UDP 在 Python 中怎么区分?

答案

# TCP:SOCK_STREAM,面向连接,可靠
tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# UDP:SOCK_DGRAM,无连接,不可靠
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.sendto(b"hello", ("127.0.0.1", 9999))
data, addr = udp_sock.recvfrom(4096)

Q2: 为什么需要 SO_REUSEADDR

答案

服务器关闭后,端口会处于 TIME_WAIT 状态(默认 2MSL ≈ 60 秒)。设置 SO_REUSEADDR 允许立即重新绑定该端口,开发环境中几乎必设。

Q3: recvrecvfrom 的区别?

答案

  • recv(bufsize):TCP 使用,从已连接的 socket 接收数据
  • recvfrom(bufsize):UDP 使用,返回 (data, address) 元组,因为 UDP 无连接

相关链接