← 返回Python进阶编程目录
← 返回学习笔记首页
专题: Python进阶编程系统学习
关键词: Python, socket, TCP, UDP, 网络编程, 服务器, 客户端, 非阻塞IO, selectors
一、课程概述
Socket(套接字)是网络编程的基石,它提供了一种进程间通信的机制,允许数据在不同主机之间或同一主机的不同进程之间进行交换。Python标准库中的 socket 模块封装了底层操作系统提供的套接字接口,使得开发者能够以简洁、Pythonic的方式构建网络应用程序。本专题将系统讲解Python socket编程的核心概念、API用法和最佳实践,涵盖TCP/UDP协议、阻塞与非阻塞IO、异常处理、高级IO多路复用乃至一个完整HTTP服务器的实战实现。
适用场景: Web服务器开发、即时通讯系统、物联网设备通信、文件传输工具、网络爬虫、远程监控、游戏服务器等几乎所有涉及网络数据传输的应用领域。
二、网络编程基础概念
2.1 TCP与UDP协议
传输层协议是socket编程的核心。TCP(传输控制协议)是面向连接 的协议,提供可靠的数据传输服务,确保数据包按序到达且不丢失,适用于对数据完整性要求高的场景,如文件传输、网页浏览。UDP(用户数据报协议)是无连接 的协议,提供尽最大努力交付的服务,不保证数据包到达顺序和完整性,但延迟更低、开销更小,适用于实时通信场景,如视频直播、在线游戏、DNS查询。
特性 TCP UDP
连接方式 面向连接(三次握手) 无连接
可靠性 可靠传输、确认重传 不可靠、尽最大努力
数据顺序 保证有序到达 不保证顺序
传输速度 相对较慢(头部开销大) 快速(头部开销小)
数据边界 流式传输,无边界 保留消息边界
典型应用 HTTP、FTP、SMTP、SSH DNS、DHCP、VoIP、视频流
2.2 IP地址与端口号
IP地址标识网络中的主机(IPv4为32位地址如 127.0.0.1,IPv6为128位地址),端口号标识主机上的特定进程(16位无符号整数,范围 0~65535)。知名端口(0~1023)由系统服务占用,如HTTP的80端口、HTTPS的443端口、SSH的22端口。注册端口(1024~49151)可供用户应用程序使用,动态/私有端口(49152~65535)通常由客户端临时分配。
2.3 地址族与套接字类型
Python的 socket 模块支持多种地址族和套接字类型的组合,最常见的组合是 AF_INET(IPv4地址族)与 SOCK_STREAM(流式套接字,对应TCP)或 SOCK_DGRAM(数据报套接字,对应UDP)。此外,AF_INET6 用于IPv6、AF_UNIX 用于同一主机上的Unix域套接字通信。
# 常见地址族与套接字类型常量
import socket
# 地址族
print(socket.AF_INET) # IPv4 地址族 (值为2)
print(socket.AF_INET6) # IPv6 地址族 (值为23)
print(socket.AF_UNIX) # Unix域套接字 (值为1)
# 套接字类型
print(socket.SOCK_STREAM) # TCP 流式套接字 (值为1)
print(socket.SOCK_DGRAM) # UDP 数据报套接字 (值为2)
print(socket.SOCK_RAW) # 原始套接字 (值为3)
三、socket模块核心API详解
3.1 创建套接字:socket()
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0) 是最基本的工厂函数,用于创建新的套接字对象。family参数指定地址族,type参数指定套接字类型,proto通常默认为0由系统自动选择。创建后的套接字对象提供了丰富的实例方法来执行绑定、监听、连接、收发数据等操作。
# 创建TCP套接字
tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 创建UDP套接字
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 使用with语句管理套接字生命周期(Python 3.6+)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(('example.com' , 80 ))
s.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n' )
data = s.recv(4096 )
print(data.decode())
3.2 绑定地址:bind()
bind(address) 将套接字绑定到一个特定的IP地址和端口上。参数是一个二元组 (host, port)。host可以为空字符串表示绑定到所有可用接口,port指定监听端口。绑定操作通常在服务器端使用,在客户端程序中通常不需要显式调用bind,系统会自动分配临时端口。
# 绑定到本地所有网络接口的8080端口
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind(('0.0.0.0' , 8080 ))
# 绑定到回环地址(仅本机可访问)
server_sock.bind(('127.0.0.1' , 8080 ))
# 绑定到特定网卡IP
server_sock.bind(('192.168.1.100' , 8080 ))
3.3 监听连接:listen()
listen([backlog]) 使服务器套接字进入被动监听状态,准备接受客户端连接请求。backlog参数指定操作系统允许的最大挂起连接数(等待accept的连接队列长度),超过此数量的连接将被拒绝。backlog的具体上限取决于操作系统,通常设置为 5~128 之间。
3.4 接受连接:accept()
accept() 阻塞等待并接受一个客户端连接请求。它返回一个二元组 (client_socket, client_address),其中 client_socket 是新建的套接字对象用于与该客户端进行数据通信,client_address 是客户端的地址信息(IP和端口)。accept 是TCP服务器实现并发连接处理的核心入口。
# TCP服务器核心循环:监听 → 接受 → 处理 → 关闭
server_sock.listen(5 )
print('服务器已启动,等待连接...' )
while True :
client_sock, addr = server_sock.accept()
print(f'收到来自 {addr} 的连接' )
try :
data = client_sock.recv(1024 )
if data:
client_sock.sendall(b'HTTP/1.1 200 OK\r\n\r\nHello, World!' )
finally :
client_sock.close()
3.5 连接服务器:connect() 与 connect_ex()
connect(address) 客户端向指定服务器地址发起TCP连接请求,连接成功或失败时返回(失败抛出异常)。connect_ex(address) 在连接失败时返回错误码而非抛出异常,类似于C语言中connect系统调用的返回方式,适合需要精细控制错误处理的场景。
3.6 发送数据:send()、sendall() 与 sendto()
send(bytes[, flags]) 发送TCP数据,返回实际发送的字节数,可能少于要发送的总字节数,需要循环发送直到全部发完。sendall(bytes[, flags]) 在内部循环调用 send 直到所有数据发送完毕或发生错误,开发者无需关心部分发送问题,推荐使用。sendto(bytes, address) 用于UDP套接字,在无连接的UDP通信中指定目标地址发送数据报。
# send 与 sendall 的对比
data = b'X' * 100000 # 10万字节
# send 可能需要多次调用才能发完
total_sent = 0
while total_sent < len (data):
sent = sock.send(data[total_sent:])
if sent == 0 :
raise RuntimeError('连接中断' )
total_sent += sent
# sendall 内部自动循环发送,简洁安全
sock.sendall(data) # 一行搞定全部发送
3.7 接收数据:recv() 与 recvfrom()
recv(bufsize[, flags]) 接收TCP数据,返回接收到的字节数据。bufsize 指定最大接收字节数,实际返回的数据长度可能小于此值。当对端关闭连接时,recv 返回空字节串 b'',这是检测连接关闭的标准方式。recvfrom(bufsize[, flags]) 用于UDP套接字,返回一个二元组 (data, address),其中 address 是发送方的地址信息。
# TCP接收循环:持续读取直到连接关闭
def recv_all (sock, bufsize=4096 ):
"""接收所有数据直到连接关闭,返回完整字节数据"""
chunks = []
while True :
chunk = sock.recv(bufsize)
if not chunk: # 连接关闭
break
chunks.append(chunk)
return b'' .join(chunks)
3.8 关闭套接字:close() 与 shutdown()
close() 释放套接字资源,在引用计数归零时真正关闭底层文件描述符。shutdown(how) 提供更精细的关闭控制:SHUT_RD 禁止后续读操作,SHUT_WR 禁止后续写操作(发送FIN包通知对端),SHUT_RDWR 同时禁止读写。shutdown 可以确保对端立即收到连接关闭通知,而 close 仅在引用计数归零时触发。
API使用口诀: 服务器端:socket → bind → listen → accept → recv/send → close;客户端:socket → connect → send/recv → close。
四、TCP服务器与客户端完整实现
4.1 单线程TCP回显服务器
以下是一个完整的TCP服务器实现,它接收客户端发送的数据并将原数据返回给客户端(回显服务)。此版本为单线程顺序处理,一次只能服务一个客户端。
# tcp_echo_server.py - TCP回显服务器
import socket
HOST = '127.0.0.1'
PORT = 8888
BACKLOG = 5
BUFFER_SIZE = 1024
def start_server ():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_sock:
# 允许地址重用,避免重启时"Address already in use"错误
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
server_sock.bind((HOST, PORT))
server_sock.listen(BACKLOG)
print(f'[服务器] 已启动,监听 {HOST}:{PORT}' )
while True :
print('[服务器] 等待客户端连接...' )
conn, addr = server_sock.accept()
print(f'[服务器] 客户端已连接:{addr}' )
with conn:
while True :
data = conn.recv(BUFFER_SIZE)
if not data:
print(f'[服务器] 客户端 {addr} 已断开' )
break
print(f'[服务器] 收到:{data.decode()}' )
conn.sendall(b'[ECHO] ' + data)
print(f'[服务器] 已回显:{data.decode()}' )
if __name__ == '__main__' :
start_server()
4.2 TCP回显客户端
# tcp_echo_client.py - TCP回显客户端
import socket
HOST = '127.0.0.1'
PORT = 8888
def start_client ():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_sock:
client_sock.connect((HOST, PORT))
print(f'[客户端] 已连接到服务器 {HOST}:{PORT}' )
while True :
msg = input('[客户端] 请输入消息(输入quit退出):' )
if msg.lower() == 'quit' :
break
client_sock.sendall(msg.encode())
response = client_sock.recv(1024 )
print(f'[客户端] 服务器回复:{response.decode()}' )
if __name__ == '__main__' :
start_client()
测试方法: 先在一个终端中运行服务器程序,然后在另一个终端中运行客户端程序。客户端发送的消息会被服务器添加 [ECHO] 前缀后返回。输入 quit 即可退出客户端。
4.3 多线程TCP服务器
上述单线程版本只能同时为一个客户端服务。在生产环境中,服务器需要同时处理多个客户端连接。使用 threading 模块为每个客户端创建一个独立线程是最简单的并发方案。
# tcp_multithread_server.py - 多线程TCP服务器
import socket
import threading
HOST = '0.0.0.0'
PORT = 8888
def handle_client (conn, addr):
"""处理单个客户端连接的函数,在独立线程中运行"""
print(f'[线程] 新连接来自 {addr}' )
with conn:
while True :
data = conn.recv(1024 )
if not data:
break
conn.sendall(b'[SERVER] ' + data)
print(f'[线程] 连接 {addr} 已关闭' )
def start_server ():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
sock.bind((HOST, PORT))
sock.listen()
print(f'[服务器] 多线程服务器启动于 {HOST}:{PORT}' )
while True :
conn, addr = sock.accept()
# 每个连接启动一个独立线程处理
t = threading.Thread(target=handle_client, args=(conn, addr))
t.start()
print(f'[服务器] 活动连接数:{threading.active_count() - 1}' )
if __name__ == '__main__' :
start_server()
五、UDP服务器与客户端完整实现
5.1 UDP服务器
UDP是无连接协议,服务器无需调用 listen 和 accept,直接 recvfrom 等待数据报到达,并通过 sendto 回复。由于无连接特性,UDP天然支持"多客户端并发访问",因为每个 recvfrom/sendto 都是独立的。
# udp_echo_server.py - UDP回显服务器
import socket
HOST = '127.0.0.1'
PORT = 9999
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
sock.bind((HOST, PORT))
print(f'[UDP服务器] 已启动,监听 {HOST}:{PORT}' )
while True :
data, addr = sock.recvfrom(1024 )
print(f'[UDP服务器] 收到来自 {addr} 的数据:{data.decode()}' )
sock.sendto(b'[UDP_ECHO] ' + data, addr)
print(f'[UDP服务器] 已回复 {addr}' )
5.2 UDP客户端
# udp_echo_client.py - UDP回显客户端
import socket
HOST = '127.0.0.1'
PORT = 9999
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
msg = 'Hello, UDP Server!'
sock.sendto(msg.encode(), (HOST, PORT))
print(f'[UDP客户端] 已发送:{msg}' )
# UDP recvfrom 可能阻塞,建议设置超时防止无限等待
sock.settimeout(5.0 )
try :
data, addr = sock.recvfrom(1024 )
print(f'[UDP客户端] 收到回复:{data.decode()}' )
except socket.timeout:
print('[UDP客户端] 超时:服务器无响应' )
UDP注意事项: UDP数据报大小受限于网络MTU(通常为1500字节),建议单次发送不超过 65507 字节(IPv4 UDP理论最大值)。UDP不保证数据送达,应用层需要自行实现确认重传机制(如需可靠传输)。
六、阻塞与非阻塞IO
6.1 默认阻塞模式
Python socket默认工作于阻塞模式。当调用 recv() 时,线程将挂起直到有数据到达;调用 accept() 时,线程将等待直到有新连接;调用 connect() 时,线程将阻塞直到连接建立或失败。阻塞模式编程模型简单直观,但在需要同时处理多个连接时必须使用多线程或多进程方案。
6.2 setblocking() 设置非阻塞
setblocking(flag) 控制套接字是否为阻塞模式。传入 False 将套接字切换为非阻塞模式,此时所有IO操作立即返回。如果没有数据可读,recv() 将抛出 BlockingIOError;如果没有连接待处理,accept() 同样抛出 BlockingIOError。
# 非阻塞模式示例
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False ) # 切换为非阻塞模式
try :
data = sock.recv(1024 )
except BlockingIOError:
# 没有数据可读,在此处执行其他任务
print('当前没有数据可读,稍后重试' )
6.3 settimeout() 设置超时
settimeout(value) 提供介于阻塞和非阻塞之间的折中方案。设置超时后,socket操作最多等待指定秒数,超时后抛出 socket.timeout 异常。超时值为 None 表示阻塞模式,0 表示非阻塞模式。超时模式在某些场景下比纯非阻塞更易用,因为它不需要频繁轮询。
# 超时模式示例
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10.0 ) # 设置10秒超时
sock.connect(('example.com' , 80 )) # 最多等待10秒
try :
data = sock.recv(1024 )
except socket.timeout:
print('接收数据超时' )
模式 设置方式 行为 适用场景
阻塞 (默认) 默认 IO操作无限等待 多线程服务器、简单客户端
非阻塞 setblocking(False) IO操作立即返回,无数据时抛异常 事件驱动框架、select/poll/epoll
超时 settimeout(n) 操作等待n秒后抛 timeuot 异常 需要限时等待的客户端
七、Socket异常处理
7.1 socket异常层级
Python 的 socket 模块定义了一套完整的异常层次结构。正确地捕获和处理这些异常是编写健壮网络应用的关键。所有 socket 异常都继承自 OSError,但为了方便精确定位错误,最好使用更具体的异常类。
# 常见的socket异常及处理
import socket
import errno
def safe_connect (host, port, timeout=5 ):
"""带异常处理的健壮连接函数"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
try :
sock.connect((host, port))
print(f'成功连接到 {host}:{port}' )
return sock
except socket.gaierror as e:
print(f'DNS解析失败 {host}: {e}' )
except socket.timeout as e:
print(f'连接超时 {host}:{port}: {e}' )
except ConnectionRefusedError as e:
print(f'连接被拒绝 {host}:{port}: {e}' )
except OSError as e:
print(f'网络错误 {host}:{port}: (errno={e.errno}) {e.strerror}' )
return None
常见错误与排查: ConnectionRefusedError 表示目标端口未开放或防火墙阻止;gaierror 表示域名无法解析;timeout 表示网络不可达或服务器过载;BrokenPipeError 表示对端已关闭连接;ConnectionResetError 表示对端异常断开(如进程崩溃)。
7.2 优雅关闭检测
TCP连接关闭检测在网络编程中尤为重要。recv 返回空字节串是优雅关闭的信号。但在实际场景中,网络异常可能导致连接半开状态而 recv 不会返回空字节串。建议结合心跳机制(heartbeat)定期探测连接健康状态,确保及时释放无效连接资源。
# 带心跳检测的接收循环
import select
def recv_with_heartbeat (sock, heartbeat_interval=30 ):
"""使用selectors实现带心跳检测的数据接收"""
sock.settimeout(heartbeat_interval)
while True :
try :
data = sock.recv(4096 )
if not data:
print('连接已优雅关闭' )
return None
yield data
except socket.timeout:
# 长时间无数据,发送心跳探测
try :
sock.sendall(b'\x00' ) # 心跳包
except (BrokenPipeError, ConnectionResetError):
print('连接已断开' )
return None
八、Socket选项配置
8.1 SO_REUSEADDR -- 地址重用
SO_REUSEADDR 是最常用的socket选项之一,它允许服务器在 TIME_WAIT 状态下重用地址和端口。不设置此选项时,如果服务器异常退出后立即重启,通常会遇到 "Address already in use" 错误,需等待约 2~4 分钟(TIME_WAIT 超时)才能重新绑定。设置此选项可以立即重启服务器,对开发和调试极为有用。
# 设置 SO_REUSEADDR
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
sock.bind(('0.0.0.0' , 8080 ))
sock.listen()
8.2 TCP_NODELAY -- 禁用Nagle算法
Nagle算法通过延迟小数据包发送来减少网络拥塞,但在实时通信场景(如在线游戏、即时通讯)中会导致明显的延迟。TCP_NODELAY 禁用Nagle算法,使小数据包立即发送。注意此选项只能用于TCP套接字。
# 为低延迟应用禁用Nagle算法
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1 )
8.3 SO_LINGER -- 控制关闭行为
SO_LINGER 控制 close() 的行为:是否等待未发送数据发送完成后再关闭连接,以及等待的时间。设置 l_onoff=1, l_linger=0 时 close 立即关闭并发送 RST 包而非正常的 FIN 四次挥手,可用于快速释放连接但可能导致数据丢失。
# 设置 SO_LINGER:关闭后等待5秒完成数据发送
sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
struct .pack('ii' , 1 , 5 ))
# 强制立即关闭(发送RST)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
struct .pack('ii' , 1 , 0 ))
推荐配置: TCP服务器建议始终设置 SO_REUSEADDR 以确保快速重启;低延迟应用设置 TCP_NODELAY;不需要设置 SO_LINGER 时保持默认即可。
九、selectors模块——IO多路复用
9.1 IO多路复用概述
IO多路复用是一种单线程同时监视多个文件描述符(包括socket)的机制。操作系统提供 select、poll、epoll(Linux)、kqueue(BSD/macOS)等系统调用。Python 的 selectors 模块(Python 3.4+)提供了统一的抽象层,自动选择当前平台最高效的实现(如 Linux 上优先使用 epoll)。
9.2 使用selectors实现并发服务器
相比多线程方案,使用 selectors 的事件驱动模型在大量并发连接(数千到数万)时性能更好,因为线程上下文切换和内存开销远小于线程方案。
# selectors_echo_server.py - 基于selectors的事件驱动TCP服务器
import selectors
import socket
sel = selectors.DefaultSelector() # 自动选择最佳实现
def accept (sock, mask):
"""处理新连接"""
conn, addr = sock.accept()
conn.setblocking(False ) # selectors要求非阻塞
sel.register(conn, selectors.EVENT_READ, read)
print(f'新连接:{addr}' )
def read (conn, mask):
"""处理已连接socket的可读事件"""
data = conn.recv(1024 )
if data:
conn.sendall(b'[SELECTOR] ' + data)
else :
# 连接关闭,取消注册并关闭socket
print(f'关闭连接:{conn.getpeername()}' )
sel.unregister(conn)
conn.close()
# 创建服务器socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
server.bind(('127.0.0.1' , 8888 ))
server.listen(100 )
server.setblocking(False )
sel.register(server, selectors.EVENT_READ, accept)
print('selectors事件驱动服务器已启动,监听 127.0.0.1:8888' )
try :
while True :
events = sel.select() # 阻塞等待事件发生
for key, mask in events:
callback = key.data # 注册时传入的回调函数
callback(key.fileobj, mask)
except KeyboardInterrupt:
print('服务器关闭' )
finally :
sel.close()
selectors vs 多线程: 当并发连接数较少(<1000)且每个连接处理逻辑较重(如涉及大量计算)时,多线程方案更简单直接;当并发连接数高(>5000)且每个连接处理逻辑轻量时,selectors事件驱动模型的内存和性能优势更加明显。现代Python异步框架(asyncio)的底层也广泛使用了selectors或类似的IO多路复用机制。
9.3 selectors API对比
实现类 底层系统调用 平台 并发上限 特点
SelectSelector select() 全平台 FD_SETSIZE 限制(通常1024) 最兼容,性能最差
PollSelector poll() Unix 无硬限制 比select好,但仍是O(n)扫描
EpollSelector epoll() Linux 2.5.44+ 取决于系统内存 O(1)事件通知,性能最好
KqueueSelector kqueue() BSD/macOS 取决于系统 类epoll,FreeBSD/macOS首选
DefaultSelector 自动选择 跨平台 最优选择 生产环境推荐
十、简单HTTP服务器实现
10.1 基于socket的迷你HTTP服务器
理解了socket编程后,我们可以从零实现一个简单的HTTP服务器。HTTP本质上就是基于TCP的文本协议:客户端发送HTTP请求(包含方法、路径、头部),服务器解析请求后回复HTTP响应(状态行、头部、正文)。
# simple_http_server.py - 基于socket的迷你HTTP服务器
import socket
import datetime
HTTP_RESPONSE = '''\
HTTP/1.1 200 OK\r
Content-Type: text/html; charset=utf-8\r
Connection: close\r
\r
<!DOCTYPE html>
<html>
<head><title>Python Socket HTTP Server</title></head>
<body>
<h1>Hello from Python Socket!</h1>
<p>Server Time: {time}</p>
<p>This page was served using raw Python sockets.</p>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</body>
</html>'''
def handle_request (conn, addr):
"""解析HTTP请求并返回响应"""
data = conn.recv(4096 )
if not data:
return
# 解析请求行:GET /path HTTP/1.1
request_text = data.decode('utf-8' , errors='replace' )
request_line = request_text.split('\r\n' )[0 ]
method, path, version = request_line.split(' ' )
print(f'[{datetime.datetime.now()}] {addr[0]} - {method} {path}' )
# 根据不同路径返回不同内容
if path == '/' :
body = '<h1>Welcome to Home</h1><p>路径: /</p>'
elif path == '/about' :
body = '<h1>About</h1><p>这是一个使用Python socket从零实现的HTTP服务器。</p>'
else :
body = '<h1>404 Not Found</h1><p>请访问 / 或 /about</p>'
response = HTTP_RESPONSE.format(time=datetime.datetime.now())
conn.sendall(response.encode('utf-8' ))
conn.close()
# 启动HTTP服务器
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
server.bind(('127.0.0.1' , 8080 ))
server.listen(10 )
print('HTTP服务器已启动,访问 http://127.0.0.1:8080' )
try :
while True :
conn, addr = server.accept()
handle_request(conn, addr)
except KeyboardInterrupt:
print('服务器关闭' )
finally :
server.close()
运行与测试
将此代码保存为文件并在终端运行。打开浏览器访问 http://127.0.0.1:8080,即可看到服务器返回的HTML页面。服务器的控制台会打印每个请求的日志。这个简单的例子展示了HTTP协议的本质——它就是基于TCP的文本协议,任何语言只要支持socket都可以实现HTTP服务器。
十一、核心要点总结
TCP vs UDP: TCP面向连接、可靠、有序,适用于文件传输和Web服务;UDP无连接、速度快、保留消息边界,适用于实时音视频和DNS查询。选择哪个协议取决于应用对可靠性和延迟的要求。
socket编程五大步骤: 创建socket → bind绑定地址 → listen监听 / connect连接 → recv/send数据收发 → close关闭。服务器和客户端的流程在listen/connect处产生分支。
阻塞与非阻塞: 阻塞模式编程简单但并发能力有限;非阻塞模式配合selectors可高效处理数万并发;超时模式提供了中间方案,适合需要限时等待的客户端场景。
异常处理: ConnectionRefusedError(连接被拒绝)、socket.timeout(超时)、socket.gaierror(DNS解析失败)、BrokenPipeError(对端关闭)是最高频的四种异常,务必覆盖处理。
SO_REUSEADDR: 服务器socket必须设置的选项,允许在TIME_WAIT状态下重用地址,是开发调试的基本配置。
selectors模块: DefaultSelector自动选择平台最优实现(Linux epoll / macOS kqueue / 备选poll/select),是构建高性能事件驱动网络应用的基础。
HTTP理解: HTTP协议本质上是基于TCP的简单文本协议,理解socket后即可从零实现HTTP服务器,这对深入理解Web技术栈至关重要。
sendall vs send: 永远优先使用sendall而非send,前者在内部处理了部分发送问题,代码更安全简洁。
十二、进一步思考与实践
进阶练习
文件传输工具: 基于TCP实现一个支持大文件断点续传的文件服务器,要求使用固定大小的缓冲区循环读写。
HTTP框架实现: 在selectors服务器基础上扩展,添加路由分发、中间件支持等特性,理解Flask/Django底层的工作机制。
WebSocket握手: 研究WebSocket协议的HTTP Upgrade握手过程,实现一个简单的WebSocket聊天服务器。
代理服务器: 实现HTTP正向代理和反向代理,理解代理转发的核心逻辑。
并发模型对比: 对同一echo服务分别实现多线程、selectors、asyncio三个版本,在相同条件下压测对比QPS和内存占用。
学习建议: socket网络编程是通向高级Python开发的必经之路。深入理解socket API和底层TCP状态转换,对后续学习asyncio、Twisted、Tornado等异步框架大有裨益。建议在虚拟机或容器中多做实验,使用Wireshark抓包分析TCP的三次握手和四次挥手过程,将理论知识与实践观察结合起来。
扩展阅读: Python官方文档 socket 模块、selectors 模块;《Unix网络编程》卷一(Stevens);《Python网络编程》(Brandon Rhodes);Real Python网站上关于socket编程的系列教程。