Java NIO-Tomcat NIO

Java NIO-Tomcat NIO

2020-04-18    07'04''

发布人: 橙汁儿橙汁儿。

200 4

介绍:
1.BIO/NIO/AIO Java 对于 IO 有几种实现形式: (1)BIO 阻塞同步 服务器端有 ServerSocket ,客户端有连接请求时会新建一个 Socket,每次服务器收到连接请求都要生成一个 Socket 和 线程 去完成连接的请求,之后客户端 和 服务器 进行阻塞式的通信,客户端发请求后,在服务器响应之前,阻塞等待,什么事情也做不了。如果客户端连接请求很多的话,开销比较大。 (2)NIO 同步非阻塞,基于缓冲区 Buffer 和 通道 Channel,向选择器 Selector 中注册通道 和 感兴趣的事件,选择器会轮询,把感兴趣的事件作为 key 返回给程序。读操作是从 Channel 读到 Buffer 中,写操作是写到 Channel 中。 (3)AIO 异步非阻塞,有请求时操作系统异步地读到 Buffer 中进行处理,处理完后通知用户,这期间用户不会等待,而是去做自己的事情。 操作系统中的 IO (1)阻塞 (2)非阻塞 (3)异步 (4)复用I/O (5)信号驱动 IO 2.Java 对于 NIO 的实现 有三大组件:Buffer、Channel、Selector Buffer 是个抽象类,实现子类有 ByteBuffer、IntBuffer、ShortBuffer、MapperedByteBuffer 内存映射文件的,这些 Buffer 底层是数组,有以下属性:position 下标,当前位置;capacity 容量、limit、mark。 position 初始值是0,每读或写 会加一,表示下一次的写入位置。 capacity 是容量,在使用 allocate 指定后就不能更改了,之后如果满的话会清空,从 0 重新开始。 limit 在读的时候,等于已经填充的数组的长度;在写的时候表示最大能写入的数据大小,等于数组的容量,也就是 capacity。 调用mark() 方法的话,会把 position 的值赋给 mark,之后使用 reset() 重置时会从 mark 处开始读。 Buffer 的 flip 方法,从写模式切换到读模式。把 position 的值赋给 limit,然后把 position 重置0。 有几个清空的方法: (1)rewind() 一般用于读,是把 position 置为 0。 (2)clear() 一般用于写,是把 position 置为 0,limit 置为 capacity,写的时候从 0 开始,新值会覆盖旧值。 (3)compact() 比方说已经读到 position=2,会把 2 之后 还没读的元素 到 limit 整体迁移到 从0开始,之后从 limit 之后继续写,它相当于是释放掉读过的空间。 Channel 有 FileChannel (不支持非阻塞)、ServerSocketChannel(TCP 连接的服务器端,调用 accept()方法返回值是 SocketChannel)、SocketChannel、DataGramChannel(UDP通道)。 Channel 有 register() 注册方法,传入 Selector 和 一个 int 类型的参数,表示感兴趣的事件:分别是 1、4、8、16,分别表示:通道内有可读的事件、可写、connect 成功建立 TCP 连接、accpept 接受 TCP 连接。如果想监听多个事件,可以把值相加。register() 返回值是 SelectionKey ,之后调用 selector 的 select() 方法,方法就会把准备好的事件拷贝到 Set 中,如果没有任何通道准备好,这个方法会阻塞,直到至少有一个通道准备好为止。可以通过遍历这个 Set,去处理相应的事情。 除了 select 方法,还有 selectNow 方法,如果没有准备好的通道会立即返回 0,而不会被阻塞。 3.Tomcat 对于 NIO 的实现 在 init() 方法中,会调用 NIOendPoint 的 bind()方法,开启一个 ServerSocketChannel。设置 acceptor 线程的个数 1,poller 线程数,如果是多核 CPU,线程数是 2;如果是单核 CPU 的话,线程数是1。 在 start() 方法中,启动 worker 线程池 和 accptor、poller 线程。 Accpector 循环调用 ServerSocketChannel 的 accept() 方法获取新的连接,生成 SocketChannel 实例,会用 getPoller() 选择一个 poller。 poller 中有一个 SynchronizedQueue 类型的 events 队列,events 会取出当前队列的 PollerEvent 对象,逐个执行 run() 方法,会把 SocketChannel 注册到 selector 中, 每个 poller 会关联一个 Selector,监听 read 事件,一旦有通道是可读的,就会进入 processkey 方法,创建 SocketProcesser 实例,把实例提交到线程池中。 4.epoll 之前有 poll,是知道有几个通道准备好,但是不知道具体是哪几个通道,所以需要遍历,时间复杂度是 O(n)。而 epoll 是 知道具体是哪几个通道,时间复杂度是 O(1)。