构建持久化HTTP Web服务器:复用连接处理多请求问题
问题描述
我写的Web服务器代码里,内部while循环包含msg = client_socket.recv(1024).decode(),但如果不把client_socket, addr = server_socket.accept()放进这个内部循环,就没法处理多个客户端请求。我搞不懂怎么复用同一个连接来处理所有请求,当前代码如下:
from socket import * import sys def run_server(port): # 创建服务器套接字 server_socket = socket(AF_INET, SOCK_STREAM) # 将套接字绑定到指定地址 server_socket.bind(('localhost', port)) # 参数1表示最大排队连接数 server_socket.listen(1) print(f"Serving on port {port}") while 1: # 接受客户端的传入连接 client_socket, addr = server_socket.accept() print(f"Connection established from address {addr}") while 1: # 接收客户端数据 msg = client_socket.recv(1024).decode() # 提取文件名 file_name = msg.split('\n')[0].split()[1].replace('/', '') # 处理客户端请求 try: with open(file_name, 'rb') as file: status_line = "HTTP/1.1 200 OK\r\n" file_extension = file_name.split('.')[-1].lower() if file_extension == 'html': content_type_header = "Content-Type: text/html\r\n\r\n" elif file_extension == 'png': content_type_header = "Content-Type: image/png\r\n\r\n" content = file.read() # 组合状态行、头部和内容 http_response = (status_line + "Connection: keep-alive\r\n" + content_type_header).encode() + content except OSError as _: # 文件未找到,生成404响应 status_line = "HTTP/1.1 404 Not Found\r\n" content_type_header = "Content-Type: text/html\r\n\r\n" content = "<h1>404 Not Found</h1><p>The requested file was not found on this server.</p>" http_response = (status_line + content_type_header + content).encode() # 将HTTP响应发送回客户端 client_socket.sendall(http_response) # 处理多个请求后关闭连接 client_socket.close() print("Connection closed") server_socket.close() if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python WebServer.py <port>") sys.exit(1) # 从命令行参数获取端口号 port = int(sys.argv[1]) run_server(port)
解决方案
你的代码存在两个核心问题:一是单线程模型下同一时间只能处理一个客户端,二是Keep-Alive连接的处理逻辑缺失,导致内部死循环无法正常退出,也没法正确复用连接处理多个请求。以下是具体修复方案:
1. 核心逻辑梳理
server_socket.accept()必须放在外层循环,用来持续接受新的客户端连接,一个客户端连接处理完毕后,主线程才能继续等待下一个客户端。- 复用同一连接处理多个请求的关键:在单个客户端连接的生命周期内,持续接收请求并响应,直到客户端主动断开,或者请求明确要求关闭连接。
2. 多线程实现(最易上手的多客户端支持)
单线程无法同时处理多个客户端,我们可以用多线程方案:每接受一个客户端连接就启动一个新线程处理该客户端的所有请求,主线程继续等待新连接,这样就能同时服务多个客户端,且每个客户端的连接可以复用处理多次请求。
修改后的完整代码
from socket import * import sys import threading def handle_client(client_socket, addr): print(f"Connection established from address {addr}") keep_alive = True while keep_alive: # 接收客户端数据,客户端断开时recv返回空字符串 msg = client_socket.recv(1024).decode() if not msg: break # 解析请求行和请求头,处理空请求情况 request_lines = msg.split('\r\n') if not request_lines or not request_lines[0].strip(): break request_line = request_lines[0].strip() # 提取文件名,处理无效请求 try: file_name = request_line.split()[1].replace('/', '') except IndexError: status_line = "HTTP/1.1 400 Bad Request\r\n" content_type = "Content-Type: text/html\r\n\r\n" content = "<h1>400 Bad Request</h1>" client_socket.sendall((status_line + content_type + content).encode()) continue # 处理文件请求 try: with open(file_name, 'rb') as file: status_line = "HTTP/1.1 200 OK\r\n" # 处理不同文件类型的Content-Type file_ext = file_name.split('.')[-1].lower() if '.' in file_name else '' content_type = "" if file_ext == 'html': content_type = "Content-Type: text/html\r\n" elif file_ext == 'png': content_type = "Content-Type: image/png\r\n" else: content_type = "Content-Type: application/octet-stream\r\n" content = file.read() # 判断是否保持连接 connection_header = "close" for line in request_lines[1:]: if line.lower().startswith('connection:'): connection_header = line.split(':')[1].strip().lower() break # HTTP/1.1默认Keep-Alive,1.0默认关闭 http_version = request_line.split()[0].split('/')[1] if http_version == "1.1" and connection_header != "close": keep_alive = True conn_line = "Connection: keep-alive\r\n" else: keep_alive = False conn_line = "Connection: close\r\n" # 组装响应 response = (status_line + conn_line + content_type + "\r\n").encode() + content except OSError: # 文件不存在返回404 status_line = "HTTP/1.1 404 Not Found\r\n" content_type = "Content-Type: text/html\r\n" content = "<h1>404 Not Found</h1><p>The requested file was not found on this server.</p>" # 同样判断连接是否保持 connection_header = "close" for line in request_lines[1:]: if line.lower().startswith('connection:'): connection_header = line.split(':')[1].strip().lower() break http_version = request_line.split()[0].split('/')[1] if http_version == "1.1" and connection_header != "close": keep_alive = True conn_line = "Connection: keep-alive\r\n" else: keep_alive = False conn_line = "Connection: close\r\n" response = (status_line + conn_line + content_type + "\r\n" + content).encode() client_socket.sendall(response) client_socket.close() print(f"Connection closed with {addr}") def run_server(port): server_socket = socket(AF_INET, SOCK_STREAM) # 设置端口复用,避免服务器重启时端口被占用 server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) server_socket.bind(('localhost', port)) # 增大排队连接数,应对多客户端同时连接 server_socket.listen(5) print(f"Serving on port {port}") while True: client_socket, addr = server_socket.accept() # 启动新线程处理客户端请求 thread = threading.Thread(target=handle_client, args=(client_socket, addr)) thread.daemon = True # 设置为守护线程,服务器退出时自动销毁 thread.start() server_socket.close() if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: python WebServer.py <port>") sys.exit(1) port = int(sys.argv[1]) run_server(port)
关键修改说明
- 拆分出
handle_client函数,专门处理单个客户端的所有请求,每个客户端对应独立线程,主线程专注于接受新连接。 - 修复内部循环退出条件:当
recv()返回空字符串时,说明客户端已断开,退出循环并关闭连接。 - 正确解析HTTP请求的
Connection头和版本,决定是否保持连接,实现同一连接下处理多个请求。 - 增加无效请求处理逻辑,避免索引错误导致程序崩溃。
- 添加
SO_REUSEADDR选项,解决服务器重启时的端口占用问题。 - 提高
listen的排队数,应对多客户端同时发起连接的场景。
内容的提问来源于stack exchange,提问作者Ling Foong
相关产品推荐
相关产品推荐

