Netty介绍使用

一、Netty简介

Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可拓展性在同类框架(比如Mina、Grizzly)中首屈一指,得到上百上千的商用项目检验。

Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。它最牛逼的地方在于简化了网络编程规范,例如TCP和UDP的Socket服务。

二、Netty的优势

  1. API使用简单,开发门槛低;

  2. 功能强大,预置了多种编解码功能,支持多种主流协议;

  3. 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;

  4. 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;

  5. 成熟,稳定,Netty修复了已经发现的所有JDK NIO的bug,业务开发人员不需要再为NIO的bug烦恼;

  6. 社区活跃,版本迭代周期短,发现的bug可以被及时修复,同时,更多的新功能也会加入;

  7. 经历了大规模的商业应用考验,质量得到验证,Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经能够满足不同行业的商业应用了;

三、代码实例

3.1 服务端

TimeServer.java

TimeServerHandler.java

3.2 客户端

TimeClient.java

TimeClientHandler.java

3.3 运行结果

客户端

服务端

四、解决TCP的粘包拆包问题

4.1 发生原因

当发生以下情况下,TCP会发生粘包拆包:

  • 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;

  • 待发送的数据大于MSS(最大报文长度),TCP在传输前将进行拆包;

  • 要发送的数据小于TCP缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;

  • 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;

4.2 解决办法

解决粘包拆包的办法:

  • 消息定长,累计读到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息;将计数器置位,重新开始读取下一个数据报;

  • 将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛;

  • 将特殊的分隔符作为消息结束符,回车换行符就是一种特殊的结束分隔符;

  • 将消息分为消息头和消息体,消息头中包含数据包的长度,这样在接收端通过读取消息头长度字段便知道每个数据包的实际长度了。

4.3 netty解决方案

为了解决TCP的粘包拆包问题,netty默认提供了多种编解码器用于处理半包,分别对应上面解决TCP粘包拆包的办法,掌握这些,TCP粘包问题就变的非常容易。

4.3.1 反面实例

先来看一下产生TCP粘包的现象,我们改一下TimeClientHandlerTimeServerHandler的代码如下,期望是客户端发送100个指令,服务端也收到100个指令并返回结果给客户端:

TimeClientHandler.java

TimeServerHandler.java

运行服务端和客户端:

服务端运行结果表明它只接收到了两条消息,第一条包含57条” QUERY TIME ORDER” 指令, 第二条包含了43条” QUERY TIME ORDER” 指令,总数正好是100条。 我们期待的是收到100条消息,每条包含 一条” QUERY TIME ORDER” 指令。这说明发生了TCP粘包。

按照设计初衷,客户端应该收到100条当前时间的消息,但实际上只收到了1条错误指令,这是因为服务端只收到两条请求,所以服务端发送了两个应答消息,但实际上客户端只收到了1条,说明服务端返回的应答消息也发生了粘包。

4.3.2 LineBasedFrameDecoder

下面我们使用LineBaseFrameDecoderStringDecoder解决TCP粘包问题。

TimeServer.java

TimeServerHandler.java

TimeClient.java

TimeClientHandler.java

运行服务端和客户端:

可以看到使用LineBaseFrameDecoderStringDecoder完美解决了TCP粘包问题。

解决TCP粘包原理

LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断是否有\n或者\r\n,如果有就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。

StringDecoder的功能非常简单,就是将接收到的对象转换成字符串,然后继续调用后面的Handler。LineBasedFrameDecoder+baseDecoder组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。

4.3.3 DelimiterBasedDecoder

DelimiterBasedDecoder可以自动完成以分隔符作为码流结束标志的消息解码,下面我们使用$_作为分隔符演示DelimiterBasedDecoder的使用方法。以经典的Echo服务为例,Server接收到Client的请求消息后,将其打印出来,然后将原始消息返回给客户端。

服务端

EchoServer.java

EchoServerHandler.java

客户端

EchoClient.java

EchoClientHandler.java

分别启动服务端和客户端打印结果如下:

可以看到DelimiterBasedDecoder以及处理好TCP的粘包。

4.3.4 FixedLengthFrameDecoder

FixedLengthFrameDecoder是规定长度解码器。它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP包的粘包/拆包问题,非常实用。

服务端

EchoServer.java

EchoServerHandler.java

客户端

EchoClient.java

EchoClientHandler.java

运行服务端和客户端结果如下:

可以看到服务端只截取了前20个字节的长度。

4.4 编码器总结

  • LineBasedFrameDecoder:以换行符为结束符对消息解码;

  • DelimiterBasedDecoder:以指定的特殊字符为结束符对消息解码;

  • FixedLengthFrameDecoder:获取指定的长度的消息;

使用这三种解码器再结合其他解码器比如字符串解码器,就可以轻松完成对消息的自动编码,而且不再需要考虑TCP粘包拆包问题,极大地提升了开发效率。

Last updated

Was this helpful?