一般业务需求都会自行定义私有协议来满足自己的业务场景,私有协议也可以解决粘包和拆包问题,比如客户端发送数据时携带数据包长度,服务端接收数据后解析消息体,获取数据包长度值,据此继续获取数据包内容。我们来看具体例子,自定义的协议如下:

  +--------------------------------------------------+----------+
   |                消息头                         |     消息体 |
   | Delimiter | Length | Type | Reserved      |       data   |
   +-------------------------------------------------+----------+

  

  1)      Delimiter:4bytes,消息头,用于分割消息。

  2)      Length:数据长度。

  3)      Type:1bytes,消息类型。

  4)      Reserved:1bytes,保留。

  5)      Data包数据

  接下来看如何实现编解码:

  1、先定义好javabean:

  总体的:

package com.wlf.netty.nettyapi.javabean;

import lombok.Getter;
import lombok.Setter; @Setter
@Getter
public class NettyMessage { private Header header; private byte[] data; @Override
public String toString() {
return "NettyMessage{" +
"header=" + header +
", data=" + data +
'}';
}
}

  头的:

package com.wlf.netty.nettyapi.javabean;

import lombok.Getter;
import lombok.Setter; @Getter
@Setter
public class Header { /**
* 4bytes,消息头,用于分割消息。如0xABEF0101
*/
private int delimiter; /**
* 1byte,类型
*/
private byte type; /**
* 1byte,保留
*/
private byte reserved; /**
* 数据长度
*/
private int length; @Override
public String toString() {
return "Header{" +
"delimiter=" + delimiter +
", length=" + length +
", type=" + type +
", reserved=" + reserved +
'}';
}
}

  2、编码:

package com.wlf.netty.nettyapi.msgpack;

import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; public class NettyMessageEncoder extends MessageToByteEncoder<NettyMessage> { @Override
protected void encode(ChannelHandlerContext channelHandlerContext, NettyMessage nettyMessage, ByteBuf byteBuf) throws Exception { if (nettyMessage == null || nettyMessage.getHeader() == null) {
throw new Exception("The nettyMessage is null.");
} // 1、写入分割标志
byteBuf.writeInt(nettyMessage.getHeader().getDelimiter()); // 2、写入数据包长度
byteBuf.writeInt(nettyMessage.getData() != null ? nettyMessage.getData().length : 0); // 3、写入请求类型
byteBuf.writeByte(nettyMessage.getHeader().getType()); // 4、写入预留字段
byteBuf.writeByte(nettyMessage.getHeader().getReserved()); // 5、写入数据
byteBuf.writeBytes(nettyMessage.getData() != null ? nettyMessage.getData() : null); }
}

  3、解码:

package com.wlf.netty.nettyapi.msgpack;

import com.wlf.netty.nettyapi.constant.Delimiter;
import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import java.util.List; public class NettyMessageDecoder extends ByteToMessageDecoder { /**
* 消息体字节大小:分割符字段4字节+长度字段4字节+请求类型字段1字节+预留字段1字节=10字节
*/
private static final int HEAD_LENGTH = 10; @Override
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception { // 字节流开始位置
int packStartIndex;
while (true) { // 获取字节流开始位置
packStartIndex = byteBuf.readerIndex(); // 若读取到分割标识,说明读取当前字节流开始位置了
if (byteBuf.readInt() == Delimiter.DELIMITER) {
break;
} // 重置读索引为0
byteBuf.resetReaderIndex(); // 长度校验,字节流长度至少10字节,小于10字节则等待下一次字节流过来
if (byteBuf.readableBytes() < HEAD_LENGTH) {
return;
}
} // 2、获取data的字节流长度
int dataLength = byteBuf.readInt(); // 校验数据包是否全部发送过来,总字节流长度(此处读取的是除去delimiter和length之后的总长度)减去type和reserved两个字节=data的字节流长度
int totalLength = byteBuf.readableBytes();
if ((totalLength - 2) < dataLength) { // 长度校验,字节流长度少于data数据包长度,说明数据包拆包了,等待下一次字节流过来
byteBuf.readerIndex(packStartIndex);
return;
} // 3、请求类型
byte type = byteBuf.readByte(); // 4、预留字段
byte reserved = byteBuf.readByte(); // 5、数据包内容
byte[] data = null;
if (dataLength > 0) {
data = new byte[dataLength];
byteBuf.readBytes(data);
} NettyMessage nettyMessage = new NettyMessage();
Header header = new Header();
header.setDelimiter(0xABEF0101);
header.setLength(dataLength);
header.setType(type);
header.setReserved(reserved);
nettyMessage.setHeader(header);
nettyMessage.setData(data); list.add(nettyMessage); // 回收已读字节
byteBuf.discardReadBytes();
}
}

  为了运行,我们需要写客户端和服务端的handler:

  4、客户端handler:

package com.wlf.netty.nettyclient.handler;

import com.wlf.netty.nettyapi.constant.Delimiter;
import com.wlf.netty.nettyapi.constant.MessageType;
import com.wlf.netty.nettyapi.javabean.Header;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import com.wlf.netty.nettyapi.util.CommonUtil;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils; import java.io.RandomAccessFile;
import java.util.Arrays; /**
* 客户端处理类
*/
@Slf4j
public class NettyClientHandler extends ChannelHandlerAdapter { @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buildClientRequest());
} /**
* 创建请求消息体
*
* @return
*/
private NettyMessage buildClientRequest() {
NettyMessage nettyMessage = new NettyMessage();
Header header = new Header();
byte[] data = new byte[0];
try {
data = buildPcmData();
} catch (Exception e) {
e.printStackTrace();
}
header.setDelimiter(0xABEF0101);
header.setLength(data.length);
header.setType((byte) 1);
header.setReserved((byte) 0);
nettyMessage.setHeader(header); // 设置数据包
nettyMessage.setData(data);
return nettyMessage;
} /**
* 构造PCM请求消息体
*
* @return
*/
private byte[] buildPcmData() throws Exception {
byte[] resultByte = longToBytes(System.currentTimeMillis()); return resultByte;
} /**
* long转字节
*
* @param values
* @return
*/
private byte[] longToBytes(long values) {
byte[] buffer = new byte[8];
for (int i = 0; i < 8; i++) {
int offset = 64 - (i + 1) * 8;
buffer[i] = (byte) ((values >> offset) & 0xff);
}
return buffer;
} /**
* 将两个数组合并起来
*
* @param array1
* @param array2
* @return
*/
private byte[] addAll(byte[] array1, byte... array2) {
byte[] joinedArray = new byte[array1.length + array2.length];
System.arraycopy(array1, 0, joinedArray, 0, array1.length);
System.arraycopy(array2, 0, joinedArray, array1.length, array2.length);
return joinedArray;
} /**
* 在处理过程中引发异常时被调用
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("[Client] netty client request error: {}", cause.getMessage());
ctx.close();
} }

  5、服务端handler:

package com.wlf.netty.nettyserver.handler;

import com.alibaba.fastjson.JSON;
import com.wlf.netty.nettyapi.constant.MessageType;
import com.wlf.netty.nettyapi.javabean.NettyMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j; @Slf4j
public class NettyServerHandler extends ChannelHandlerAdapter { @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
NettyMessage nettyMessage = (NettyMessage) msg; if (nettyMessage.getHeader() != null && nettyMessage.getHeader().getType() == (byte) 1) {
log.info("[server] server receive client message : {}", nettyMessage);
if (nettyMessage == null || nettyMessage.getData() == null) {
log.error("nettyMessage is null.");
} // 获取时间戳(8字节)
byte[] data = nettyMessage.getData();
ByteBuf buf = Unpooled.buffer(data.length);
buf.writeBytes(data);
long startTime = buf.readLong();
log.info("data length: {}", data.length);
log.info("startTime: {}", startTime); }
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error("server received failed, error : {}", cause.getMessage());
cause.printStackTrace();
ctx.close();
} }

  最后,为了启动,我们还得再补两个启动类:

  6、客户端:

package com.wlf.netty.nettyclient.client;

import com.wlf.netty.nettyapi.msgpack.NettyMessageDecoder;
import com.wlf.netty.nettyapi.msgpack.NettyMessageEncoder;
import com.wlf.netty.nettyclient.handler.NettyClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j; import java.net.InetSocketAddress; /**
* 客户端
* 1.为初始化客户端,创建一个Bootstrap实例
* 2.为进行事件处理分配了一个NioEventLoopGroup实例,其中事件处理包括创建新的连接以及处理入站和出站数据;
* 3.当连接被建立时,一个NettyClientHandler实例会被安装到(该Channel的一个ChannelPipeline中;
* 4.在一切都设置完成后,调用Bootstrap.connect()方法连接到远程节点。
*/
@Slf4j
public class NettyClient { private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); EventLoopGroup group = new NioEventLoopGroup(); public void connect(int port, String host) throws Exception {
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new NettyMessageDecoder());
channel.pipeline().addLast(new NettyMessageEncoder());
channel.pipeline().addLast(new NettyClientHandler());
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().closeFuture().sync();
} finally {
workGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 9911;
new NettyClient().connect(port, "127.0.0.1");
}
}

  7、服务端启动类:

package com.wlf.netty.nettyserver.server;

import com.wlf.netty.nettyapi.msgpack.NettyMessageDecoder;
import com.wlf.netty.nettyapi.msgpack.NettyMessageEncoder;
import com.wlf.netty.nettyserver.handler.NettyServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;import lombok.extern.slf4j.Slf4j; @Slf4j
public class NettyServer { private final EventLoopGroup bossGroup = new NioEventLoopGroup();
private final EventLoopGroup workGroup = new NioEventLoopGroup(); public void bind(int port) throws Exception{
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast(new NettyMessageDecoder());
channel.pipeline().addLast(new NettyMessageEncoder());
channel.pipeline().addLast(new NettyServerHandler());
}
});
// 绑定端口
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 9911;
new NettyServer().bind(port);
} }

  直接跑上面两个启动类,先跑服务端,再跑客户端:

  客户端输出:

17:20:04.258 [nioEventLoopGroup-1-1] INFO com.wlf.netty.nettyclient.handler.NettyClientHandler - [client] client send data : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=1, reserved=0}, data=[B@432f82d5}

  服务端输出:

17:20:04.295 [nioEventLoopGroup-1-1] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - [server] server receive client message : NettyMessage{header=Header{delimiter=-1410399999, length=8, type=1, reserved=0}, data=[B@16eddbb3}
17:20:04.295 [nioEventLoopGroup-1-0] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - data length: 8
17:20:04.295 [nioEventLoopGroup-1-1] INFO com.wlf.netty.nettyserver.handler.NettyServerHandler - startTime: 1570785604258

  以上解码在字节流总容量小于1024时都没问题,但超过就会出现服务端获取不到数据的问题,详情和解决办法请参见netty5拆包问题解决实例

  

netty5自定义私有协议实例的更多相关文章

  1. 【Win 10开发】协议-上篇:自定义应用协议

    就像系统许多内置应用可以通过URI来启动(如ms-settings-bluetooth:可以打开蓝牙设置页),我们自己开发的应用程序,如果需要的话,可以为应用程序自定义一个协议.应用程序协议在安装时会 ...

  2. [转]Windows 注册自定义的协议

    [转自] http://blog.sina.com.cn/s/blog_86e4a51c01010nik.html 1.注册应用程序来处理自定义协议 你必须添加一个新的key以及相关的value到HK ...

  3. 自定义URL协议在Web中启动本地应用程序

    转自(http://blog.csdn.net/jackychen_king/article/details/7743811) 1.注册应用程序来处理自定义协议 你必须添加一个新的key以及相关的va ...

  4. Newtonsoft.Json 自定义 解析协议

    在开发web api的时候 遇到一个要把string未赋值默认为null的情况改成默认为空字符串的需求 这种情况就需要自定义json序列话的 解析协议了 Newtonsoft.Json默认的解析协议是 ...

  5. TeamTalk自定义IM协议的理解

    一.TeamTalk自定义IM协议 TeamTalk自定义IM协议是一种基于protocol buffer的消息传递协议,protocol buffer可以自定义消息格式.protocol buffe ...

  6. 通过私有协议Chrome浏览器页面打开本地程序

    近期方有这样的要求:这两个系统,根据一组Chrome开展,根据一组IE开展,需要Chrome添加一个链接,然后进入IE该系统的开发.这,需要Chrome跳转到创建一个链接IE浏览器指定的页面.同时也实 ...

  7. 真正实现Netty私有协议开发

    首先<Netty权威指南>私有协议开发那一章的样例代码是编译不通过的(但是这丝毫不影响本书的价值)处理方案可以参考:http://www.itnose.net/detail/6112870 ...

  8. JS中new的自定义实现创建实例对象

    我们都知道在JS中通常通过对象字面量和new关键字来创建对象,那么今天我就来给大家讲讲new是怎么创建实例对象的:首先创建一个构造函数: function Person(name,age){ this ...

  9. java学习--自定义类的实例的大小比较和排序

    我们知道Object类有一个equals方法,用于比较两个对象是否相等 我们只要在自定义类中重写了equals方法(若不重写就是比较两个实例的地址,相当于==)就可以用来比较该类的两个实例是否相等 问 ...

随机推荐

  1. vscode——tab转空格

    前言 为了规范写法,开启了eslint,但是tab没设置转空格,这里记录下设置过程. 步骤 进入设置并搜索tab 配置设置 复制相应的设置 写入到json文件中 "editor.detect ...

  2. Java中装箱和拆箱的代码

    建议使用1.5或以上的jdk运行, //装箱  值类型到引用类型  int i = 10;  Object object =i;  System.out.println(object);      / ...

  3. grep命令选项

    -c:只输出匹配行的计数. -I:不区分大 小写(只适用于单字符). -h:查询多文件时不显示文件名. -l:查询多文件时只输出包含匹配字符的文件名. -n:显示匹配行及 行号. -s:不显示不存在或 ...

  4. 转发标签forward

    当执行到<jsp:forward page="相对路径"></jsp:forward>后,会立即结束当前页面的显示,跳转到另一个页面(JSP.HTML.Se ...

  5. 如何在Eclipse中写Processing的sketch

    有时候人们需要写更复杂的sketch,此时Processing提供的IDE就略显单薄,下面将介绍如何在eclipse中开发Processing. 一共分4步: 一.搭建环境:安装JRE.JDK.Ecl ...

  6. pcl-设置多线段宽度和颜色

    显示点云有使用vtk的,有使用 ros 中riz ?库的,使用pcl显示点云数据比较方便,但是对于一些模型形状只能固定特定的效果,比如说直线段,只能绘制点到点两点之间的线段.但是项目需要绘制点1到点2 ...

  7. 【概率论】6-3:中心极限定理(The Central Limit Theorem)

    title: [概率论]6-3:中心极限定理(The Central Limit Theorem) categories: - Mathematic - Probability keywords: - ...

  8. Deepin-TIM或Deepin-QQ调整界面DPI字体大小的方法

    Deepin-TIM或Deepin-QQ调整界面DPI字体大小的方法 env WINEPREFIX="/home/landv/.deepinwine/Deepin-QQ" deep ...

  9. Three.js实现滚轮放大展现不同的模型

    目录 Three.js实现滚轮放大展现不同的模型 修改OrbitControls.js的源码 OrbitControls在透视相机(PerspectiveCamera)的控制原理 具体实现 Three ...

  10. 第12组 Alpha冲刺(3/6)

    Header 队名:To Be Done 组长博客 作业博客 团队项目进行情况 燃尽图(组内共享) 展示Git当日代码/文档签入记录(组内共享) 注: 由于GitHub的免费范围内对多人开发存在较多限 ...