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

Ruby客户端与Python服务器TCP通信时无法接收完整消息的解决方案咨询

解决TCP流式通信的消息完整性问题

你猜的完全没错——TCP是流式协议,它只会把数据当成连续的字节流传输,不会帮你划分消息的边界。所以你看到的“只收到部分内容”,就是因为客户端/服务器在调用recv或gets时,只拿到了当前缓冲区里的部分字节,剩下的还在传输途中。

要解决这个问题,你需要在应用层自定义消息边界规则,让两端都能明确识别“一条完整消息的结束位置”。下面给你两种实用的方案,附带修改后的代码:


方案1:用换行符作为消息分隔符(适合纯文本场景)

这是最简单的实现方式,适合你的日志类文本消息——只要保证消息本身不会包含换行符,就可以用\n作为完整消息的标记。

修改后的Ruby客户端(client.rb)

require 'socket'
hostname = 'localhost'
port = 7778
s = TCPSocket.open(hostname, port)

# 发送时给消息末尾加上换行符,标记消息结束
message = "2020-06-25T11:11:00+00:00 5 127.0.0.1 printer: event\n"
s.write(message)

# gets 默认会读取到换行符为止,确保拿到完整消息
while line = s.gets
  puts "收到服务器回传:#{line.chop}"
end
s.close()

修改后的Python服务器(server.py)

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 7778)
print('starting up on %s port %s' % server_address)
sock.bind(server_address)
sock.listen(1)

while True:
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        buffer = b''  # 用缓存存储未处理的字节
        while True:
            data = connection.recv(1024)
            if not data:
                print('no more data from', client_address)
                break
            
            buffer += data
            # 检查缓存里是否有完整的消息(包含换行符)
            while b'\n' in buffer:
                # 拆分出完整消息和剩余缓存
                full_message, buffer = buffer.split(b'\n', 1)
                print('received "%s"' % full_message.decode('utf-8'))
                # 回传时也要加上换行符,让客户端能识别完整消息
                connection.sendall(full_message + b'\n')
    finally:
        connection.close()

方案2:用长度前缀标记消息长度(通用严谨方案)

如果你的消息可能包含换行符,或者需要更严谨的消息边界控制,推荐用“长度前缀”的方式:发送前先把消息的长度转成固定字节数(比如4字节的网络字节序整数),接收端先读长度,再根据长度读取对应字节数的内容。

修改后的Ruby客户端(client.rb)

require 'socket'
hostname = 'localhost'
port = 7778
s = TCPSocket.open(hostname, port)

message = "2020-06-25T11:11:00+00:00 5 127.0.0.1 printer: event"
# 把消息长度打包成4字节的大端整数(网络字节序)
length_prefix = [message.bytesize].pack('N')
# 先发送长度前缀,再发送消息内容
s.write(length_prefix + message)

# 先读取4字节的长度前缀
length_data = s.read(4)
unless length_data
  puts "服务器已断开连接"
  s.close()
  exit
end
# 解析出消息长度
message_length = length_data.unpack('N')[0]
# 读取对应长度的完整消息
received_message = s.read(message_length)
puts "收到服务器回传:#{received_message}"

s.close()

修改后的Python服务器(server.py)

import socket
import struct

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 7778)
print('starting up on %s port %s' % server_address)
sock.bind(server_address)
sock.listen(1)

def recv_exact(connection, required_length):
    """辅助函数:确保读取到指定长度的字节数据"""
    received = b''
    while len(received) < required_length:
        chunk = connection.recv(required_length - len(received))
        if not chunk:
            return None  # 连接中断
        received += chunk
    return received

while True:
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        # 先读取4字节的长度前缀
        length_prefix = recv_exact(connection, 4)
        if not length_prefix:
            print('no more data from', client_address)
            break
        
        # 解析消息长度(!N表示大端4字节无符号整数)
        message_length = struct.unpack('!N', length_prefix)[0]
        # 读取对应长度的完整消息
        full_message = recv_exact(connection, message_length)
        if not full_message:
            break
        
        print('received "%s"' % full_message.decode('utf-8'))
        # 回传时同样先发送长度前缀,再发送消息
        connection.sendall(length_prefix + full_message)
    finally:
        connection.close()

方案选择建议

  • 如果你的消息是纯文本且不会包含换行符,优先选方案1,实现简单成本低;
  • 如果消息可能包含特殊字符,或者需要处理二进制数据,方案2更可靠,能避免边界冲突问题。

内容的提问来源于stack exchange,提问作者user1234

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 09:57:50