Netty——网络编程的演变
本系列文章,将深入探讨Netty框架在网络通信中的应用,以及Netty框架如何实现高效的网络通信。希望通过这次的输出,能够进一步加深自己对网络通信以及Netty这一块的理解,从而为网络应用的开发提供更多的支持。
网络通信从最初的传输控制协议(TCP)到现在的应用层协议(HTTP),经历了多个阶段的发展。在每一个阶段,都有一些新的技术被提出来,来改善网络传输的效率和可靠性。在传输控制协议(TCP)阶段,技术有拥塞控制,流量控制,滑动窗口协议等等;在应用层协议(HTTP)阶段,技术有HTTP1.0,HTTP1.1,HTTP2.0,HTTP3.0等等。这些技术的出现,都是为了提高网络传输的效率和可靠性,以满足不同的应用场景。
此外,在网络通信的发展过程中,Netty这一框架也发挥了重要的作用。Netty是一个异步的,基于事件驱动的网络应用框架,它支持多种传输协议,可以实现快速的网络通信。Netty的出现,大大提高了网络通信的效率,使得网络应用变得更加简单,更加可靠。他们两个的共同点就是朝着更好的支持多连接的方向发展,只不过前者是在通信协议层面,而Netty则主要是在服务端。
一、网络通信协议

在网络通信过程中,理解“链接”和“字节流”是非常重要的。为了持续发送消息,需要建立一个类似管道的链接,消息就以字节流的形式发送出去,这就是网络通信中“链接”和“字节流”的概念。现在最常见的HTTP和HTTPS都是基于它们而来的,但是这并不代表这就是最佳的组合,因此HTTP协议也在不断发展,以完善其不足。
在HTTP1.0中,虽然通过维护FIFO队列来支持连接复用的技术,但默认是不开启的,因此建立一个通信连接时只能支持一个消息流的发送,这时对大文件的传输更加友好,因此出现了诸如雅虎23的优化原则。在此版本中,持久连接与即时压缩只能够二选一,这是因为?
如果你想标记一个消息发送完毕,你可以采取两种方法:断开链接或者提前告知文件的大小。然而,由于当时还没有第三种方法,所以这两种方法就不可兼得,因此只能选择其中一种来提升性能。
HTTP1.1的发展使得客户端和服务端之间的传输能够同时支持持久连接和压缩功能,它采用了分块编码的思路,从而实现了链接复用。然而,消息的传输仍然是串行的,这就导致了首队列等待问题。为了缓解这一问题,后边进行了一定程度的优化,变为服务端的首队列等待,因为服务端可以更好的评估一个消息流需要的性能与消耗的时间,但还是无法彻底解决问题。
正文: 在HTTP2.0时代,消息传输的最小单位从链接进化到帧,彻底解决了首部阻塞问题。在这个时代,一个链接中的多个消息可以进行交叉传输,因此HTTP1.0时代的雅虎21条原则也不再适用。HTTP2.0更好地支持小文件传输,甚至更擅长小文件传输。这主要是基于两点:
1、为了减小Header的传输压力对头采用了基于字典的信息复用,从一定程度上来说请求越多它的压缩效果也就越好
2、如果要是发送的过程中有消息中的一个帧传送出现了问题,会影响这个链接中所有的消息传送,甚至会引起重传。换个角度说就是传递小的就算失败了他返工也没有那么大呀!
HTTP与TCP的结合并不一定是最完美的,因为它们是不同层次的协议,也是一代早期的协议,存在一定的时代局限性。此外,这也不一定是唯一的选择,我们可以选择替代它们。这就有了UDP协议族的出现,目前官方主推的是谷歌的QUIC,它提出了链接标识符的概念,不再依赖于IP地址,可以很好地支持移动设备的连接。也就是说,当我们在Wi-Fi或无线网络环境中,不需要因为更换Wi-Fi就需要重新经历“一轮四次握手告别,三次握手重连”的过程,那么以后我们在高铁上的网速会不会变快呢?哈哈,如果是坐水平火箭,效果会更加显著吧?此外,它还可以对每个流进行单独控制,即使一个流出现错误,协议栈仍然可以独立地为其他流提供服务,这时候就可以说它对小文件和大文件的支持都很友好了。同时,它也更好地分离了四层协议和七层协议的职责。
二、初代通信框架Socket与BIO编程

在右边这张图中,这就是初代网络编程的一个常见接口,不只是Java语言。此时Java中的就是BIO编程,可以称之为阻塞编程,每一个连接进来我们都需要创建一个独立的线程来为其服务,在前面“字节流”我们知道消息并不是一下发过来的,所以在读取消息的过程中一定存在一个等待,这个等待就是一个阻塞。线程是计算机的稀缺资源,频繁的创建和销毁机器也吃不消。如果小时并发量小还好说,但是面对高并发的时候就无能为力了。这时候的主要问题是线程多,从而造成的创建和销毁带来的资源消耗,以及频繁的上下文切换带来的问题。此时NIO出现了,它来了!
三、非阻塞NIO

NIO中最重要的三个元素是Selector、Channel和Buffer。Selector作为选择器,监听网络事件,主要是连接、读、写。每一个请求过来的链接都会成为一个Channel,但需要正确处理。在BIO中,由于消息不是一下子到来的,所以在消息读取处理时会阻塞,如果有100个请求,就相当于有100个线程不断观察是否有需要读写的数据,这样就有100个阻塞的线程。Selector将监听的工作拿过来,如果监听到有需要处理的事件,就交给相关的线程处理,而Selector则专门负责监听,这样可以提高效率。消息不总是能够立即被处理,所以Buffer用来进行消息缓冲。
发展到NIO已经解决了我们在BIO中遇到的最大痛点,线程创建过多。但是这时候完全手动编程的难度有些太高了,开发人员要熟悉Java多线程编程,因为NIO编程涉及到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序。此外,我们还需要处理一些更为复杂的问题:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等。还有接口本身的一些臭名昭著的Bug,比如Epoll Bug,它会导致Selector的空轮训,最终导致CPU 100%。由于编程复杂性,使用原生NIO往往也会造成网络通信层面与业务上的强耦合,而这不是优秀代码应该有的样子。这时候就需要一个性能好、上手简单的网络通信框架,那么它就是我们这一系列文章的主角Netty!
~~~~~~愈看后事如何,且听下回讲解~~~~~~

