因为项目需要,需要自定义通信协议。序列化协议使用到了Google的ProtoBuf,这里也是通过一个案例来实现基于Netty的私有化协议的开发。
protoBuf 介绍
Google Protocol Buffer(protoBuf)是一种平台无关,语言无关,可扩展且轻便高效的序列化数据结构的协议,
相比传统的XML、JSON序列化的方式,更小、序列化更快、传输速度更快。只需要定义一次你的结构化文件,就可以自己
生成源代码,实现轻松的读写结构化文件。并且可以使用各种语言.
优点
- 跨平台、跨语言,支持Java、Python、C++、Go、JavaScript;
- 序列化更快(比xml、json方式提升20~100倍);
- 代码自动生成,生成不同语言代码;
缺点
- 通用性较差,没有json、xml普及;
- 数据结构化没有json、xml表达能力那么强;
- 以二进制数据流方式存储(不可读),需要通过.proto文件 才能了解到数据结构;
总结:protoBuf更侧重数据序列化,应用场景更明确,xml、json的应用场景更为丰富。
protoBuf 语法
这里就不一一介绍语法,后面会开专门章节来讲,直接贴上一段定义好的.proto消息文件。
syntax = "proto2";
package com.elisland.customprotocol;
option optimize_for = SPEED; //文件属性
option java_package = "com.elisland.customprotocol";
option java_outer_classname = "CustomMessageData";
message MessageData{
required int64 length = 1;
optional Content content = 2;
enum DataType {
REQ_LOGIN = 0; //上线登录验证环节 等基础信息上报
RSP_LOGIN = 1; //返回上线登录状态与基础信息
PING = 2; //心跳
PONG = 3; //心跳
REQ_ACT = 4; //动作请求
RSP_ACT = 5; //动作响应
REQ_CMD = 6; //指令请求
RSP_CMD = 7; //指令响应
REQ_LOG = 8 ;//日志请求
RSP_LOG = 9; //日志响应
}
optional DataType order = 3;
message Content{
optional int64 contentLength = 1;
optional string data = 2;
}
}
文件生成,这里使用Java语言生成,java文件。语法 protoc --java_out=生成java文件位置 .proto文件位置
代码生成完成后,接下来就是基于Netty来传输protoBuf协议。
Netty 使用
Netty简介:高效的Java NIO框架,通过对传统NIO的封装,提供更加便捷高效的非阻塞式开发。 后面会开具体的章节来讲解Netty的学习。本次案例,通过Netty构建客户端、服务端之间的数据传输、以及通过我们自定义编解码器的方式来解析和编码我们使用的.protoBuf消息。闲话少说,后面直接上代码。
服务器Server端
服务端
public class MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();//接收连接,将连接发送给worker
EventLoopGroup workerGroup = new NioEventLoopGroup();//处理连接
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyDecode());
pipeline.addLast(new MyEncode());
pipeline.addLast(new MyServerHandle());
}
});
ChannelFuture sync = serverBootstrap.bind(8899).sync();
sync.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();//关闭
workerGroup.shutdownGracefully();
}
}
}
自定义编码器
public class MyEncode extends MessageToByteEncoder<CustomMessageData.MessageData> {
/**
* 编码器
* @param ctx
* @param msg
* @param out
* @throws Exception
*/
@Override
protected void encode(ChannelHandlerContext ctx, CustomMessageData.MessageData msg, ByteBuf out) throws Exception {
System.out.println("MyEncode invoke...");
out.writeInt(((int) (msg.getLength())));
out.writeByte((byte)msg.getOrder().getNumber());
out.writeBytes(msg.getContent().getData().getBytes());
}
}
自定义解码器
public class MyDecode extends ReplayingDecoder {
/**
* 解码器
* @param ctx
* @param in
* @param out
* @throws Exception
*/
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("Mydecode invoke...");
int length = in.readInt();
byte order = in.readByte();
byte[] content = new byte[length];
in.readBytes(content);
CustomMessageData.MessageData decodeData = CustomMessageData.MessageData.newBuilder()
.setLength(length)
.setOrder(CustomMessageData.MessageData.DataType.forNumber(order))
.setContent(CustomMessageData.MessageData.Content.newBuilder()
.setData(new String(content, Charset.forName("utf-8")))).build();
out.add(decodeData);
}
}
服务端Handler
public class MyServerHandle extends SimpleChannelInboundHandler<CustomMessageData.MessageData> {
int count;//记录接收数据数量
/**
* 接受client发送来的消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, CustomMessageData.MessageData msg) throws Exception {
long length = msg.getLength();
CustomMessageData.MessageData.DataType order = msg.getOrder();
String ContentData = msg.getContent().getData();
System.out.println("服务端接收到的数据长度:" + length);
System.out.println("服务端接收到的数据指令:" + order);
System.out.println("服务端接收到的数据内容:" + ContentData);
System.out.println("服务端接收到的数据数量:" + (++count));
String sendClientMessage = UUID.randomUUID().toString();
int sendClientMessageLength = sendClientMessage.getBytes("utf-8").length;
//每次收到客户端消息后,向客户端返回UUID字符串
CustomMessageData.MessageData message = CustomMessageData.MessageData.newBuilder()
.setLength(sendClientMessageLength)
.setOrder(CustomMessageData.MessageData.DataType.forNumber(1))
.setContent(CustomMessageData.MessageData.Content.newBuilder().setData(sendClientMessage)).build();
ctx.writeAndFlush(message);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
服务器Client端
客户端
public class MyClient {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyDecode());
pipeline.addLast(new MyEncode());
pipeline.addLast(new MyClientHandle());
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
channelFuture.channel().closeFuture().sync();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
客户端handler
public class MyClientHandle extends SimpleChannelInboundHandler<CustomMessageData.MessageData> {
int count;
/**
* 客户端和服务端建立连接后,向服务端发送消息
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for (int i = 0; i < 10; i++) {
String message = "message for client";
int length = message.getBytes().length;
MessageProtocol messageProtocol = new MessageProtocol();
messageProtocol.setLength(length);
messageProtocol.setContent(message);
//构建消息内容
CustomMessageData.MessageData messageData = CustomMessageData.MessageData.newBuilder()
.setLength(length)
.setOrder(CustomMessageData.MessageData.DataType.forNumber(1))
.setContent(CustomMessageData.MessageData.Content.newBuilder().setData(message)).build();
ctx.writeAndFlush(messageData);
}
}
/**
* 接受响应服务端消息
* @param ctx
* @param msg
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, CustomMessageData.MessageData msg) throws Exception {
long length = msg.getLength();
CustomMessageData.MessageData.DataType order = msg.getOrder();
String data = msg.getContent().getData();
System.out.println("客户端接收到的数据长度:" + length);
System.out.println("客户端接收到的数据指令:" + order);
System.out.println("客户端接收到的数据内容:" + data);
System.out.println("客户端接收到的数据数量:" + (++count));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
ctx.close();
}
}
注* .protoBuf生成的java文件未贴出。
流程:服务端启动后,监听客户端,当客户端启动完成后,客户端handler中的channelActive()方法会连续向服务器端发送10条消息。在传输过程中,先经过自定义的Encode编码器,服务器端接受到发送过来的消息时,再通过自定义解码器去解析消息。服务器向客户端返回应答消息(UUID字符串),同样是通过编码器将消息编码,客户端收到消息后再通过解码器将消息解析打印。