总言:netty是一款基于NIO(Nonblocking I/O,非阻塞IO)开发的网络通信框架。
准备知识:IO,NIO
下面介绍四种I/O模型。
1、阻塞I/O模型:缺省情况下所有文件操作都是阻塞的。当一个线程去读取某个缓冲区时,如果缓冲区没有数据,那么这个线程会一致等待下去,这就是阻塞I/O。
伪代码如下:
{
// read阻塞
read(socket, buffer);
// 处理buffer
process(buffer);
}
2、非阻塞I/O模型:
用户线程不断发起IO请求. 数据未到达时系统返回一状态值; 数据到达后才真正读取数据。
用户线程每次请求IO都可以立即返回,但是为了拿到数据,需不断轮询,无谓地消耗了大量的CPU
一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性
伪代码如下:
{
// read非阻塞
while(read(socket, buffer) != SUCCESS);
process(buffer);
}
3、I/O复用模型:
IO多路复用建立在内核提供的阻塞函数select上
用户先将需要进行IO操作的socket添加到select中,然后等待阻塞函数select返回。当数据到达后,socket被激活,select返回,用户线程就能接着发起read请求。
伪代码如下:
{
// 注册
select(socket);
// 轮询
while(true) {
// 阻塞
sockets = select();
// 数据到达, 解除阻塞
for(socket in sockets) {
if(can_read(socket)) {
// 数据已到达, 那么socket阻不阻塞无所谓
read(socket, buffer);
process(buffer);
}
}
}
}
看起来和加了循环的同步阻塞IO差不多?
实际上, 我们可以给select注册多个socket, 然后不断调用select读取被激活的socket,实现在同一线程内同时处理多个IO请求的效果.
至此, 同步阻塞(阻塞在select) / 同步非阻塞(IO没有阻塞) {不知道该怎么称呼}完成
更进一步, 我们把select轮询抽出来放在一个线程里, 用户线程向其注册相关socket或IO请求,等到数据到达时通知用户线程,则可以提高用户线程的CPU利用率.
这样, 便实现了异步方式。
IO多路复用是最常使用的IO模型,因其轮询select的线程会被阻塞, 异步程度还不够“彻底”, 所以常被称为异步阻塞IO
4、异步IO
真正的异步IO需要操作系统更强的支持。
IO多路复用模型中,数据到达内核后通知用户线程,用户线程负责从内核空间拷贝数据;
而在异步IO模型中,当用户线程收到通知时,数据已经被操作系统从内核拷贝到用户指定的缓冲区内,用户线程直接使用即可。
伪代码如下:
// 继承CompletionHandler, buffer为用户线程指定的缓冲区
void UserCompletionHandler::handle_event(buffer) {
process(buffer);
}
// 调用异步的read函数
{
aio_read(socket, new UserCompletionHandler);
}
相比于IO多路复用,异步IO并不常用,因为目前操作系统对异步IO的支持并不完善,IO多路复用也基本够用. 有很多做法是用IO多路复用模型模拟异步IO(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。
JDK7已经支持了AIO, netty采用过又放弃了, 据说是性能并没有多路复用好.