一. 什么是网络IO?
网络IO,拿read操作举例,就是程序(内存)从网卡读数据的过程。网卡相当于是文件IO的磁盘。客户端发过来的内容会先存在网卡中。
2. 使用缓冲字节流读写效率为什么比普通字节流读写效率好?(这个原理会类比到下面讲的IO多路复用中的Buffer)
因为普通字节流执行read操作时,直接将数据拷贝给int b。 而缓冲字节流会在内存中设立一块缓冲区,长度为8192的字节数组。此时在执行read操作时,磁盘会将数据一次性地读8192个字节到缓冲区,然后int b再一个一个地去这个缓冲区去拷贝。当int b把缓冲区读完后,缓冲区再区磁盘中拷贝8192个字节。直到读完。
这样做的好处是,磁盘与内存之间的数据拷贝会非常耗时且消耗cpu,而内存内部自己的拷贝则很快。所以设立缓冲区会减少磁盘与内存之间的拷贝次数,提高效率。
write操作也一样,int b先写到write缓冲区,写满缓冲区后,缓冲区一次性拷贝到磁盘中。
3. IO多路复用为什么和多线程一样工作效率高?
1.并发是单个cpu来处理多个线程,比如说下图中的红线代表cpu,蓝线代表的是线程。并发是红线在两个蓝线之间来回切换。 而并行是有两个cpu同时处理两个线程。
所以一个cpu,但是开启两个线程,如果这两个线程在执行过程中没有任何的等待时间,那么开启多线程是没有意义的,并不能提高执行时间。但是如果其中有一个线程执行过程出现了阻塞等待,那么此时cpu就去处理另一个线程,而不是只等着当前线程,所以会提高执行效率。
所以开启多线程他不是并行而是并发!!!!!!!!!!!!!
交替进行:
那么开启多线程之后也就是可以让cpu没有等待的时间,不停的在线程之间切换去执行。
而IO多路复用虽然不是多线程,但是会让一个单线程去管理每一个socket。就相当于让一个单线程去管理一些多线程(因为一个socket就是一个线程),哪个socket线程就绪就让cpu执行哪个线程。这也相当于没有了等待时间。这就是为什么IO多路复用时单线程但是和多线程效率一样的原因。
4. IO多路复用的原理
NIO 主要包括以下三个核心组件:
- Buffer(缓冲区):NIO 读写数据都是通过缓冲区进行操作的。读操作的时候将 Channel 中的数据填充到 Buffer 中,而写操作时将 Buffer 中的数据写入到 Channel 中。
- Channel(通道):Channel 是一个双向的、可读可写的数据传输通道,NIO 通过 Channel 来实现数据的输入输出。通道是一个抽象的概念,它可以代表文件、套接字或者其他数据源之间的连接。
- Selector(选择器):允许一个线程处理多个 Channel,基于事件驱动的 I/O 多路复用模型。所有的 Channel 都可以注册到 Selector 上,由 Selector 来分配线程来处理事件。
这个Buffer的工作原理就和上面的缓冲字节流的Buffer原理差不多。这个Buffer是内核缓冲区,存在于内核态,存在Buffer中的数据还没有被发送到用户态中,所以程序还使用不了数据,此时线程就回去执行其他的操作。
当数据将缓冲区全部读或者写好后,selector就会去通知线程进行相应的read和write操作,比如read操作,在线程接到通知后,就会启动程序,将Buffer中的数据拷贝到用户空间去。
Selector(选择器)
Selector(选择器) 是 NIO 中的一个关键组件,它允许一个线程处理多个 Channel。Selector 是基于事件驱动的 I/O 多路复用模型,主要运作原理是:通过 Selector 注册通道的事件,Selector 会不断地轮询注册在其上的 Channel。当事件发生时,比如:某个 Channel 上面有新的 TCP 连接接入、读和写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来。Selector 会将相关的 Channel 加入到就绪集合中。通过 SelectionKey 可以获取就绪 Channel 的集合,然后对这些就绪的 Channel 进行响应的 I/O 操作。
一个多路复用器 Selector 可以同时轮询多个 Channel,由于 JDK 使用了 epoll()
代替传统的 select
实现,所以它并没有最大连接句柄 1024/2048
的限制。这也就意味着只需要一个线程负责 Selector 的轮询,就可以接入成千上万的客户端。
Selector 可以监听以下四种事件类型:
SelectionKey.OP_ACCEPT
:表示通道接受连接的事件,这通常用于ServerSocketChannel
。SelectionKey.OP_CONNECT
:表示通道完成连接的事件,这通常用于SocketChannel
。SelectionKey.OP_READ
:表示通道准备好进行读取的事件,即有数据可读。SelectionKey.OP_WRITE
:表示通道准备好进行写入的事件,即可以写入数据。
Selector
是抽象类,可以通过调用此类的 open()
静态方法来创建 Selector 实例。Selector 可以同时监控多个 SelectableChannel
的 IO
状况,是非阻塞 IO
的核心。
一个 Selector 实例有三个 SelectionKey
集合:
- 所有的
SelectionKey
集合:代表了注册在该 Selector 上的Channel
,这个集合可以通过keys()
方法返回。 - 被选择的
SelectionKey
集合:代表了所有可通过select()
方法获取的、需要进行IO
处理的 Channel,这个集合可以通过selectedKeys()
返回。 - 被取消的
SelectionKey
集合:代表了所有被取消注册关系的Channel
,在下一次执行select()
方法时,这些Channel
对应的SelectionKey
会被彻底删除,程序通常无须直接访问该集合,也没有暴露访问的方法。