python client 与 netty server , 使用protobuf通讯的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lucyTheSlayer/article/details/79722751

场景是这样的,服务端是netty,所有消息采用protobuf封装

//handler
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new IdleStateHandler(0, 0, 60, TimeUnit.SECONDS));  //1
        pipeline.addLast(new HeartbeatHandler());
        // ----Protobuf处理器,这里的配置是关键----
        pipeline.addLast("frameDecoder", new ProtobufVarint32FrameDecoder());
        //配置Protobuf解码处理器,消息接收到了就会自动解码,ProtobufDecoder是netty自带的,Message是自己定义的Protobuf类
        pipeline.addLast("protobufDecoder",
                new ProtobufDecoder(FFCallSigOuterClass.Sig.getDefaultInstance()));
        pipeline.addLast(new FFCallServerHandler()); //自己的业务处理
        // 用于在序列化的字节数组前加上一个简单的包头,只包含序列化的字节长度。
        pipeline.addLast("frameEncoder",
                new ProtobufVarint32LengthFieldPrepender());
        //配置Protobuf编码器,发送的消息会先经过编码
        pipeline.addLast("protobufEncoder", new ProtobufEncoder());
        // ----Protobuf处理器END----

此时如果客户端也是netty,那么通讯没有问题,但如果是非java语言,那么如果直接发送/接受protobuf序列化后的二进制包,会报以下异常:

io.netty.handler.codec.DecoderException: com.google.protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).

经过一阵子百度谷歌(毕竟 copy & paste engineer),原因是服务端采用了

pipeline.addLast("frameDecoder", new ProtobufVarint32FrameDecoder());

以及

 pipeline.addLast("frameEncoder",
                new ProtobufVarint32LengthFieldPrepender());

在对每个protobuf消息进行发送前,会加一个消息的长度,同时解码的时候,也需要先解析这个长度再解析消息,以免出现半包、粘包问题。

所以,非java语言的客户端只需要自己撸个消息长度上去即可。

以python为例

    def sendSig(self,sig):
        print("=>", sig.SerializeToString())
        value = len(sig.SerializeToString())
        bits = value & 0x7f
        value >>= 7
        while value:
            self.send_queue.put(six.int2byte(0x80 | bits))
            bits = value & 0x7f
            value >>= 7
        self.send_queue.put(six.int2byte(bits))
        self.send_queue.put(sig.SerializeToString())

这是往服务器发送protobuf消息,sig即是待发送的protobuf对象。具体编码细节可以搜索Varint32。

至于接受消息,则略微复杂一点,我贴出我的实现,没有考虑性能,凑合反正能用。

class FFRespReciver(Thread):

    PARSING_LEN = 0
    PARSING_MSG = 1

    def __init__(self,ffconnector):
        super(FFRespReciver,self).__init__()
        self.ffconnector = ffconnector
        self.data_buffer = b""
        self.parse_status = FFRespReciver.PARSING_LEN
        self.msg_len = 0

    def run(self):
        while True:
            data = self.ffconnector.recv_queue.get()
            for b in data:
                if self.parse_status == FFRespReciver.PARSING_LEN:
                    self.data_buffer += six.int2byte(b)
                    if not (b & 0x80):
                        self.msg_len = DecodeVarint(self.data_buffer)
                        self.parse_status = FFRespReciver.PARSING_MSG
                        self.data_buffer = b""
                        continue
                elif self.parse_status == FFRespReciver.PARSING_MSG:
                    self.data_buffer += six.int2byte(b)
                    if len(self.data_buffer) == self.msg_len:
                        sig = SIG.Sig()
                        sig.ParseFromString(self.data_buffer)
                        self.process(sig)
                        self.data_buffer = b""
                        self.msg_len = 0
                        self.parse_status = FFRespReciver.PARSING_LEN

    def process(self,sig):
        print(sig)
        //do the dirty job

def DecodeVarint(buffer):
        mask = (1 << 32) - 1
        result_type = int
        result = 0
        shift = 0
        for b in buffer:
            result |= ((b & 0x7f) << shift)
            shift += 7
            if shift >= 64:
                raise Exception('Too many bytes when decoding varint.')
        result &= mask
        result = result_type(result)
        return result

即用个状态机,先接受消息长度,解析后,接受消息并处理,然后继续接受消息长度。

PS:仅个人记录所用,方便以后查看,不用重新再百度谷歌一遍。。。




阅读更多
换一批

没有更多推荐了,返回首页