最近学习了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. vue笔记未整理

    全局组件 局部组件 子组件传值到父组件 父子组件传值 watch跟计算属性差不多,都会有缓存,计算属性优先 计算属性get set 对象 数组 对象 数组 不复用 改变数组 直接修改数组,页面没变化 ...

  2. 【P2577】 午餐

    题目简述 THU ACM小组一行N个人去食堂吃饭,计划是这样的:先把所有的人分成两队,并安排好每队中各人的排列顺序,然后一号队伍到一号窗口去排队打饭,二号队伍到二号窗口去排队打饭.每个人打完饭后立刻开 ...

  3. [BJOI2019]光线[递推]

    题意 题目链接 分析 令 \(f_i\) 表示光线第一次从第一块玻璃射出第 \(i\) 块玻璃的比率. 令 \(g_i\) 表示光线射回第 \(i\) 块玻璃,再射出第 \(i\) 块玻璃的比率. 容 ...

  4. Elastic Stack-Kibana使用介绍(七)

    一.前言     主要来讲述一下Kibana使用以及上生产时候的一些配置,要是大家对这块比较感兴趣我到时候也可以在结合Grafana做一些图表方面的介绍,后面等介绍完Beats以后我去阿里云租几台机器 ...

  5. npm包--rimraf

    含义 rimraf 包的作用:以包的形式包装rm -rf命令,用来删除文件和文件夹的,不管文件夹是否为空,都可删除. 安装 npm install rimraf --save-dev 使用 const ...

  6. 记录一次无厘头的粗心失误——java后台报错:Unknown column 'xxx' in 'field list'

    原因: sql文件马虎,直接用错了仓库.用的不是程序调用的仓库.而自己pojo和mapper还是采用Mybatis的逆向工程生成的.当时搞得很无厘头. 解决方案: sql用到程序指定的仓库就行啦. 总 ...

  7. 微信内分享第三方H5链接无法使用内置浏览器打开的解决方案

    很多朋友在微信内想分享转发H5链接的时候都会很容易碰到H5链接在微信内无法打开或在微信内无法打开app下载页的情况.通常这种情况微信会给个提示 “已停止访问该网址” ,那么导致这个情况的因素有哪些呢, ...

  8. Cnario Player 接入视频采集卡采集外部音视频信号测试

    测试产品 型号: TC-D56N1-30P采集卡 参数: 1* HDMI 1.4输入, PCIe 接口为PCI-Express x4(Gen2), 最高支持4096x2160@30Hz, 支持1920 ...

  9. 分布式唯一ID生成方案是什么样的?(转)

    一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...

  10. React Navigation & React Native & React Native Navigation

    React Navigation & React Native & React Native Navigation React Navigation https://facebook. ...