使用protobuf定义消息

本文详细介绍了如何使用Protocol Buffers(protobuf)进行消息通信,包括下载、安装protobuf-2.3.0,编写.proto文件,以及如何通过C++代码发送和接收消息。
下载protobuf-2.3.0:
    http://protobuf.googlecode.com/files/protobuf-2.3.0.zip
安装: 
unzip protobuf-2.3.0.zip
cd protobuf-2.3.0
./configure
make 
make check 
make install

结果:
Libraries have been installed in:
   /usr/local/lib
Head files hava been installed in:
/usr/local/include/google/
protobuf/


开始写.proto文件:
BaseMessage.proto:
message MessageBase
{
    required int32 opcode = 1;
    // other: sendMgrId, sendId, recvMgrId, recvId, ...
}

message BaseMessage
{
    required MessageBase msgbase = 1;
}

BaseMessage.proto是其它消息proto文件的基础,以容器模块的C2S_GetContainerInfo为例:
ContainerMessage.proto:
import "BaseMessage.proto";

message C2SGetContainerInfoMsg
{
    required MessageBase msgbase = 1;
    optional int32 containerType = 2;
}

.proto文件编写规则
1)所有消息都需要包含msgbase这项,并编号都为1,即:
  required MessageBase msgbase = 1;
2)除了msgbase这项写成required外,其它所有项都写成optional。

编译 .proto 文件
protoc -I=. --cpp_out=. BaseMessage.proto
protoc -I=. --cpp_out=. ContainerMessage.proto
生成BaseMessage.pb.h、BaseMessage.pb.cc
    ContainerMessage.pb.h、ContainerMessage.pb.cc
将它们添加到工程文件中。

编写C++代码:
1)发送消息:
C2SGetContainerInfoMsg msg;
msg.mutable_msgbase()->set_opcode(C2S_GetContainerInfo);
msg.set_containertype(1);
std::string out = msg.SerializeAsString();
send(sockfd, out.c_str(), out.size(), 0);
2)接收消息
char buf[MAXBUF + 1];
int len;
bzero(buf, MAXBUF + 1);
len = recv(new_fd, buf, MAXBUF, 0);
if (len > 0)
{
    printf("%d接收消息成功:'%s',共%d个字节的数据\n",
            new_fd, buf, len);

    BaseMessage baseMsg;
    std::string data = buf;
    baseMsg.ParseFromString(data);

    int opcode = baseMsg.mutable_msgbase()->opcode();

    printf("opcode=%d\n", opcode);

    switch (opcode)
    {
    case C2S_GetContainerInfo:
    {
        C2SGetContainerInfoMsg msg;
        msg.ParseFromString(data);

        printf("containerType=%d\n", msg.containertype());

        break;
    }
    default:
    {
        break;
    }
    }
}
else
{
    if (len < 0)
        printf("消息接收失败!错误代码是%d,错误信息是'%s'\n",
             errno, strerror(errno));
    close(new_fd);
    return -1;
}


编译C++代码:
Need to link lib:
protobuf
pthread


参考: 
1, Google Protocol Buffer 的使用和原理: http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html?ca=drs-
2,http://code.google.com/p/protobuf/
为了提升通信效率和数据结构的规范性,我们可以将聊天系统中传输的消息格式从原始字符串改为使用 **Protocol Buffers(Protobuf)** 进行序列化和反序列化。 --- ## ✅ 本篇目标 - 使用 Protobuf 定义聊天消息格式 - 在 Netty WebSocket 聊天系统中,使用 Protobuf 传输消息 - 支持消息类型(文本、心跳等) - 支持客户端与服务端的 Protobuf 编解码 --- ## 🧩 实现步骤概览 1. 定义 `.proto` 文件描述消息结构 2. 使用 `protoc` 编译生成 Java 类 3. 在 Netty 中添加 Protobuf 编解码器 4. 修改 `TextWebSocketFrameHandler` 处理 Protobuf 消息 5. 修改前端发送和接收 Protobuf 消息使用 `ArrayBuffer` 或 `Blob`) --- ## 📁 项目结构更新 ``` netty-chat ├── pom.xml ├── src │ └── main │ ├── java │ │ └── com.example.nettychat │ │ ├── ChatServer.java │ │ ├── ChatServerInitializer.java │ │ ├── TextWebSocketFrameHandler.java │ │ └── ChatMessage.java │ └── resources │ └── web │ └── index.html ├── proto │ └── chat_message.proto └── generated └── com.example.nettychat.protobuf.ChatMessageProtos.java ``` --- ## 🧱 1. 定义 Protobuf 消息格式 `chat_message.proto` ```proto // proto/chat_message.proto syntax = "proto3"; option java_package = "com.example.nettychat.protobuf"; option java_outer_classname = "ChatMessageProtos"; message ChatMessage { string sender = 1; string content = 2; int32 type = 3; // 0: normal text, 1: ping, 2: pong int64 timestamp = 4; } ``` --- ## 🧱 2. 编译生成 Java Protobuf使用 `protoc` 编译: ```bash protoc --java_out=generated proto/chat_message.proto ``` 生成文件路径:`generated/com/example/nettychat/protobuf/ChatMessageProtos.java` --- ## 🧱 3. 添加 Protobuf 依赖到 `pom.xml` ```xml <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.21.12</version> </dependency> ``` --- ## 🧱 4. 修改 `ChatServerInitializer.java` 添加 Protobuf 编解码器 ```java package com.example.nettychat; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.*; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.handler.timeout.IdleStateHandler; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import java.util.concurrent.TimeUnit; import com.example.nettychat.protobuf.ChatMessageProtos; public class ChatServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new WebSocketServerProtocolHandler("/chat")); // Protobuf 编解码 pipeline.addLast(new ProtobufVarint32FrameDecoder()); pipeline.addLast(new ProtobufDecoder(ChatMessageProtos.ChatMessage.getDefaultInstance())); pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new IdleStateHandler(60, 60, 60, TimeUnit.SECONDS)); pipeline.addLast(new TextWebSocketFrameHandler()); } } } ``` --- ## 🧱 5. 修改 `TextWebSocketFrameHandler.java` 使用 Protobuf ```java package com.example.nettychat; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.handler.timeout.IdleStateEvent; import io.netty.util.concurrent.GlobalEventExecutor; import com.example.nettychat.protobuf.ChatMessageProtos.ChatMessage; public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<ChatMessage> { private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override public void handlerAdded(ChannelHandlerContext ctx) { channels.add(ctx.channel()); System.out.println("客户端加入:" + ctx.channel().id()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) { System.out.println("客户端离开:" + ctx.channel().id()); } @Override protected void channelRead0(ChannelHandlerContext ctx, ChatMessage message) { int type = message.getType(); String content = message.getContent(); if (type == 1) { // ping System.out.println("收到心跳 ping"); ChatMessage pong = ChatMessage.newBuilder(message) .setType(2) .setContent("pong") .build(); ctx.writeAndFlush(pong); } else if (type == 0) { System.out.println("收到消息:" + content); channels.writeAndFlush(message); } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; switch (event.state()) { case WRITER_IDLE: ChatMessage ping = ChatMessage.newBuilder() .setSender("server") .setContent("ping") .setType(1) .setTimestamp(System.currentTimeMillis()) .build(); ctx.writeAndFlush(ping); break; case READER_IDLE: ctx.close(); break; } } } } ``` --- ## 🧱 6. 修改 `index.html` 使用二进制方式发送 Protobuf 消息 前端需要使用 `ArrayBuffer` 和 Protobuf JS 库来序列化和反序列化消息。 ### 安装 Protobuf JS 库(在 HTML 中引入) ```html <script src="https://unpkg.com/google-protobuf@3.21.12/google-protobuf.js"></script> <script src="chat_message_pb.js"></script> <!-- 用 protoc 生成的 JS Protobuf 文件 --> ``` ### 示例 JS 发送 Protobuf 消息 ```html <script> var ws = new WebSocket("ws://localhost:8080/chat"); ws.binaryType = 'arraybuffer'; ws.onmessage = function(e) { if (typeof e.data === 'string') { console.log("收到文本消息:" + e.data); return; } var bytes = new Uint8Array(e.data); var message = chat_message_pb.ChatMessage.deserializeBinary(bytes); if (message.getType() === 2) { console.log("收到心跳 pong"); } else { var chat = document.getElementById("chat"); chat.innerHTML += "<p>" + message.getSender() + ": " + message.getContent() + "</p>"; } }; function send() { var input = document.getElementById("msg"); var content = input.value; if (!content) return; var message = new chat_message_pb.ChatMessage(); message.setSender("user1"); message.setContent(content); message.setType(0); message.setTimestamp(Date.now()); var buffer = message.serializeBinary(); ws.send(buffer); input.value = ""; } </script> ``` --- ## ✅ Protobuf 编译命令(生成 JS) ```bash protoc --js_out=import_style=commonjs,binary:generated proto/chat_message.proto ``` 生成文件:`generated/chat_message_pb.js` --- ## ✅ 总结 | 功能 | 实现方式 | |------|----------| | 消息结构定义 | Protobuf `.proto` 文件 | | 消息编解码 | Netty 的 `ProtobufDecoder` / `ProtobufEncoder` | | 心跳机制 | Netty `IdleStateHandler` + Protobuf 消息类型 | | 前端支持 | 使用 `ArrayBuffer` + Protobuf JS 库 | | 消息广播 | Netty `ChannelGroup` | --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值