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

  +--------------------------------------------------+----------+
   |                消息头                         |     消息体 |
   | 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. Dynamics 365 目录

    Dynamics 365 目录 1.Dynamics 365 on-premises 安装 2.Dynamics 365 安装过程中的问题 2.1Dynamics 365 安装问题——无法访MSCRM ...

  2. Vue中的插槽---slot

    一:什么是插槽? 插槽(Slot)是Vue提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性. 插槽显不显示.怎样显示是由 ...

  3. Centos7 docker pull速度特别慢

    vim /etc/docker/daemon.json { "registry-mirrors" : ["https://docker.mirrors.ustc.edu. ...

  4. 洛谷 AT2434 JOI 公園 (JOI Park) 题解

    人生第一次AC黑题,我太感动了. 每日一题 day31 打卡 Analysis 先跑遍DJ,求出1到 i的最短路.得到每个点到 1号点的距离后,从小到大排序一遍,这时便可以枚举每个点到 1号点的距离修 ...

  5. mock模拟后台数据

    import Mock from 'mockjs' const Random = Mock.Random // 获取random对象,随机生成各种数据,具体请翻阅文档 const domain = ' ...

  6. shell脚本之文件操作

    文件测试运算符 文件测试运算符用于检测 Unix 文件的各种属性. 下面最常用的是 -f 参数 来检查是否是文件 #!/bin/bash #start nginx php-fpm list1=&quo ...

  7. $\text{fhq-treap}$总结

    \(\text{fhq-treap}\)总结 又名范浩强\(\text{treap}\),是一种无旋\(\text{treap}\).其原理同\(\text{treap}\)一样都是通过维护一个随机堆 ...

  8. 10.2.1.1-NAT+PAT综合详解

  9. Android中Popupwindow和Dialog的区别

    Android中的对话框有两种:PopupWindow和AlertDialog.它们都可以实现弹窗功能,但是他们之间有一些差别,下面总结了一点. (1)Popupwindow在显示之前一定要设置宽高, ...

  10. 第十一周助教工作总结——NWNU李泓毅

    助教博客链接:https://www.cnblogs.com/NWNU-LHY/ 本次作业的要求:原型设计:https://www.cnblogs.com/nwnu-daizh/p/10761596. ...