使用Protobuf定义网络协议
准备工具:
工具下载地址如下:https://github.com/protocolbuffers/protobuf/releases/tag/v3.6.1,主要使用到的文件有:
protoc.exe工具:通过此工具将从自定义的协议文件(.proto)得到相应(.java)的Java类文件;
对应proto.exe版本的protobuf-java.jar包,用于解析上面得到的.java类,这里我使用的是2.5.0版本的protobuf;
chat_send.proto协议文件,关于proto协议文件的书写语法详细的可以查看:Protobuf语言指南,chat_send.proto内容如下(包名package可以根据当前服务器应用的包名进行修改):
//定义使用的protobuf版本 syntax = "proto2"; //定义所在的protobuf包空间 package ares.logic.msg.proto; //生成的java类所在的包路径 option java_package = "ares.logic.msg.proto"; //生成的java类的类名 option java_outer_classname = "ChatSendMsg"; //声明一个message类 message ChatSend{ //(5) optional int32 mid = 1; // 消息ID, 非必要 required int64 playerid = 3; // 游戏角色ID 必要 required int32 userid = 4; // 用户ID, 必要 required int32 power = 5; // 角色战力值(如果没有可设置为角色等级) required int32 channel = 6; //消息频道 必要 required string content = 7; //聊天内容 必要 required string playername = 8; // 游戏昵称 必要 required int32 zeusid = 9; // 区服ID 必要 required string ip = 10; // 当前发言人的IP 必要 optional int32 banned = 11; // 是否禁言
cs_enum.proto协议类型枚举文件,用于列举所有协议数据结构的编号:
syntax = "proto2"; //定义所在的protobuf包空间 package ares.logic.msg.proto; //生成的java类所在的包路径 option java_package = "ares.logic.msg.proto"; //生成的java类的类名 option java_outer_classname = "EnmMsgId"; enum EnmCmdId { UNIVERSAL = 0; ChatSend = 1001;//登录请求协议号 }
将.proto转化为Java类文件的处理脚本,这里其实只是一句命令行指令:
protoc chat_send.proto --java_out=../src/
这里我根据实际项目目录结构定义了一个转文件的.bat批处理文件:
echo on call protoc --version call protoc chat_send.proto --java_out=../src/ call protoc cs_enum.proto --java_out=../src/ PAUSE
protobuf数据通信过程:
1.客户端创建数据:
要构建一个protobuf数据,需要通过对应协议文件的数据结构,先通过每个数据类型的newBuilder()方法来创建对应的Builder对象,再对Builder中的属性进行赋值,最后才能使用Builder来build()数据对象
public ChatSend getProtobuf() { ChatSend.Builder chatBuilder = ChatSend.newBuilder(); chatBuilder.setMid(EnmCmdId.ChatSend.getNumber()); chatBuilder.setPlayerid(roleId); chatBuilder.setBanned(banned); chatBuilder.setChannel(channel); chatBuilder.setContent(content); chatBuilder.setIp(loginIp); chatBuilder.setPlayername(roleName); chatBuilder.setPower(level); chatBuilder.setUserid(userId); chatBuilder.setZeusid(serverId); return chatBuilder.build(); }
客户端单独启用一个线程,将请求的消息发送给服务器
private void initThread() { BlockingThreadPool.createThreads(BlockingThreadPool.SkyEye_CHECKER, 1, new SocketHandler()); } private class SocketHandler implements BlockingThreadPool.Callbacker<SkyEyeMsg> { private boolean retry; @Override public void callback(SkyEyeMsg info) { byte[] data = info.getProtobuf().toByteArray(); try { if (socket == null) { initSocket(); } byte[] len = intToByteArray(data.length); socket.getOutputStream().write(HEAD); socket.getOutputStream().write(len); socket.getOutputStream().write(data); } catch (Exception e) { closeSocket(); //socket重连 if(retry){ SkyEyeChecker.logger.error("ckeckMsg Exception :" + e); }else{ retry = true; callback(info); retry = false; } } } byte[] intToByteArray(int len) { byte[] data = new byte[4]; data[0] = (byte) (len >> 24); data[1] = (byte) (len >> 16); data[2] = (byte) (len >> 8); data[3] = (byte) len; return data; } }