前言

上一篇Netty 心跳 demo 中,了解了Netty中的客户端和服务端之间的心跳。这篇就来讲讲Netty中的粘包和拆包以及相应的处理。

名词解释

粘包: 会将消息粘粘起来发送。类似吃米饭,一口吃多个饭粒,而不是一粒一粒的吃。
拆包: 会将消息拆开,分为多次接受。类似喝饮料,一口一口的喝,而不是一口气喝完。

简单的来说:
多次发送较少内容,会发生粘包现象。
单次发送内容过多,会发生拆包现象。

我们使用简单的Netty的服务端和客户端demo用来测试粘包和拆包。
Hello Netty 发送一百次,就会发送粘包现象;
将《春江花月夜》和《行路难》发送一次就会发送拆包现象;

示例图:

粘包:

拆包:

解决粘包、拆包

因为Netty已经提供了几个常用的解码器,帮助我们解决这些问题,所以我们不必再去造轮子了,直接拿来用就好了。

解决粘包

在Server服务端使用定长数据帧的解码器 FixedLengthFrameDecoder之后。
可以明显看到数据已经按照我们所设定的大小分割了。

解决拆包

在Server服务端使用字节解码器 LineBasedFrameDecoder 之后。
由于字节已经超过我们设置的最大的字节数,所以报错了。

所以,我们发送的字节在设置的范围内的话,就可以看到拆包现象已经解决。

Netty还提供了一个HttpObjectAggregator类用于解决粘包、拆包现象。
以下摘自Netty官方文档

如果对于单条HTTP消息你不想处理多个消息对象,你可以传入 HttpObjectAggregator 到pipline中。HttpObjectAggregator 会将多个消息对象转变为单个 FullHttpRequest 或者 FullHttpResponse。

使用HttpObjectAggregator 之后

可以看到,粘包和拆包现象得到了改善。

那么开始贴代码,几乎和之前的demo一样。

服务端:

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; /**
*
* Title: NettyServer
* Description: Netty服务端
* Version:1.0.0
* @author pancm
* @date 2017年10月8日
*/
public class NettyServer {
private static final int port = 6789; //设置服务端端口
private static EventLoopGroup group = new NioEventLoopGroup(); // 通过nio方式来接收连接和处理连接
private static ServerBootstrap b = new ServerBootstrap(); /**
* Netty创建全部都是实现自AbstractBootstrap。
* 客户端的是Bootstrap,服务端的则是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException {
try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new NettyServerFilter()); //设置过滤器
// 服务器绑定端口监听
ChannelFuture f = b.bind(port).sync();
System.out.println("服务端启动成功,端口是:"+port);
// 监听服务器关闭监听
f.channel().closeFuture().sync();
}catch(Exception e){
e.printStackTrace();
}
finally {
group.shutdownGracefully(); //关闭EventLoopGroup,释放掉所有资源包括创建的线程
}
}
}
mport io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; /**
*
* Title: HelloServerInitializer
* Description: Netty 服务端过滤器
* Version:1.0.0
* @author pancm
* @date 2017年10月8日
*/
public class NettyServerFilter extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
// 解码和编码,应和客户端一致
// ph.addLast(new FixedLengthFrameDecoder(100)); //定长数据帧的解码器 ,每帧数据100个字节就切分一次。 用于解决粘包问题
// ph.addLast(new LineBasedFrameDecoder(2048)); //字节解码器 ,其中2048是规定一行数据最大的字节数。 用于解决拆包问题
ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024));
ph.addLast("decoder", new StringDecoder());
ph.addLast("encoder", new StringEncoder());
ph.addLast("handler", new NettyServerHandler());// 服务端业务逻辑
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import java.net.InetAddress; /**
*
* Title: HelloServerHandler
* Description: 服务端业务逻辑 粘包、拆包测试
* Version:1.0.0
* @author pancm
* @date 2017年10月8日
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/** 条数 */
private int count=0;
/**
* 业务逻辑处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String)msg;
System.out.println("接受的数据是: " + body + ";条数是: " + ++count);
} /**
* 建立连接时,返回消息
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接的客户端地址:" + ctx.channel().remoteAddress());
ctx.writeAndFlush("客户端"+ InetAddress.getLocalHost().getHostName() + "成功与服务端建立连接! ");
super.channelActive(ctx);
}
}

客户端

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel; import java.io.IOException;
/**
*
* Title: NettyClient
* Description: Netty客户端 粘包、拆包测试
* Version:1.0.0
* @author pancm
* @date 2017年10月16日
*/
public class NettyClient { public static String host = "127.0.0.1"; //ip地址
public static int port = 6789; //端口
/// 通过nio方式来接收连接和处理连接
private static EventLoopGroup group = new NioEventLoopGroup();
private static Bootstrap b = new Bootstrap();
private static Channel ch; /**
* Netty创建全部都是实现自AbstractBootstrap。
* 客户端的是Bootstrap,服务端的则是 ServerBootstrap。
**/
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("客户端成功启动...");
b.group(group);
b.channel(NioSocketChannel.class);
b.handler(new NettyClientFilter());
// 连接服务端
ch = b.connect(host, port).sync().channel();
star(3);
} public static void star(int i) throws IOException{
String str="春江潮水连海平,海上明月共潮生。"
+" 滟滟随波千万里,何处春江无月明! "
+" 江流宛转绕芳甸,月照花林皆似霰;"
+" 空里流霜不觉飞,汀上白沙看不见。"
+" 江天一色无纤尘,皎皎空中孤月轮。"
+" 江畔何人初见月?江月何年初照人?"
+" 人生代代无穷已,江月年年望相似。"
+" 不知江月待何人,但见长江送流水。"
+" 白云一片去悠悠,青枫浦上不胜愁。"
+" 谁家今夜扁舟子?何处相思明月楼?"
+" 可怜楼上月徘徊,应照离人妆镜台。"
+" 玉户帘中卷不去,捣衣砧上拂还来。"
+" 此时相望不相闻,愿逐月华流照君。"
+" 鸿雁长飞光不度,鱼龙潜跃水成文。"
+" 昨夜闲潭梦落花,可怜春半不还家。"
+" 江水流春去欲尽,江潭落月复西斜。"
+" 斜月沉沉藏海雾,碣石潇湘无限路。"
+" 不知乘月几人归,落月摇情满江树。"
+" 噫吁嚱,危乎高哉!蜀道之难,难于上青天!蚕丛及鱼凫,开国何茫然!尔来四万八千岁,不与秦塞通人烟。"
+" 西当太白有鸟道,可以横绝峨眉巅。地崩山摧壮士死,然后天梯石栈相钩连。上有六龙回日之高标,下有冲波逆折之回川。"
+" 黄鹤之飞尚不得过,猿猱欲度愁攀援。青泥何盘盘,百步九折萦岩峦。扪参历井仰胁息,以手抚膺坐长叹。"
+" 问君西游何时还?畏途巉岩不可攀。但见悲鸟号古木,雄飞雌从绕林间。又闻子规啼夜月,愁空山。"
+" 蜀道之难,难于上青天,使人听此凋朱颜!连峰去天不盈尺,枯松倒挂倚绝壁。飞湍瀑流争喧豗,砯崖转石万壑雷。"
+" 其险也如此,嗟尔远道之人胡为乎来哉!剑阁峥嵘而崔嵬,一夫当关,万夫莫开。"
+" 所守或匪亲,化为狼与豺。朝避猛虎,夕避长蛇;磨牙吮血,杀人如麻。锦城虽云乐,不如早还家。"
+" 蜀道之难,难于上青天,侧身西望长咨嗟!";
if(i==1){
for(int j=0;j<100;j++){
str="Hello Netty";
ch.writeAndFlush(str);
}
}else if(i==2){
str+=str;
ch.writeAndFlush(str);
}else if(i==3){
//System.getProperty("line.separator") 结束标记
byte [] bt=(str+System.getProperty("line.separator")).getBytes();
ByteBuf message = Unpooled.buffer(bt.length);
message.writeBytes(bt);
ch.writeAndFlush(message);
} System.out.println("客户端发送数据:"+str+",发送数据的长度:"+str.length());
} }
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/**
*
* Title: NettyClientFilter
* Description: Netty客户端 过滤器
* Version:1.0.0
* @author pancm
* @date 2017年10月8日
*/
public class NettyClientFilter extends ChannelInitializer<SocketChannel> { @Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
/*
* 解码和编码,应和服务端一致
* */
ph.addLast("decoder", new StringDecoder());
ph.addLast("encoder", new StringEncoder());
ph.addLast("handler", new NettyClientHandler()); //客户端的逻辑
}
}
import java.util.Date;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; /**
*
* Title: NettyClientHandler
* Description: 客户端业务逻辑实现
* Version:1.0.0
* @author pancm
* @date 2017年10月8日
*/
public class NettyClientHandler extends ChannelInboundHandlerAdapter { /**
* 业务逻辑处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客户端接受的消息:"+msg);
}
/**
* 建立连接时
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("建立连接时:"+new Date());
ctx.fireChannelActive();
} /**
*
* 关闭连接时
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("关闭连接时:"+new Date());
}
} 该项目我放在github上了,有兴趣的可以看看!https://github.com/xuwujing/Netty

Netty4 学习笔记之三:粘包和拆包的更多相关文章

  1. Netty学习(四)-TCP粘包和拆包

    我们都知道TCP是基于字节流的传输协议.那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句号什么地方有分号这个对于TCP底层来说并不清楚.应用层向TCP层发送用 ...

  2. C#网络编程学习(5)---Tcp连接中出现的粘包、拆包问题

    本文参考于CSDN博客wxy941011 1.疑问 我们使用第四个博客中的项目. 修改客户端为:连接成功后循环向服务器发送从1-100的数字.看看服务器会不会正常的接收100次数据. 可是我们发现服务 ...

  3. 【Netty】TCP粘包和拆包

    一.前言 前面已经基本上讲解完了Netty的主要内容,现在来学习Netty中的一些可能存在的问题,如TCP粘包和拆包. 二.粘包和拆包 对于TCP协议而言,当底层发送消息和接受消息时,都需要考虑TCP ...

  4. TCP粘包,拆包及解决方法

    在进行Java NIO学习时,发现,如果客户端连续不断的向服务端发送数据包时,服务端接收的数据会出现两个数据包粘在一起的情况,这就是TCP协议中经常会遇到的粘包以及拆包的问题.我们都知道TCP属于传输 ...

  5. [转帖]Linux学习笔记之rpm包管理功能全解

    Linux学习笔记之rpm包管理功能全解 https://www.cnblogs.com/JetpropelledSnake/p/11177277.html rpm 的管理命令 之前学习过 yum 的 ...

  6. netty 解决TCP粘包与拆包问题(二)

    TCP以流的方式进行数据传输,上层应用协议为了对消息的区分,采用了以下几种方法. 1.消息固定长度 2.第一篇讲的回车换行符形式 3.以特殊字符作为消息结束符的形式 4.通过消息头中定义长度字段来标识 ...

  7. netty 解决TCP粘包与拆包问题(一)

    1.什么是TCP粘包与拆包 首先TCP是一个"流"协议,犹如河中水一样连成一片,没有严格的分界线.当我们在发送数据的时候就会出现多发送与少发送问题,也就是TCP粘包与拆包.得不到我 ...

  8. 关于TCP的粘包和拆包

    问题产生 一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这个就是TCP的拆包和封包问题. 下面可以看一张图,是客户端向服务端发送包: 1. 第一种情况 ...

  9. TCP粘包的拆包处理

    因为TCP是流式处理的,所以包没有边界,必须设计一个包头,里面表示包的长度(一般用字节表示),根据这个来逐个拆包.如果对于发送/接收频率不高的话,一般也就不做拆包处理了,因为不大可能有粘包现象. 以下 ...

随机推荐

  1. Path.Combine 合并两个路径字符串,会出现的问题

    Path.Combine(path1,path2) 1.如果path2字符串,以 \ 或 / 开头,则直接返回 path2

  2. MarkDown的快速入门

    简介 简单的去解释MarkDown就是html,但是将html中的元素用符号去代替使用.本文用的编译软件是Atom(神器),不多说直接上图看效果. 语法 文本 列表 区块 分割符 表格 链接 mark ...

  3. Lua与javascript的差异

    Lua与javascript的差异 2010-03-08 Lua模拟器js方案 1.语法级模拟 lua与js语言差异 1.1注释 js 为//,lua为--. 1.2变量 js利用var来声明全局变量 ...

  4. TCP协议解析

    本文摘抄自:http://www.kuqin.com/shuoit/20141018/342719.html 本文描述了TCP协议,首先简单介绍了TCP完成了一些什么功能:介绍了TCP报文格式,以及典 ...

  5. King

    King Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submissi ...

  6. SQL查询多条不重复记录值简要解析【转载】

    转载http://hi.baidu.com/my_favourate/item/3716b0cbe125f312505058eb SQL查询多条不重复记录值简要解析2008-02-28 11:36 以 ...

  7. redis数据库安装及简单的增删改查

    redis下载地址:https://github.com/MSOpenTech/redis/releases. 解压之后,运行 redis-server.exe redis.windows.conf  ...

  8. TFS在项目中Devops落地进程(上)

    经过近2年折腾,基于TFS的Devops主线工程大体落地完毕.在此大体回忆下中间的各种历程. 开始之前简单说下什么是TFS(Team Foundation Server). TFS是微软推出的一款AL ...

  9. 三、VueJs 填坑日记之项目文件认识

    上一篇博文,我们搭建了一套基础的vuejs的环境,首先安装node.js,然后利用npm包管理器,安装vue-cli,设置淘宝镜像,初始化项目,安装依赖,运行.在这一篇,我们将认识vuejs项目里的各 ...

  10. gcc调试 学习1

    gdb进入调试 b 6 在第6行设置断点 d 2 删除num为2的断点 info b 查看断点 run 运行 n 执行到断点1 s 如果下一条是函数就进入函数 n 继续执行 print i  输出i的 ...