《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)
一、Netty粘包和拆包解决方案
Netty提供了多个解码器,可以进行分包的操作,分别是:
* LineBasedFrameDecoder (换行)
LineBasedFrameDecoder是回车换行解码器,如果用户发送的消息以回车换行符作为消息结束的标识,则可以直接使用Netty的LineBasedFrameDecoder对消息进行解码,只需要在初始化Netty服务端或者客户端时将LineBasedFrameDecoder正确的添加到ChannelPipeline中即可,不需要自己重新实现一套换行解码器。
LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有“\n”或者“\r\n”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。防止由于数据报没有携带换行符导致接收到ByteBuf无限制积压,引起系统内存溢出。
* DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包)
DelimiterBasedFrameDecoder是分隔符解码器,用户可以指定消息结束的分隔符,它可以自动完成以分隔符作为码流结束标识的消息的解码。
回车换行解码器实际上是一种特殊的DelimiterBasedFrameDecoder解码器。
* FixedLengthFrameDecoder(使用定长的报文来分包)
FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包/拆包等问题,非常实用。
对于定长消息,如果消息实际长度小于定长,则往往会进行补位操作,它在一定程度上导致了空间和资源的浪费。但是它的优点也是非常明显的,编解码比较简单,因此在实际项目中仍然有一定的应用场景。
* LengthFieldBasedFrameDecoder (自定义解码器跟编码器)
本文介绍的重点LengthFieldBasedFrameDecoder,一般包含了消息头(head)、消息体(body):消息头是固定的长度,一般有有以下信息 -> 是否压缩(zip)、消息类型(type or cmdid)、消息体长度(body length);消息体长度不是固定的,其大小由消息头记载,一般记载业务交互信息。
netty对应来说就是编码器(Encoder)跟解码器(Decoder),一般其中会有一个基本消息类对外输出
二、实例演示
首先编写自定义协议类:
package com.spring.netty.handler2; /**
* 自定义Person协议
*/
public class PersonProtocol {
private int length;
private byte[] content; public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public byte[] getContent() {
return content;
}
public void setContent(byte[] content) {
this.content = content;
}
}
新建服务端代码:
package com.spring.netty.handler2; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; public class MyServer {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(); try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).
childHandler(new MyServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package com.spring.netty.handler2; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new MyPersonDecoder());
pipeline.addLast(new MyPersonEncoder()); pipeline.addLast(new MyServerHandler());
}
}
package com.spring.netty.handler2; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ReplayingDecoder; import java.util.List; /**
* Person解码器
*/
public class MyPersonDecoder extends ReplayingDecoder<Void> {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
System.out.println("MyPersonDecoder decode invoked!"); int length = in.readInt();
byte[] content = new byte[length];
in.readBytes(content); PersonProtocol personProtocol = new PersonProtocol();
personProtocol.setLength(length);
personProtocol.setContent(content);
out.add(personProtocol);
}
}
package com.spring.netty.handler2; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; /**
* 编码器
*/
public class MyPersonEncoder extends MessageToByteEncoder<PersonProtocol> {
@Override
protected void encode(ChannelHandlerContext ctx, PersonProtocol msg, ByteBuf out) throws Exception {
System.out.println("MyPersonEncoder encode invoked!"); out.writeInt(msg.getLength());
out.writeBytes(msg.getContent());
}
}
package com.spring.netty.handler2; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.Charset;
import java.util.UUID; public class MyServerHandler extends SimpleChannelInboundHandler<PersonProtocol> {
private int count;
@Override
protected void channelRead0(ChannelHandlerContext ctx, PersonProtocol msg) throws Exception {
int length = msg.getLength();
byte[] content = msg.getContent();
System.out.println("服务端接收到的数据:");
System.out.println("长度:"+length);
System.out.println("内容:"+new String(content, Charset.forName("utf-8")));
System.out.println("服务端接收到的消息数量:"+(++this.count));
//服务端向客户端返回uuid
String responseMessage = UUID.randomUUID().toString();
int responseLength = responseMessage.getBytes("utf-8").length;
byte[] responseContent = responseMessage.getBytes("utf-8"); PersonProtocol personProtocol = new PersonProtocol();
personProtocol.setLength(length);
personProtocol.setContent(content);
ctx.writeAndFlush(responseContent);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
编写客户端程序:
package com.spring.netty.handler2; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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 MyClient {
public static void main(String[] args) throws Exception {
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 MyPersonDecoder());
pipeline.addLast(new MyPersonEncoder()); pipeline.addLast(new MyClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("localhost",8899).sync();
channelFuture.channel().closeFuture().sync();
}finally {
eventLoopGroup.shutdownGracefully();
}
}
}
package com.spring.netty.handler2; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import java.nio.charset.Charset; public class MyClientHandler extends SimpleChannelInboundHandler<PersonProtocol> {
private int count; @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
for(int i=0;i<10;++i){
String messageToBeSent = "sent from client";
byte[] content = messageToBeSent.getBytes(Charset.forName("utf-8"));
int length = messageToBeSent.getBytes(Charset.forName("utf-8")).length; PersonProtocol personProtocol = new PersonProtocol();
personProtocol.setLength(length);
personProtocol.setContent(content); ctx.writeAndFlush(personProtocol);
}
} //从服务器端接收数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, PersonProtocol msg) throws Exception {
int length = msg.getLength();
byte[] content = msg.getContent();
System.out.println("客户端接收到的数据:");
System.out.println("长度:"+length);
System.out.println("内容:"+new String(content, Charset.forName("utf-8")));
System.out.println("客户端接收到的消息数量:"+(++this.count));
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
分别启动服务端和客户端查看效果:
服务端效果如图:

客户端效果如图:

《精通并发与Netty》学习笔记(14 - 解决TCP粘包拆包(二)Netty自定义协议解决粘包拆包)的更多相关文章
- SAS学习笔记14 利用SAS绘制地图(二)
笔记9讲过利用SAS绘制地图,这次接着讲 用中国各地(不含港澳台)的平均湿度数据来绘制地图 在地图上标出地名 宏%maplabel有9个参数,依次为:地图文件名.包含区域名称的数据集文件.输出的注释数 ...
- 并发编程学习笔记(14)----ThreadPoolExecutor(线程池)的使用及原理
1. 概述 1.1 什么是线程池 与jdbc连接池类似,在创建线程池或销毁线程时,会消耗大量的系统资源,因此在java中提出了线程池的概念,预先创建好固定数量的线程,当有任务需要线程去执行时,不用再去 ...
- Netty学习笔记-入门版
目录 Netty学习笔记 前言 什么是Netty IO基础 概念说明 IO简单介绍 用户空间与内核空间 进程(Process) 线程(thread) 程序和进程 进程切换 进程阻塞 文件描述符 文件句 ...
- SQL反模式学习笔记14 关于Null值的使用
目标:辨别并使用Null值 反模式:将Null值作为普通的值,反之亦然 1.在表达式中使用Null: Null值与空字符串是不一样的,Null值参与任何的加.减.乘.除等其他运算,结果都是Null: ...
- Ext.Net学习笔记14:Ext.Net GridPanel Grouping用法
Ext.Net学习笔记14:Ext.Net GridPanel Grouping用法 Ext.Net GridPanel可以进行Group操作,例如: 如何启用Grouping功能呢?只需要在Grid ...
- Netty学习笔记(二) 实现服务端和客户端
在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...
- Netty 学习笔记(1)通信原理
前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始. Netty 的通信原理 Netty 底层 ...
- golang学习笔记14 golang substring 截取字符串
golang学习笔记14 golang substring 截取字符串golang 没有java那样的substring函数,但支持直接根据 index 截取字符串mystr := "hel ...
- mybatis学习笔记(14)-查询缓存之中的一个级缓存
mybatis学习笔记(14)-查询缓存之中的一个级缓存 标签: mybatis mybatis学习笔记14-查询缓存之中的一个级缓存 查询缓存 一级缓存 一级缓存工作原理 一级缓存測试 一级缓存应用 ...
- Python3+Selenium3+webdriver学习笔记14(等待判断 鼠标事件 )
!/usr/bin/env python -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记14(等待判断 鼠标事件 )'''from selenium im ...
随机推荐
- JavaScript设置和获取cookie
创建 //创建cookie function setCookie(name, value, expires, path, domain, secure) { var cookieText = enco ...
- 2019牛客暑期多校训练营(第五场)F maximum clique 1 二分图求最大独立集
https://ac.nowcoder.com/acm/contest/885/F #include <bits/stdc++.h> //CLOCKS_PER_SEC #define se ...
- redis使用例子
package test.iafclub.redis; import java.util.ArrayList; import java.util.HashMap; import java.util.I ...
- PHP mysqli_error() 函数
返回最近调用函数的最后一个错误描述: <?php // 假定数据库用户名:root,密码:123456,数据库:RUNOOB $con=mysqli_connect("localh ...
- 021_STM32程序移植之_ESP8266连接onenet
本次教程是使用STM32C8T6通过ESP8266-12F模块将数据传输到ONENET云端去,并且云端能够下发命令给单片机来实现云端控制.本次实验硬件设备:STM32C8T6最小系统,ESP8266- ...
- GitHub 更新fork的代码
转载地址:http://blog.csdn.net/do_it__/article/details/7836513 一.前提 本文的前提是你已经在github上fork了别人的分支,并且弄好了跟git ...
- mkfs/mk2fs/fsck/e2fsck/tune2fs/blkid
mkfs 格式化创建Linux文件系统的工具 centos 6 使用xfs文件系统 fsck 检测及修复文件系统的工具 当磁盘出现逻辑错误的时候,可以尝试修复. fsck -t ext2|ext3 / ...
- sizeof +数组名
链接:https://www.nowcoder.com/questionTerminal/daa5422cb468473c9e6e75cc98b771de 来源:牛客网 sizeof一个数组名称的时候 ...
- 3-2新建Photoshop图像
http://www.missyuan.com/thread-350740-1-1.html [CTRL N][文件 新建] 按住CTRL双击Photoshop的空白区(这个好像是打开文件){快捷 ...
- Beyond Compare4激活码(版本 4.2.8)
w4G-in5u3SH75RoB3VZIX8htiZgw4ELilwvPcHAIQWfwfXv5n0IHDp5hv 1BM3+H1XygMtiE0-JBgacjE9tz33sIh542EmsGs1yg ...