最近学习了netty,想写一个简单的rpc,结果发现发送消息时遇到难题了,网上搜了一下,这种情况是半包问题和粘包问题,主要是出现在并发高一些的时候。

talk is cheap

客户端编码:

    protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
encode0(channelHandlerContext,o,byteBuf);
}
private void encode0(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
if(o instanceof UserInfo){
byte[] data = Serializition.serialize((UserInfo) o,UserInfo.class);
byteBuf.writeBytes(data);
}
}

服务端解码:

protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
myDecode(channelHandlerContext,byteBuf,list);
}
public void myDecode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list){
int len = byteBuf.readableBytes();
byte[] data = new byte[len];
byteBuf.readBytes(data);
UserInfo userInfo = Serializition.deSerialize(data,UserInfo.class);
list.add(userInfo);
}

这是最初版本的,一开始以为只要读出来反序列化成对象就ok了,进行了简单的测试发现没问题,但客户端发送频繁一些服务端就开始报错:

警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: java.lang.RuntimeException: Reading from a byte array threw an IOException (should never happen).

分析一下发现对于来自同一个远程连接来说,服务端只会分配一个bytebuf来接收消息(这里使用的是UnpooledDirectByteBuf),这个bytebuf容量是动态扩增的,如果当前的长度不够用来存储新的消息就会自动扩展。当客户端发送不频繁时,服务端有足够的时间来做准备接收和处理消息,不会出现问题。但客户端频繁发送时就会出现问题了,如上,服务端的可读的字节超过了一个对象,读取后下一个对象反序列化就会出现问题。

解决思路:

  1.每次发送定长的消息,不够就补全,服务端设置对应的长度(但这样有问题:如果这样做客户端会发送很多无用信息,浪费性能,而且不知道设置多大的长度合适)

  2.使用netty自带的编码和解码器,如使用/r/n标志符解码,这就要继承MessageDecoder了,也就是字符解码,即先将消息在字节--字符串--对象将转换(有点浪费效率,而且万一内容中有对应的分隔符就会出问题)

  3.每次发送消息前先获取对象字节数组的长度(我最开始使用的方法,后来在网上也找到别人一样的思路)

  客户端:

    protected void encode(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
encode1(channelHandlerContext,o,byteBuf);
}
private void encode1(ChannelHandlerContext channelHandlerContext, Object o, ByteBuf byteBuf) throws Exception {
if(o instanceof UserInfo){
byte[] data = Serializition.serialize((UserInfo) o,UserInfo.class);
byteBuf.writeInt(data.length);
byteBuf.writeBytes(data);
}
}

服务端:

    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
myDecode1(channelHandlerContext,byteBuf,list);
}
public void myDecode1(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list){
if(byteBuf.readableBytes()>4){
int len = byteBuf.readInt();
byte[] data = new byte[len];
byteBuf.readBytes(data);
UserInfo userInfo = Serializition.deSerialize(data,UserInfo.class);
list.add(userInfo);
}
}

这就看起来简单了  数据流是 |int|bytes|int|bytes,但实际情况还是发生了问题,还是出现了一样的问题。异常原因是服务端实例化数组长度后可读字节不够,原因是发送时客户端是分包发送的。

因此我在这个方法的基础上增加了一个条件:如果可读字节数不够就保存已创建好的字节数组,等下一次字节数够时使用

    private volatile int len=0;
protected void decode5(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int length =len>0?len:(byteBuf.readableBytes()>=4?byteBuf.readInt():0);
if(byteBuf.readableBytes()>=length&&length>0) {
byte[] data = new byte[length];
byteBuf.readBytes(data);
UserInfo userInfo = Serializition.deSerialize(data, UserInfo.class);
list.add(userInfo);
//bytes.put(length, data);
len=0;
}else {
len = length;
}
}

经过测试,问题得到解决。

记一次解决netty半包问题的经历的更多相关文章

  1. 通过大量实战案例分解Netty中是如何解决拆包黏包问题的?

    TCP传输协议是基于数据流传输的,而基于流化的数据是没有界限的,当客户端向服务端发送数据时,可能会把一个完整的数据报文拆分成多个小报文进行发送,也可能将多个报文合并成一个大报文进行发送. 在这样的情况 ...

  2. Netty 粘包/拆包应用案例及解决方案分析

    熟悉TCP变成的可以知道,无论是客户端还是服务端,但我们读取或者发送消息的时候,都需要考虑TCP底层粘包/拆包机制,下面我们先看一下TCP 粘包/拆包和基础知识,然后模拟一个没有考虑TCP粘包/拆包导 ...

  3. Http 调用netty 服务,服务调用客户端,伪同步响应.ProtoBuf 解决粘包,半包问题.

    实际情况是: 公司需要开发一个接口给新产品使用,需求如下 1.有一款硬件设备,客户用usb接上电脑就可以,但是此设备功能比较单一,所以开发一个服务器程序,辅助此设备业务功能 2.解决方案,使用Sock ...

  4. netty解决粘包半包问题

    前言:开发者用到TCP/IP交互时,偶尔会遇到粘包或者半包的数据,这种情况有时会对我们的程序造成严重的影响,netty框架为解决这种问题提供了若干框架 1. LineBasedFrameDecoder ...

  5. c# socket 解决粘包,半包

    处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...

  6. Netty 粘包/半包原理与拆包实战

    Java NIO 粘包 拆包 (实战) - 史上最全解读 - 疯狂创客圈 - 博客园 https://www.cnblogs.com/crazymakercircle/p/9941658.html 本 ...

  7. socket编程 TCP 粘包和半包 的问题及解决办法

    一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...

  8. Netty使用LineBasedFrameDecoder解决TCP粘包/拆包

    TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...

  9. Netty - 粘包和半包(上)

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 定义 TCP 传输中,客户端发送数据,实际是把数据写 ...

随机推荐

  1. Django学习笔记(1)--第一个项目

    操作系统:Windows Python版本:python3.6 前置工作: pip install virtualenvwrapper #安装虚拟环境管理包 1.创建虚拟环境 mkvirtualenv ...

  2. Ubuntu 14.04 下使用微软的跨平台轻量级开发神器 Visual Studio Code

    因为 Visual Studio Code 不断更新,官方最新 v1.32 的 .deb 包已经不能用于 Ubuntu 14.04 直接安装了. 下载 v1.31 的 deb 包安装即可:https: ...

  3. 有关CSS的overflow和border-radius的那些事,你的圆角被覆盖了吗?

    事件起因 最初是网友的一个提问,来自于我的知识星球社区: 说实话,不得不佩服这个网友的眼力,这么小的细节都能发现.不过这也正是 FineUI 一直前进的动力,来自社区的监督和促进. 从截图上看,貌似圆 ...

  4. 复习交换代数——Noether正规化

    目录 简介 初等启发 证明过程 几何意义 定理应用 参考资料 简介 在交换代数中有如下定理 Noether正规化引理 令$R$是一个有限生成$k$-代数整环,则存在$t_1,\ldots,t_n\in ...

  5. 在C++中定义常量

    在 C++ 中,有两种简单的定义常量的方式: 使用 #define 预处理器. 使用 const 关键字 使用 #define 预处理器: #define identifier value: #inc ...

  6. 【MySQL 读书笔记】当我们在执行更新语句的时候我们在做什么

    该篇其实重点涉及两个日志的使用和处理. 一个是 server 层的 binlog 一个是服务器层的 redolog. 首先还是根据主线来介绍当我们在执行更新语句的时候我们在做什么 Redo Log M ...

  7. LeetCode136.只出现一次的数字

    给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用额外空间来实现吗? 示例 1: 输入: [ ...

  8. mysql-windows版及优化

    一.Windows版下载地址:https://dev.mysql.com/downloads/mysql/ 二.安装并初始化mysql: 1.如果想要让MySQL安装在指定目录,那么就将解压后的文件夹 ...

  9. 升级AndroidStudio3.4问题汇总

    1.Could not get unknown property 'bootClasspath' for object of type org.gradle.api.tasks.compile.Com ...

  10. java中53个关键字的意义及使用方法

    摘自:https://www.cnblogs.com/feng9exe/p/9224450.html 1.java的关键字分别是什么,作用是什么? static 例子: public class Te ...