最近学习了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. Luogu P1038 神经网络

    qwq 拓扑排序模板题. 拓扑排序,是在一个$DAG$中,其拓扑排序为其所有结点的一个线性排序(答案不唯一). 该排序满足这样的条件——对于图中的任意两个结点$u$和$v$,若存在一条有向边从$u$指 ...

  2. Java 7 和 Java 8 中的 HashMap原理解析

    HashMap 可能是面试的时候必问的题目了,面试官为什么都偏爱拿这个问应聘者?因为 HashMap 它的设计结构和原理比较有意思,它既可以考初学者对 Java 集合的了解又可以深度的发现应聘者的数据 ...

  3. flask轻量级框架入门

    # -*- encoding: utf-8 -*- #导入Flask类, 导入重定向,url_for是简易寻址跳转, from flask import Flask,redirect,url_for, ...

  4. 什么是Vagrant

    相信大家对VMware和VirsualBox不会太陌生,虚拟化的好处在这里我就不多说了.那么我们就一起来学习用Vagrant 为自己来打造一个神奇的跨平台开发环境吧!! 开发过程中,我们经常碰到一个问 ...

  5. socket传输数据循环多次的性能要好于一次

    今天做了一个测试,测试使用python的socket传输一个588k的数据,当传输过程执行一次时,执行时间为2ms左右.我觉着执行一次的时间并不能说明问题,于是就写了一个循环,循环执行1000次,然后 ...

  6. Nginx HTTP框架提供的其它变量

    L74

  7. 「Manacher算法」学习笔记

    觉得这篇文章写得特别劲,插图非常便于理解. 目的:求字符串中的最长回文子串. 算法思想 考虑维护一个数组$r[i]$代表回文半径.回文半径的定义为:对于一个以$i$为回文中心的奇数回文子串,设其为闭区 ...

  8. 升级 pip 超时解决方案

    安装的时候发现报错如下, 提示需要升级pip 按照指引进行升级, 同样的超时 使用 豆瓣源进行安装 pip python -m pip install --upgrade pip -ihttp://p ...

  9. Linux内核模块编程——Hello World模块

    Linux内核模块编程 编程环境 Ubuntu 16.04 LTS 什么是模块 内核模块的全称是动态可加载内核模块(Loadable Kernel Modul,KLM),可以动态载入内核,让它成为内核 ...

  10. POJ2975 Nim 【博弈论】

    DescriptionNim is a 2-player game featuring several piles of stones. Players alternate turns, and on ...