Netty框架学习
一 Netty的介绍与应用场景
-
netty的介绍
- Netty是JBOSS提供的一个Java开源框架,现在是Github上的独立项目
- Netty是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络IO程序
- Netty主要针对在TCP写一下,面向Clients端的高并发应用,或者P2P场景下的大量数据持续传输的应用
- Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
- 原生JDK的IO和网络编程是基于TCP/IP协议的,而NIO是在原生BIO的基础上的非阻塞IO编程技术。要透彻理解Netty,需要先学习NIO,这样才能理解阅读Netty的源码
-
Netty的应用场景
- 互联网行业
在分布式系统中,各个节点之间需要远程服务调用,因此高性能的RPC框架必不可少。Netty作为异步高性能的通信框架,往往作为基础通信组件被这些RPC框架使用。典型的应用有阿里分布式服务框架Dubbo - 游戏行业
Netty作为高性能的基础通信组件,提供了TCP/UDP和HTTP协议栈,方便定制和开发私有协议栈,账号登录服务器。 - 大数据领域
经典的Hadoop的高性能通信和序列化组件Avro的RPC框架,采用Netty进行跨界点通信
他的NettyService基于Netty框架封装实现
- 互联网行业
二 Java BIO编程
- IO模型
- IO模型简单的理解:就是不使用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能
- Java共支持三种网络编程模型IO模式:BIO、NIO、AIO
- BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
- NIO:同步非阻塞,服务器实现模式为一个线程处理多个连接,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求九进行处理
- AIO:异步非阻塞,AIO引入异步通道的概念,采用Proactor模式,简化了程序编写,有效的请求才会启动线程。特点是先由操作系统完成后才通知服务端程序启动线程去处理
-
三种IO模型使用场景分析
- BIO方式:适用于连接数目较小且固定的架构,对服务器资源要求较高,是JDK4之前的唯一选择,但是程序简单容易理解
- NIO方式:适用于连接数目较多且连接比较短的架构,如聊天服务器、弹幕系统、服务器之间通讯等,编程比较复杂
- AIO方式:适用于连接数目较多且连接比较长的架构,充分调用OS参与并发操作,编程比较复杂
-
BIO基本介绍
- Java BIO就是传统的java io编程,其相关的类和接口在java.io包下
- BIO就是blocking IO,也即同步阻塞IO。服务器实现模式为一个线程对应一个连接,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情就会导致不必要的线程开销,可以通过线程池机制改善,实现多个客户连接服务器。
-
BIO工作机制
使用Socket作为通道,在Socket中封装有输入输出流,服务器通过开启单独的线程来和对应的客户端进行数据传输- 服务器端启动一个ServerSocket
- 客户端启动Socket对服务器进行通讯,默认情况下服务器端需要对每个客户建立一个线程与其通讯
- 客户端发出请求后,先询问服务器是否有线程响应,如果没有则会继续等待,或者被拒绝
- 如果有响应,客户端线程会等待请求结束后再继续执行
-
BIO问题分析
- 每个请求都需要独立的线程来对其进行处理,与对应的客户端进行数据传输
- 当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大
- 连接建立后,如果当前线程暂时没有数据可读,那么将会阻塞在read操作上,造成线程资源浪费
三 Java NIO编程
- Java NIO基本介绍
- Java NIO全程java non-blocking IO,指的是JDK提供的新API,是同步非阻塞的
- NIO相关类都被放在java.nio包下,并且对原java BIO进行了很多改写
- NIO有三大组成部分:Channel、Buffer、Selector
- NIO是面向缓冲区或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区前后移动,增加了处理过程中的灵活性
- Java NIO的非阻塞模式,使一个线程从通道发送请求或者读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞。在数据变得可用之前,该线程可以做其他的工作。写数据也是如此,一个线程请求写入某通道,不需要等待数据完全写入就可以同时做其他事情
public void test(){
//创建一个Buffer,可以存放5个int
IntBuffer buffer = IntBuffer.allocate(5);
//向Buffer中存入数据
buffer.put(25);
buffer.put(14);
//将buffer进行读写转换
buffer.filp();
//对buffer依次进行读取
buffer.get();//25
buffer.get();//14
}
- NIO和BIO的比较
- BIO是以流的方式处理数据,而NIO是以块的方式处理数据,块IO的效率比流IO的效率高很多
- BIO是阻塞的,NIO是非阻塞的
- BIO基于字节流进行操作,而NIO基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区或者从缓冲区写入通道。Selector用于监听多个通道的事件,因此使用单个线程就可以监听多个客户端通道
-
NIO的核心组件关系
- 每个channel都会对应一个Buffer
- Selector对应一个线程,一个线程对应多个channel
- 程序切换到哪个channel是由事件Event决定的
- Selector会根据不同的事件,在各个通道上切换
- Buffer是一个内存块,底层是一个数组
- 数据的读取写入是通过buffer实现的,buffer可读可写,但是需要使用flip方法来切换
-
缓冲区Buffer
-
基本介绍
缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,能够跟踪和记录缓冲区的状态变化情况。 -
Buffer类及其子类
在NIO中,Buffer是一个顶层父类,包含很多子类如ByteBuffer、ShortBuffer、IntBuffer等
Buffer类定义了所有的缓冲区都具有的四个属性来提供关于其所包含的数据元素的信息
- capacity:容量,即buffer可以容纳的最大数据量,在缓冲区创建时被设定且不能改变
- limit:表示当前缓冲区的终点,不能对超过极限的位置进行读写操作,且极限是可以修改的
- position:位置,表示下一个要被读或者写的索引。每次对buffer进行读写操作都会改变该值,为下次读写做准备
- mark:标记
-
-
通道Channel
NIO通道类似于流,但是可以同时进行读写,并且实现异步读写数据。Channel不像stream,它是双向的。常用的Channel有FileChannel、ServerSocketChannel、SocketChannel等
public void test() throw Exception{
FileInputStream fis = new FileInputStream("1.txt");
FileChannel fileChannel1 = fis.getChannel();
FileOutputStream fos = new FileOutputStream("2.txt");
FileChannel fileChannel2 = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//循环读取
while(true){
buffer.clear();//清空buffer
int read = fileChannel1.read(buffer);
if(read == -1)
break;
//将数据写到文件2中
buffer.filp();
fileChannel2.write(buffer);
}
fileInputStream.close();
fileOuputStream.close();
}
- 选择器Selector
-
基本介绍
- Java的NIO采用非阻塞的IO方式,可以用一个线程处理多个的客户端连接,会使用到Selector
- 选择器能够检测到多个注册的通道上是否有事件发生,如果有事件发生,会根据事件类型进行相应的处理。这样就能够只用一个线程去管理多个通道,也即管理多个连接。
- 只有在连接/通道真正有读写使劲发生时,才会进行读写操作,这样就大大减少了资源开销,不需要为每个连接都维护一个线程。
- 避免了多线程之间的上下文切换导致的开销
-
Selector特点
- Netty的NioEventLoop聚合了Selector(也叫多路复用器),可以同时并发处理成百上千个客户端连接。
- 当线程从某客户端Socket通道进行读写数据时,如没有数据可用,线程可以进行其他任务
- 线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入输出通道(即一个NioEventLoop可以在其中注册多个Channel进行处理数据)
- 由于读写操作都是非阻塞的,这就可以充分提升IO线程的运行效率,避免由于频繁IO阻塞导致的线程挂起
- 一个IO线程可以并发处理N个客户端连接和读写操作。
-
- NIO非阻塞编程原理分析
- 当客户端连接时,会通过ServerSocketChannel得到SocketChannel
- Selector通过select方法进行监听,会得到有事件发生的Channel的个数
- 使用register方法可以将Channel注册到Selector上,一个Selector上可以注册多个Channel
- 注册后会返回对应Channel的SelectionKey,与Selector相关联
- 使用SelectionKey可以进一步得到Channel,通过Channel可以完成对应的业务处理