Ruby客户端-服务端Socket数据传输截断问题求助
问题根源与解决方案
核心问题
你的代码在处理二进制数据时,误用了文本分隔符和行读取方法,直接导致数据截断:
Marshal.dump生成的二进制数据中,必然包含逗号(,)和换行符(\n)——这两个字符正好是你用来拼接请求、判断数据结束的标记。- 客户端用
,拼接action与二进制数据,当二进制里的逗号被服务端split(',')识别为分隔符时,数据会被拆成多个片段,服务端只拿到第一个片段。 - 客户端用
socket.puts发送数据(自动追加换行符),而二进制数据本身可能包含换行符,服务端socket.gets读到第一个换行就停止读取,导致只拿到部分数据。
解决方案(推荐两种)
方案1:使用长度前缀传输二进制数据(最可靠)
二进制数据不能用字符做分隔,标准做法是先发送数据长度,再发送实际内容,让服务端按长度读取完整数据:
客户端修改代码:
require 'marshal' def send_request(socket, action, data = {}) # 将整个请求序列化 payload = Marshal.dump({ action: action, data: data }) # 先发送payload的字节长度(用8字节网络字节序编码) socket.write([payload.size].pack('Q')) # 发送实际序列化内容 socket.write(payload) end player = { type: :FISHERMAN, name: "Andrew", party: $Trainer.party, speech: "Good game." } send_request(@server, 'player_data', { data: Marshal.dump(player) })
服务端修改代码:
require 'marshal' begin loop do # 先读取8字节的长度标记 len_data = socket.read(8) break if len_data.nil? || len_data.size < 8 payload_size = len_data.unpack('Q')[0] # 按长度读取完整的payload payload = socket.read(payload_size) break if payload.nil? || payload.size < payload_size request = Marshal.load(payload) action = request[:action] data = request[:data] case action when 'player_data' player_data = data[:data] # 处理完整的Marshal数据 player = Marshal.load(player_data) # ...后续逻辑 end end rescue => e # 处理连接中断、序列化异常等情况 puts "Error: #{e.message}" end
方案2:对二进制数据做Base64编码(兼容文本传输)
如果一定要用文本分隔的方式,可将二进制数据转为Base64字符串(无特殊字符),避免分隔符冲突:
客户端修改代码:
require 'marshal' require 'base64' def send_request(socket, action, data = {}) # 对二进制数据做Base64编码 encoded_data = data.transform_values { |v| Base64.strict_encode64(v) } request_data = [action] + encoded_data.map { |k, v| "#{k}:#{v}" } socket.puts(request_data.join(',')) end player = { type: :FISHERMAN, name: "Andrew", party: $Trainer.party, speech: "Good game." } send_request(@server, 'player_data', { data: Marshal.dump(player) })
服务端修改代码:
require 'marshal' require 'base64' begin loop do data = socket.gets break if data.nil? request_data = data.chomp.split(',') action = request_data[0] request = {} request_data[1..].each do |item| key, value = item.split(':', 2) request[key] = value end case action when 'player_data' # 解码Base64数据 encoded_data = request['data'] player_data = Base64.strict_decode64(encoded_data) # 还原Marshal对象 player = Marshal.load(player_data) # ...后续逻辑 end end end
内容的提问来源于stack exchange,提问作者Miquel Ibáñez
相关产品推荐
相关产品推荐

