構造化されたバイナリ パケットをパック/アンパックできるカスタム EM::Protocol モジュールを作成しようとしています。パケット構造は、文字列、その他の簡単に解析可能な形式、またはある種の DSL として、名前/形式のペアとして定義する必要があります。
アイデアを理解するための簡単なコード:
module PacketProtocol
def self.included(base)
base.extend ClassMethods
end
def receive_data(data)
# retrieve packet header
# find matching packet definition
# packet.unpack(data)
end
module ClassMethods
def packet(defn)
# create an instance of Packet (see blow) and shove it
# somewhere i can get to later.
end
end
end
module MyHandler
include PacketProtocol
packet '<id:S><len:S><msg:A%{len}>'
end
EM.run do
EM.start_server '0.0.0.0', 8080, MyHandler
end
私の目標は、ランタイムの複雑さを最小限に抑えることです。パケット定義は実行ごとに静的であるため、この (粗雑な) 実装は避けたいと思います。
class Packet
FmtSize = {
'S' => 2,
'A' => Proc.new {|fmt| fmt[1..-1].to_i }
}
def initialize(defn)
@fields = defn.scan(/<([^>]+):([^>]+)>/)
end
def pack(data)
data.values.pack @fields.map { |name, fmt| fmt % data }.join
end
def unpack(defn)
data = {}
posn = 0
@fields.each do |name, len|
fmt = len % data
len = FmtSizes[fmt[0]]
len = len.call(fmt) if len.class == Proc
data[name.to_sym] = bytes[posn..posn + len - 1].unpack(fmt)[0]
posn += len
end
data
end
end
data = { :id => 1, :len => 5, :msg = 'Hello' }
packet = Packet.new '<id:S><len:S><msg:A%{len}>'
packed = packet.pack(data)
require 'benchmark'
Benchmark.bm(7) do |x|
x.report('slow') {
100000.times do
unpacked = packet.unpack(packed)
end
}
x.report('fast') {
100000.times do
data = {}
data[:id] = packed[0..1].unpack('S' % data)
data[:len] = packed[2..3].unpack('S' % data)
data[:msg] = packed[4..8].unpack('A%{len}' % data)
end
}
end
# output:
# user system total real
# slow 1.970000 0.000000 1.970000 ( 1.965525)
# fast 0.140000 0.000000 0.140000 ( 0.146227)
2 つの例のうち、Packet クラスを使用すると、数倍遅くなるようです。
それで。質問は:
実行時にコードを生成できる方法 (または宝石) はありますか (単純に文字列を評価する以外に)?
編集:
BinDataが見つかりました。機能セットは優れていますが、ベンチマークもはるかに遅くなります。