IO与Netty了解一下!网络通信框架是这样构成的!

随着互联网应用对高并发、高可用的要求越来越高,传统的垂直架构由于其自身的局限性逐渐被分布式、弹性伸缩的微服务架构替代。

微服务将单体应用拆分为多个独立的微服务应用,每个应用独立运行,每个服务间通过远程调用(RPC)进行通信,此时高性能的通信方式就显得尤为重要,实现RPC通信的底层框架Netty由于其稳定性、拓展性以及框架成熟度的优秀表现,在RPC框架领域应用广泛,著名的Hadoop、Dubbo、RocketMQ等框架都使用了Netty作为其网络通信的底层框架。

Netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架,对比于BIO(Blocking I/O,阻塞IO),它的并发性能得到了很大提高。接下来,带大家了解下BIO与NIO的区别。

BIO (blocking io)

BIO即传统IO,是一种同步阻塞的通信模式,模式简单,使用方便。但并发处理能力低,通信耗时,依赖网速。应用程序在获取网络数据的时候,如果网络传输数据很慢,那么程序就一直等着,直到传输完毕为止。 

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理,每处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的请求——应答通信模型。

1、Server端

Client端

这种处理方式在高并发的情况下,会创建出大量的线程,以至于最终耗尽资源,无法对外服务。所以传统的BIO线程模型,在现代情景的互联网应用中无法作为底层的通信模型进行使用。

NIO (non-blocking io)

NIO也称为New IO,是一种同步非阻塞的通信模式,NIO 相对于BIO来说是一大进步。客户端和服务器之间通过Channel通信,NIO可以在Channel进行读写操作,这些Channel都会被注册在Selector多路复用器上。Selector通过一个线程不停的轮询这些Channel,找出已经准备就绪的Channel执行IO操作。NIO 通过一个线程轮询,再基于轮询到的事件进行处理,不需要再为每个连接单独开个线程处理,从而以较高的资源复用率处理成千上万个客户端的请求,这就是非阻塞NIO的特点。

NIO的三大核心为Selector(选择器),Buffer(缓冲区),Channel(通道)。

Java架构交流群:895244712,群内提供Java学习资料(架构技术视频,面试,就业指导,java书籍,工具&软件等)欢迎大家加入。

▲缓冲区Buffer

它是NIO与BIO的一个重要区别。BIO是将数据直接写入或读取到Stream对象中,而NIO的数据操作都是在缓冲区进行的。缓冲区实际上也是一个数组,通常是一个字节数组(ByteBuffer),这个数组为缓冲区提供了数据的访问读写等操作属性,如位置、容量、上限等概念。(ByteBuffer由于只有一个位置指针处理读写操作,因此每次读写的时候都需要额外调用flip()将指针复位,否则功能将出错)

通道Channel

和流不同,通道是双向的。通道分为两大类:一类是网络读写(SelectableChannel),一类是用于文件操作(FileChannel),我们使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子类。最关键的是channel有多种状态位,可以与selector结合起来,方便selector去识别。

选择器(多路复用器)Selector

是NIO编程的基础,非常重要,提供选择已经就绪的任务的能力。当Channel注册到选择器后,Selector会分配给每个通道一个key值。Selector会不断地轮询注册在其上的Channel,如果某个Channel处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以取得就绪的Channel集合,从而进行后续的IO操作。

1、Server端

2、Client端

为什么选择Netty

Netty是基于Java NIO的网络应用框架,使用Netty可以快速开发网络应用,例如服务器和客户端的协议。Netty提供了一种新的方式来开发网络应用,使得它很容易使用和有很强的扩展性。Netty内部的实现是复杂的,但是Netty提供了简单易用的API从网络处理代码中解耦业务逻辑。

Netty的优点有:

1.相较于JDK原生的NIO提供的API,Netty的API使用简单,开发门槛低;

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

3.定制能力强,可以通过ChannelHandler进行扩展;

4.性能高,对比其它NIO框架,Netty综合性能最优;

5.经历了大规模的应用验证。在互联网、大数据、网络游戏、企业应用、电信软件得到成功,很多著名的框架通信底层就用了Netty,比如Dubbo;

6.稳定,修复了已经发现的NIO Bug。

Java架构交流群:895244712,群内提供Java学习资料(架构技术视频,面试,就业指导,java书籍,工具&软件等)欢迎大家加入。

Netty的使用

步骤:

1.创建两个NioEventLoopGroup实例,NioEventLoopGroup是个线程组,它包含了一组NIO线程,专门用于网络事件处理。这里创建两个的原因是一个用于服务端接收客户端的连接,另一个用于进行SocketChannel的网络读写;

2.创建一个ServerBootstrap对象,它是Netty用于启动NIO服务端的辅助启动类。配置Netty的一系列参数,例如将两个NIO线程组当作入参传递到ServerBootstrap中,设置创建的Channel,接受传出数据的缓存大小等;

3.创建一个实际处理数据的类Channellnitalizer,绑定IO事件的处理类ChannelHandler,进行初始化的准备工作,比如设置接受传出数据的字符、格式以及实际处理数据的接口;

4.绑定接口,执行同步阻塞方法;

5.最后调用NIO线程组的shutdownGracefully进行优雅退出,它会释放跟shutdownGracefully相关联的资源。

1、Server端

ChannelHandler类似于Servlet的Filter过滤器,负责对IO事件或者IO操作进行拦截和处理,它可以选择性地拦截和处理自己感兴趣的事件。Netty提供了ChannelHandlerAdapter基类,如果用户ChannelHandler关心某个事件,只需要覆盖ChannelHandlerAdapter对应的方法即可,对于不关心的,可以直接继承使用父类的方法,这样子类的代码就会非常简洁和清晰。

 ChannelHandlerContext对象提供了许多操作,这里我们调用了write(Object)方法来逐字地把接受到的消息写入。ctx.write(Object)方法不会使消息写入到通道上,他被缓冲在了内部,你需要调用ctx.flush()方法来把缓冲区中数据强行输出。或者你可以用更简洁的cxt.writeAndFlush(msg)以达到同样的目的。

还有ByteBuf是一个引用计数对象,这个对象需要调用release()方法来释放。

2、ServerHandler

3、Client端

4、ClientHandler

TCP拆包和粘包的问题解决

在基于流的传输里比如TCP/IP,接收到的数据会先被存储到一个socket接收缓冲里。不幸的是,基于流的传输并不是一个数据包队列,而是一个字节队列。即使你发送了2个独立的数据包,操作系统也不会作为2个消息处理,而仅仅是作为一连串的字节而言。因此这是不能保证你远程写入的数据就会准确地读取。举个例子,让我们假设操作系统的TCP/TP协议栈已经接收了3个数据包:

但是基于流传输的性质,在你的应用程序读取数据的时候很可能会被分成下面的片段。

因此,一个接收方不管他是客户端还是服务端,都应该把接收到的数据整理成一个或者多个更有意思并且能够让程序的业务逻辑更好理解的数据。在上面的例子中,接收到的数据应该被构造成下面的格式:

解决方式

1.消息定长,例如每个报文的大小固定为200个字节,如果不够,空位补空格。

2.在包尾部增加特殊字符进行分割,例如加”$”

3.将消息分为消息头和消息体,消息头中包含表示信息的总长度(或者消息体长度)的字段。

用POJO代替ByteBuf

在ChannelHandler中使用POJO的优点是显而易见的; 处理程序将从ByteBuf中提取信息的代码分离出来,使程序更易维护和可重用。

下面的例子通过MarshallingCodeCFactory工厂类创建了MarshallingDecoder解码器、MarshallingEncoder编码器,并添加到ChannelPipeline中。

1、POJO类

2、在ChannelPipeline加上编解码的Handler

3、MarshallingCodeCFactory工厂类

猜你喜欢

转载自blog.csdn.net/weixin_43277643/article/details/83050574