You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Socket Server recv()阻塞问题排查求助

阻塞Socket收发未知长度文本时服务端无限等待的问题

问题场景

我要搭建客户端与服务端的简单工作流,收发未知长度的文本数据,步骤如下:

    1. 服务端启动并监听
    1. 客户端启动并完成通信配置
    1. 客户端向服务端发送文本数据
    1. 服务端分块接收消息
    1. 服务端利用数据执行任务
    1. 服务端分块返回处理结果
    1. 客户端接收全部数据
    1. 关闭连接,服务端继续监听

我选择了阻塞版本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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:55:58