iosnettyprotocol-buffersgcdasyncsocketcocoaasyncsocket

iOS protobuf GCDAsyncSocket


I am writing a small app which requires sending protobuf message to a netty server via tcp, where CocoaAsyncSocket/GCDAsyncSocket is used.

To do this, I use the following code:

Message_LoginMessageRequest_Builder *msg_tmp_login_build=[Message_LoginMessageRequest builder];
[msg_tmp_login_build setPhoneNumber:input_phone.text];
[msg_tmp_login_build setEmail:input_email.text];
[msg_tmp_login_build setPassword:input_password.text];

Message_Builder *msg_tmp_build=[Message builder];
[msg_tmp_build setType:Message_MessageTypeLoginReq];
[msg_tmp_build setLoginRequest:msg_tmp_login_build.build];

// send the message to the socket: we need to code the stream first
Message *msg=[msg_tmp_build build];
NSMutableData *msg_data=[[NSMutableData alloc] initWithCapacity:[[msg data] length]];
PBCodedOutputStream *coded_output_stream=[PBCodedOutputStream streamWithData:msg_data];
[msg writeToCodedOutputStream:coded_output_stream];
[socket writeData:msg_data withTimeout:-1 tag:Message_MessageTypeLoginReq];

However, the code always received "out of space" error in writeToCodedOutputStream, where the detailed trace information is: writeToCodedOutputStream/writeEnum/writeTag/writeRawVariant32/writeRawByte/flush.

Any help? Thanks!


Solution

  • I have figured out the answer myself, by reading the code for netty codec for protobuf, which can be found in netty/codec/src/main/java/io/netty/handler/codec/protobuf/

    In netty protobuf, a message header describing the length of the protobuf message (refer to message body later) must be inserted before the message body. The length of the message body must be coded via RawVariant32 type. For more detail, please refer the example in ProtobufVariant32FrameDecoder.java, which is also cited below:

    /**
     * A decoder that splits the received {@link ByteBuf}s dynamically by the
     * value of the Google Protocol Buffers
     * <a href="http://code.google.com/apis/protocolbuffers/docs/encoding.html#varints">Base
     * 128 Varints</a> integer length field in the message.  For example:
     * <pre>
     * BEFORE DECODE (302 bytes)       AFTER DECODE (300 bytes)
     * +--------+---------------+      +---------------+
     * | Length | Protobuf Data |----->| Protobuf Data |
     * | 0xAC02 |  (300 bytes)  |      |  (300 bytes)  |
     * +--------+---------------+      +---------------+
     * </pre>
     *
     * @see CodedInputStream
     */
    public class ProtobufVarint32FrameDecoder extends ByteToMessageDecoder {
    
        // TODO maxFrameLength + safe skip + fail-fast option
        //      (just like LengthFieldBasedFrameDecoder)
    
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            in.markReaderIndex();
            final byte[] buf = new byte[5];
            for (int i = 0; i < buf.length; i ++) {
                if (!in.isReadable()) {
                    in.resetReaderIndex();
                    return;
                }
    
                buf[i] = in.readByte();
                if (buf[i] >= 0) {
                    int length = CodedInputStream.newInstance(buf, 0, i + 1).readRawVarint32();
                    if (length < 0) {
                        throw new CorruptedFrameException("negative length: " + length);
                    }
    
                    if (in.readableBytes() < length) {
                        in.resetReaderIndex();
                        return;
                    } else {
                        out.add(in.readBytes(length));
                        return;
                    }
                }
            }
    
            // Couldn't find the byte whose MSB is off.
            throw new CorruptedFrameException("length wider than 32-bit");
        }
    }
    

    You are also welcomed to visit my personal page for more detail: http://167.88.47.13/?p=254