【转】Netty之解决TCP粘包拆包(自定义协议)
1、什么是粘包/拆包
一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。
2、解决办法
2.1、消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定,这样即使粘包了通过接收方编程实现获取定长报文也能区分。
2.2、包尾添加特殊分隔符,例如每条报文结束都添加回车换行符(例如FTP协议)或者指定特殊字符作为报文分隔符,接收方通过特殊分隔符切分报文区分。
2.3、将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段
3、自定义协议,来实现TCP的粘包/拆包问题
3.0 自定义协议,开始标记
3.1 自定义协议的介绍
3.2 自定义协议的类的封装
3.3 自定义协议的编码器
3.4 自定义协议的解码器
4、协议相关的实现
4.1 协议的封装
- import java.util.Arrays;
- /**
- * <pre>
- * 自己定义的协议
- * 数据包格式
- * +——----——+——-----——+——----——+
- * |协议开始标志| 长度 | 数据 |
- * +——----——+——-----——+——----——+
- * 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
- * 2.传输数据的长度contentLength,int类型
- * 3.要传输的数据
- * </pre>
- */
- public class SmartCarProtocol {
- /**
- * 消息的开头的信息标志
- */
- private int head_data = ConstantValue.HEAD_DATA;
- /**
- * 消息的长度
- */
- private int contentLength;
- /**
- * 消息的内容
- */
- private byte[] content;
- /**
- * 用于初始化,SmartCarProtocol
- *
- * @param contentLength
- * 协议里面,消息数据的长度
- * @param content
- * 协议里面,消息的数据
- */
- public SmartCarProtocol(int contentLength, byte[] content) {
- this.contentLength = contentLength;
- this.content = content;
- }
- public int getHead_data() {
- return head_data;
- }
- public int getContentLength() {
- return contentLength;
- }
- public void setContentLength(int contentLength) {
- this.contentLength = contentLength;
- }
- public byte[] getContent() {
- return content;
- }
- public void setContent(byte[] content) {
- this.content = content;
- }
- @Override
- public String toString() {
- return "SmartCarProtocol [head_data=" + head_data + ", contentLength="
- + contentLength + ", content=" + Arrays.toString(content) + "]";
- }
- }
4.2 协议的编码器
- /**
- * <pre>
- * 自己定义的协议
- * 数据包格式
- * +——----——+——-----——+——----——+
- * |协议开始标志| 长度 | 数据 |
- * +——----——+——-----——+——----——+
- * 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
- * 2.传输数据的长度contentLength,int类型
- * 3.要传输的数据
- * </pre>
- */
- public class SmartCarEncoder extends MessageToByteEncoder<SmartCarProtocol> {
- @Override
- protected void encode(ChannelHandlerContext tcx, SmartCarProtocol msg,
- ByteBuf out) throws Exception {
- // 写入消息SmartCar的具体内容
- // 1.写入消息的开头的信息标志(int类型)
- out.writeInt(msg.getHead_data());
- // 2.写入消息的长度(int 类型)
- out.writeInt(msg.getContentLength());
- // 3.写入消息的内容(byte[]类型)
- out.writeBytes(msg.getContent());
- }
- }
4.3 协议的解码器
- import java.util.List;
- import io.netty.buffer.ByteBuf;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.handler.codec.ByteToMessageDecoder;
- /**
- * <pre>
- * 自己定义的协议
- * 数据包格式
- * +——----——+——-----——+——----——+
- * |协议开始标志| 长度 | 数据 |
- * +——----——+——-----——+——----——+
- * 1.协议开始标志head_data,为int类型的数据,16进制表示为0X76
- * 2.传输数据的长度contentLength,int类型
- * 3.要传输的数据,长度不应该超过2048,防止socket流的攻击
- * </pre>
- */
- public class SmartCarDecoder extends ByteToMessageDecoder {
- /**
- * <pre>
- * 协议开始的标准head_data,int类型,占据4个字节.
- * 表示数据的长度contentLength,int类型,占据4个字节.
- * </pre>
- */
- public final int BASE_LENGTH = 4 + 4;
- @Override
- protected void decode(ChannelHandlerContext ctx, ByteBuf buffer,
- List<Object> out) throws Exception {
- // 可读长度必须大于基本长度
- if (buffer.readableBytes() >= BASE_LENGTH) {
- // 防止socket字节流攻击
- // 防止,客户端传来的数据过大
- // 因为,太大的数据,是不合理的
- if (buffer.readableBytes() > 2048) {
- buffer.skipBytes(buffer.readableBytes());
- }
- // 记录包头开始的index
- int beginReader;
- while (true) {
- // 获取包头开始的index
- beginReader = buffer.readerIndex();
- // 标记包头开始的index
- buffer.markReaderIndex();
- // 读到了协议的开始标志,结束while循环
- if (buffer.readInt() == ConstantValue.HEAD_DATA) {
- break;
- }
- // 未读到包头,略过一个字节
- // 每次略过,一个字节,去读取,包头信息的开始标记
- buffer.resetReaderIndex();
- buffer.readByte();
- // 当略过,一个字节之后,
- // 数据包的长度,又变得不满足
- // 此时,应该结束。等待后面的数据到达
- if (buffer.readableBytes() < BASE_LENGTH) {
- return;
- }
- }
- // 消息的长度
- int length = buffer.readInt();
- // 判断请求数据包数据是否到齐
- if (buffer.readableBytes() < length) {
- // 还原读指针
- buffer.readerIndex(beginReader);
- return;
- }
- // 读取data数据
- byte[] data = new byte[length];
- buffer.readBytes(data);
- SmartCarProtocol protocol = new SmartCarProtocol(data.length, data);
- out.add(protocol);
- }
- }
- }
4.4 服务端加入协议的编/解码器
4.5 客户端加入协议的编/解码器
5、服务端的实现
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- 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;
- public class Server {
- public Server() {
- }
- public void bind(int port) throws Exception {
- // 配置NIO线程组
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- try {
- // 服务器辅助启动类配置
- ServerBootstrap b = new ServerBootstrap();
- b.group(bossGroup, workerGroup)
- .channel(NioServerSocketChannel.class)
- .handler(new LoggingHandler(LogLevel.INFO))
- .childHandler(new ChildChannelHandler())//
- .option(ChannelOption.SO_BACKLOG, 1024) // 设置tcp缓冲区 // (5)
- .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
- // 绑定端口 同步等待绑定成功
- ChannelFuture f = b.bind(port).sync(); // (7)
- // 等到服务端监听端口关闭
- f.channel().closeFuture().sync();
- } finally {
- // 优雅释放线程资源
- workerGroup.shutdownGracefully();
- bossGroup.shutdownGracefully();
- }
- }
- /**
- * 网络事件处理器
- */
- private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- // 添加自定义协议的编解码工具
- ch.pipeline().addLast(new SmartCarEncoder());
- ch.pipeline().addLast(new SmartCarDecoder());
- // 处理网络IO
- ch.pipeline().addLast(new ServerHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new Server().bind(9999);
- }
- }
6、服务端Handler的实现
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- public class ServerHandler extends ChannelHandlerAdapter {
- // 用于获取客户端发送的信息
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg)
- throws Exception {
- // 用于获取客户端发来的数据信息
- SmartCarProtocol body = (SmartCarProtocol) msg;
- System.out.println("Server接受的客户端的信息 :" + body.toString());
- // 会写数据给客户端
- String str = "Hi I am Server ...";
- SmartCarProtocol response = new SmartCarProtocol(str.getBytes().length,
- str.getBytes());
- // 当服务端完成写操作后,关闭与客户端的连接
- ctx.writeAndFlush(response);
- // .addListener(ChannelFutureListener.CLOSE);
- // 当有写操作时,不需要手动释放msg的引用
- // 当只有读操作时,才需要手动释放msg的引用
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
- throws Exception {
- // cause.printStackTrace();
- ctx.close();
- }
- }
7、客户端的实现
- import io.netty.bootstrap.Bootstrap;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.ChannelOption;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.SocketChannel;
- import io.netty.channel.socket.nio.NioSocketChannel;
- public class Client {
- /**
- * 连接服务器
- *
- * @param port
- * @param host
- * @throws Exception
- */
- public void connect(int port, String host) throws Exception {
- // 配置客户端NIO线程组
- EventLoopGroup group = new NioEventLoopGroup();
- try {
- // 客户端辅助启动类 对客户端配置
- Bootstrap b = new Bootstrap();
- b.group(group)//
- .channel(NioSocketChannel.class)//
- .option(ChannelOption.TCP_NODELAY, true)//
- .handler(new MyChannelHandler());//
- // 异步链接服务器 同步等待链接成功
- ChannelFuture f = b.connect(host, port).sync();
- // 等待链接关闭
- f.channel().closeFuture().sync();
- } finally {
- group.shutdownGracefully();
- System.out.println("客户端优雅的释放了线程资源...");
- }
- }
- /**
- * 网络事件处理器
- */
- private class MyChannelHandler extends ChannelInitializer<SocketChannel> {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- // 添加自定义协议的编解码工具
- ch.pipeline().addLast(new SmartCarEncoder());
- ch.pipeline().addLast(new SmartCarDecoder());
- // 处理网络IO
- ch.pipeline().addLast(new ClientHandler());
- }
- }
- public static void main(String[] args) throws Exception {
- new Client().connect(9999, "127.0.0.1");
- }
- }
8、客户端Handler的实现
- import io.netty.channel.ChannelHandlerAdapter;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.util.ReferenceCountUtil;
- //用于读取客户端发来的信息
- public class ClientHandler extends ChannelHandlerAdapter {
- // 客户端与服务端,连接成功的售后
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- // 发送SmartCar协议的消息
- // 要发送的信息
- String data = "I am client ...";
- // 获得要发送信息的字节数组
- byte[] content = data.getBytes();
- // 要发送信息的长度
- int contentLength = content.length;
- SmartCarProtocol protocol = new SmartCarProtocol(contentLength, content);
- ctx.writeAndFlush(protocol);
- }
- // 只是读数据,没有写数据的话
- // 需要自己手动的释放的消息
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object msg)
- throws Exception {
- try {
- // 用于获取客户端发来的数据信息
- SmartCarProtocol body = (SmartCarProtocol) msg;
- System.out.println("Client接受的客户端的信息 :" + body.toString());
- } finally {
- ReferenceCountUtil.release(msg);
- }
- }
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
- throws Exception {
- ctx.close();
- }
- }
9、参考的博客地址
- http://www.cnblogs.com/whthomas/p/netty-custom-protocol.html
- http://www.cnblogs.com/fanguangdexiaoyuer/p/6131042.html
【转】Netty之解决TCP粘包拆包(自定义协议)的更多相关文章
- Netty之解决TCP粘包拆包(自定义协议)
1.什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消 ...
- 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?
前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...
- Netty使用LineBasedFrameDecoder解决TCP粘包/拆包
TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...
- 《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)
一.粘包/拆包概念 TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认 ...
- 1. Netty解决Tcp粘包拆包
一. TCP粘包问题 实际发送的消息, 可能会被TCP拆分成很多数据包发送, 也可能把很多消息组合成一个数据包发送 粘包拆包发生的原因 (1) 应用程序一次写的字节大小超过socket发送缓冲区大小 ...
- 《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)
一.Netty粘包和拆包解决方案 Netty提供了多个解码器,可以进行分包的操作,分别是: * LineBasedFrameDecoder (换行) LineBasedFrameDecoder是回 ...
- Netty解决TCP粘包/拆包问题 - 按行分隔字符串解码器
服务端 package org.zln.netty.five.timer; import io.netty.bootstrap.ServerBootstrap; import io.netty.cha ...
- Netty(三)TCP粘包拆包处理
tcp是一个“流”的协议,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 粘包.拆包问题说明 假设客户端分别发送数据包D1和D ...
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
随机推荐
- intellij idea 破解教程
首先呼吁:抵制盗版,抵制盗版,抵制盗版 如果只是个人开发学习用,那么下面的教程可能比较适合你了 有两种方法,第一种:Activate--License server,在License server a ...
- Tomat和JDK安装配置
昨天重装了一下系统,升级成了win10,原先配置好的jdk和tomcat信息都没有了,所以重新配置一下,做一个小记录 首先去官网下载java JDK和Tomcat,这个不提了,下载之后安装,安装完毕后 ...
- python中网络编程基础
一:什么是c\s架构 1.c\s即client\server 客户端\服务端架构. 客户端因特定的请求而联系服务器并发送必要的数据等待服务器的回应最后完成请求 服务端:存在的意义就是等待客户端的请求, ...
- Oracle(限定查询2)
3.2 对数据进行限定查询 在标准SQL之中定义了许多的运算符. 3.2.1.关系运算符 范例: 范例: 范例: 在使用关系运算符判断字符数据的时候注意大小写的编写问题.因为Oracle是区分大小写的 ...
- SuperMap 二维地图和三维场景弹窗窗口大小控制
注:此处所说的弹窗窗口,主要指的是那些弹窗窗口中嵌入iframe,包含信息页面的窗口大小控制. 1.首先来了解下 SuperMap 示例中的处理方案 二维的处理方式 //初始化Anchored类 po ...
- Unity --- 纹理压缩基本知识点
1.Unity支持的压缩格式的分类,这里主要指Android平台和IOS平台: DXT格式 --- Nvidia Tegra(图睿)提供ETC --- 安卓原生支持的,OPNEGL2.0都支持,ET ...
- 输出图片格式BARTENDER
try { BarTender.Application btApp = new BarTender.Application(); BarTe ...
- nginx ----> nginx配置/反向代理/负载均衡
nginx [engine x]是一个HTTP和反向代理服务器,一个邮件代理服务器和一个通用的TCP/UDP代理服务器,最初由Igor Sysoev编写. 环境: Ubuntu16.04 安装ngin ...
- 【源码分析】Mybatis使用中,同一个事物里,select查询不出之前insert的数据
一.问题场景模拟问题:第二次查询和第一次查询结果一模一样,没有查询出我新插入的数据 猜测:第二次查询走了Mybatis缓存 疑问:那为什么会走缓存呢? 1.service方法 @Override @T ...
- java.lang.NoClassDefFoundError: org/springframework/web/context/WebApplicationContext
一.NoClassDefFoundError与ClassNotFoundException NoClassDefFoundError错误的发生,是因为Java虚拟机在编译时能找到合适的类,而在运行时不 ...