Python Socket Server recv()阻塞问题排查求助
阻塞Socket收发未知长度文本时服务端无限等待的问题
问题场景
我要搭建客户端与服务端的简单工作流,收发未知长度的文本数据,步骤如下:
- 服务端启动并监听
- 客户端启动并完成通信配置
- 客户端向服务端发送文本数据
- 服务端分块接收消息
- 服务端利用数据执行任务
- 服务端分块返回处理结果
- 客户端接收全部数据
- 关闭连接,服务端继续监听
我选择了阻塞版本Socket编写代码,目前步骤0-2正常运行,但步骤3中服务端接收完数据后会无限等待,if not chunk:始终不执行,while循环无法终止。试过匹配闭合双大括号等判断条件,没用。实际场景数据较长,必须分块传输,求问题原因及解决办法。
原代码
import threading import socket import json import time class RWF_server: def start_server(self): host_address = 'localhost' port = 9999 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: server.bind((host_address, port)) except OSError: raise print(f"Could not bind to {host_address}:{port}, is a server already running here?") server.listen(5) print(f"Server listening on {host_address}:{port}") while True: conn, addr = server.accept() print(f'Connected by {addr}') data = "" while True: chunk = conn.recv(1024).decode('utf-8') print('Server got:', chunk) if not chunk: break data += chunk if not data: continue # Continue to the next connection if no data was received message = json.loads(data) print(f'Received full message: {message}, getting to work') # Simulate processing time.sleep(3) print('Task is done') response = {'status': 'success', 'message': 'Data received'} response_data = json.dumps(response) conn.sendall(response_data.encode('utf-8')) conn.close() class RWF_client: def start_client(self): content = {2: 'path_to_a_file\\__batch_B3310.bat', 'data': {'FY_phase': 0.0, 'MY_phase': 0.0, 'head_chamfer_legth': 0.1, 'head_radius': 2900.0, 'head_thickness': 13.0, 'head_thickness_at_nozzle': 13.0, }} print('The following content is sent to the server: {}'.format(content)) with socket.socket() as sock: try: sock.connect(('localhost', 9999)) except (ConnectionRefusedError, TimeoutError): raise sock.sendall(json.dumps(content).encode('utf-8')) print('Content sent to the server') response = "" while True: chunk = sock.recv(1024).decode('utf-8') print('Client getting response: {}'.format(chunk)) if not chunk: break response += chunk print(f'Received response: {json.loads(response)}') response = json.loads(response) return response server_thread = threading.Thread(target=RWF_server().start_server) server_thread.start() client_thread = threading.Thread(target=RWF_client().start_client) client_thread.start() server_thread.join() client_thread.join()
问题原因
问题核心是客户端发送完数据后未关闭Socket的写端,导致服务端的recv()一直阻塞等待,无法收到EOF(空字节信号),因此if not chunk:永远不会触发。
在TCP协议中,只有当客户端主动关闭连接的写通道(或整个连接),服务端才会收到空的chunk,以此判断数据传输完成。但你的客户端发送完数据后直接进入接收响应的循环,此时Socket双向连接仍处于打开状态,服务端无法得知数据已发送完毕,会持续等待。
解决方法
提供两种可靠方案,按需选择:
方案1:客户端发送完数据后关闭写端(保留读端)
在客户端调用sock.sendall()后,添加sock.shutdown(socket.SHUT_WR),明确告知服务端“数据已发送完毕,不会再发新数据”,但客户端仍能保持连接接收服务端的响应。
修改后的客户端关键代码片段:
sock.sendall(json.dumps(content).encode('utf-8')) print('Content sent to the server') # 关闭写端,告知服务端数据发送完成 sock.shutdown(socket.SHUT_WR) response = "" while True: chunk = sock.recv(1024).decode('utf-8') print('Client getting response: {}'.format(chunk)) if not chunk: break response += chunk
方案2:使用长度前缀协议(更适合生产环境)
如果需要频繁收发数据(不想每次发完就关闭写端),可以在发送JSON数据前,先发送数据的长度(比如用固定4字节的整数表示),服务端先接收长度值,再根据长度接收对应字节的数据,无需依赖连接关闭判断结束。
示例修改:
- 客户端发送逻辑:
json_data = json.dumps(content).encode('utf-8') # 先发送数据长度(4字节,大端字节序) length = len(json_data) sock.sendall(length.to_bytes(4, byteorder='big')) # 再发送实际数据 sock.sendall(json_data)
- 服务端接收逻辑:
# 先接收长度前缀 length_bytes = conn.recv(4) if not length_bytes: continue length = int.from_bytes(length_bytes, byteorder='big') # 再接收对应长度的数据 data = b"" while len(data) < length: chunk = conn.recv(min(1024, length - len(data))) if not chunk: break # 连接异常中断 data += chunk data = data.decode('utf-8') message = json.loads(data)
完整修改后的代码(方案1)
import threading import socket import json import time class RWF_server: def start_server(self): host_address = 'localhost' port = 9999 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: server.bind((host_address, port)) except OSError: print(f"Could not bind to {host_address}:{port}, is a server already running here?") raise server.listen(5) print(f"Server listening on {host_address}:{port}") while True: conn, addr = server.accept() print(f'Connected by {addr}') data = "" while True: chunk = conn.recv(1024).decode('utf-8') print('Server got:', chunk) if not chunk: break data += chunk if not data: conn.close() continue # Continue to the next connection if no data was received message = json.loads(data) print(f'Received full message: {message}, getting to work') # Simulate processing time.sleep(3) print('Task is done') response = {'status': 'success', 'message': 'Data received'} response_data = json.dumps(response) conn.sendall(response_data.encode('utf-8')) conn.close() class RWF_client: def start_client(self): content = {2: 'path_to_a_file\\__batch_B3310.bat', 'data': {'FY_phase': 0.0, 'MY_phase': 0.0, 'head_chamfer_legth': 0.1, 'head_radius': 2900.0, 'head_thickness': 13.0, 'head_thickness_at_nozzle': 13.0, }} print('The following content is sent to the server: {}'.format(content)) with socket.socket() as sock: try: sock.connect(('localhost', 9999)) except (ConnectionRefusedError, TimeoutError): raise sock.sendall(json.dumps(content).encode('utf-8')) print('Content sent to the server') # 关闭写端,告知服务端数据发送完成 sock.shutdown(socket.SHUT_WR) response = "" while True: chunk = sock.recv(1024).decode('utf-8') print('Client getting response: {}'.format(chunk)) if not chunk: break response += chunk print(f'Received response: {json.loads(response)}') response = json.loads(response) return response server_thread = threading.Thread(target=RWF_server().start_server) server_thread.start() client_thread = threading.Thread(target=RWF_client().start_client) client_thread.start() server_thread.join() client_thread.join()
内容的提问来源于stack exchange,提问作者jake77
相关产品推荐
相关产品推荐

