阻塞 与 非阻塞
阻塞
请求收到后,请求被一直阻塞,直到条件满足(可读,可写等)
非阻塞
请求收到后,立即返回一个标志信息,而不会一直阻塞等待。一个通过 Selector选择器遍历channel获取满足条件的channel来处理。
同步 与 异步
同步
每个请求必须逐个地被处理,一个请求的处理会导致整个流程的暂时等待,这些事件无法并发地执行。用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行。nio是业务线程在io操作准备好得到通知,接着由该线程进行io操作,但是io本身还是同步的。
异步I/O
多个请求可以并发地执行,一个请求或者任务的执行不会导致整个流程的暂时等待。用户线程发起I/O请求后仍然继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数。AIO是在io操作完成后,给线程发出通知。
传统BIO简单实现及分析
传统bio 是同步阻塞io
这里简单实现一个直接输出客户端输入的server
server 实现
1 | public class SocketServer { |
client 实现
1 | public class SocketClient { |
可以看到 这里server只能一个线程只处理一个请求。每个请求的处理时间都在6秒(client发送数据的时候我暂停了,模拟一个缓慢的网络环境)。
服务器需要处理大量的请求连接,如果每个请求都像上面的client 一样很慢,这就会拖慢服务器的处理速度,服务器每秒能处理的请求就会很少。
这里服务器慢不是由于服务端有很多繁重的任务,而是在等慢慢的cpu,高效cpu等低效 的网路io实在是太不划算了。
NIO简单实现及分析
NIO:准备好了通知我,是同步非阻塞io
了解nio,首先要知道 Channel,Buffer ,Selector这几个关键组件
Channel: 类似于流,和一个文件或者socket对应,往channel中写数据,就等于往socket中写数据
Buffer: 简单理解为一个内存区域或者byte数组,数据需要包装成Buffer才能与channel交互
selector: SelectableChannel可以注册到selector中,被selector管理。一个selector可以管理多个Channel
当Channel的数据准备好了 selector就会接到通知。
可以看到一个线程管理一个selector处理多个channel,也就是多个客户端连接,这就能用少量的线程处理大量的客户端连接。
server实现
1 | 4j |
使用前面bio 的client连接nio的server,可以看到及时客户端迟钝或者出现网络延迟,对服务端影响也不大。
这里的server还是只用了一个main线程来处理请求,也可以像上面bio那样使用线程池来处理请求,将读写请求读写注册到不同的selector中,一个主selector负责监控连接请求
多个子selector负责监控处理读写请求
多selector实现如下
1 | public class ThreadPoolSocketServer { |
这里采用了5个ReadWriteProccess来处理读写请求,主线程只用建立连接,建立好连接之后就交给ReadWriteProccess来处理降低了主线程的压力。netty中也是类似的多Reactor模式。
当然客户端也能使用nio来实现
client实现
1 | public class SocketClient { |
AIO简单实现及分析
aio比nio更进一步,它不是在io准备好时通知线程,而是在io完成时,再通过回调函数的方式给线程发送通知
可以看到下面的实现全是异步回调函数。
server实现
1 | public class SocketServer { |
client实现
1 | public class SocketClient { |