Reputation: 26660
When working with Protocol Buffers the real message size becomes known when a whole object is written to IO
. So I use following approach: write object to intermediate stream, get it's size and then write whole data, with header containing size int, to TCP socket.
What I do not like in following code is the message_size
function which uses real disk file instead of memory stream.
require 'protocol_buffers'
module MyServer
class AuthRequest < ProtocolBuffers::Message
required :int32, :vers, 1
required :int32, :orgID, 2
required :string, :password, 3
end
class MyServer
def self.authenticate(socket, params)
auth = AuthRequest.new(:vers => params[:vers], :orgID => params[:orgID], :password => params[:password])
size = message_size(auth)
if size.present?
socket.write([size, 0].pack 'NN')
auth.serialize(socket)
socket.flush
end
end
def self.message_size(obj)
size = nil
io = File.new('tempfile', 'w')
begin
obj.serialize(io)
io.flush
size = io.stat.size + 4
ensure
io.close
end
size
end
end
end
Controller:
require 'my_server'
require 'socket'
class MyServerTestController < ActionController::Base
def test
socket = TCPSocket.new('192.168.1.15', '12345')
begin
MyServer::MyServer.authenticate(socket, {vers: 1, orgID: 100, password: 'hello'})
ensure
socket.close
end
end
end
Upvotes: 1
Views: 3423
Reputation: 37409
You can easily use StringIO
as your memory stream. Mind you, it is called StringIO
since it is implemented on a string, and it is definitely not bound for it being string data - it works just as easily on binary data:
def self.message_size_mem(obj)
size = nil
io = StringIO.new
begin
obj.serialize(io)
io.flush
size = io.size + 4
ensure
io.close
end
size
end
auth = AuthRequest.new(:vers => 122324, orgID: 9900235, password: 'this is a test for serialization')
MyServer.message_size(auth)
# => 47
MyServer.message_size_mem(auth)
# => 47
io = StringIO.new
auth.serialize(io)
io.flush
io.string
# => "\bԻ\a\u0010ˡ\xDC\u0004\u001A this is a test for serialization"
Upvotes: 3