为什么要介绍NIO和AIO
Netty作为分布式的中间件,性能要求比较高。传统的方法方法是一个客户端开启一个线程,客户端的请求会被服务器派发,并且这种方式是倾向于cpu等待的,假使客户端处理缓慢或者网络拥堵,服务器会花费不少时间等待;意味着摒弃这种方式,能提高服务器的并发能力。
BIO:Block-IO
Block-io是阻塞同步的,如上面所说的,会倾向于cpu等待,会相当依赖客户端处理性能与客户端数量多少,通信是否耗时与网速。但是这种比较传统的通信方式,模式相对简单,使用也很方便。也可加入线程池进行优化,减少每次IO的资源消耗,在池中保持线程的激活状态。
BIO就是socket和serverSocket一一对应来完成一个套接字的通道。
NIO:准备好了再通知我
NIO是NonBlock-IO,是非阻塞同步的通信方式,其实NIO本身和并发没有什么关系,而是它很大大提高线程的使用率来解决并发问题的。
NIO有三个比较重要的概念:
- Buffer:缓冲区,实际上是一个ByteBuffer数组。
- Channel:通道,通道分两种(网络读写和文件读写)。通道是双向的,而stream是单向的。
- Selector:多路复用器,负责轮询Channel,找出就绪状态的Channel。(就绪状态意味着可被处理了)
NIO是一个selector轮询多个channel,channel会将自己注册在selector中,但自己的数据准备好了,就通知selector,也就是“准备好了再通知我”。
核心代码(具体代码参见下面提供的git)
....
selector.select();//消费阻塞
Set readykeys = selector.selectedKeys();//获取已经准备好的keys
Iterator i = readykeys.iterator();//迭代
long e = 0;
while (i.hasNext()) {
SelectionKey sk = (SelectionKey) i.next();
i.remove();//必须消除,防止重复消费
if (sk.isAcceptable()) {
doAccept(sk);//如果为接受状态,接受
} else if (sk.isValid() && sk.isReadable()) {//如果是可读
if (!time_stat.containsKey(((SocketChannel) sk.channel()).socket())) {
//将socket方法如map
time_stat.put(((SocketChannel) sk.channel()).socket(),
System.currentTimeMillis());//增加一个时间戳
//读取
doRead(sk);
}
} else if (sk.isValid() && sk.isWritable()) {
//写
doWrite(sk);
e = System.currentTimeMillis();
long b = time_stat.remove(((SocketChannel) sk.channel()).socket());
System.out.println("spend:" + (e - b) + "ms");//输入处理写入耗时
}
}
....
AIO:读完了再通知我
AIO是在NIO的基础上,提供了异步操作(asynchronized)。AIO是在IO操作完成后,再给线程发送通知,AIO就是完成不阻塞的,通过一个回调函数,等待IO完成后使用。
AIO提供了一个异步通道:
AsynchronousSocketChannel
此类的方法签名会有一个参数CompletionHandler,处理回调成功时或者失败时的结果。
重要的代码(具体代码参见下面提供的git)
......
......
//首先需要适用异步通道
public final static int PORT=65500;
private AsynchronousServerSocketChannel server;
public AIOServer() throws IOException {
server=AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));
}
public void start(){
System.out.println("Server listen on" +PORT);
//注册事件和事件完成过后的处理器
server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
public void completed(AsynchronousSocketChannel result, Object attachment) {
final ByteBuffer buffer=ByteBuffer.allocate(1024);
System.out.println(Thread.currentThread().getName());
Future<Integer> writeResult=null;
try{
result.read(buffer).get(100, TimeUnit.SECONDS);
buffer.flip();
writeResult=result.write(buffer);
}catch (InterruptedException|ExecutionException e){
e.printStackTrace();
}catch (TimeoutException e){
e.printStackTrace();
}finally {
try {
server.accept(null,this);
writeResult.get();
result.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
public void failed(Throwable exc, Object attachment) {
System.out.println("failed:"+exc);
}
});
}
....
....
Netty和NIO的关系
Netty创建工作组时,提供了一个NioServerSocketChannel(),这是便开启了NIO模式。
总结
这里主要介绍的是BIO/NIO/AIO的区别,三者根据顺便来排列,是十分越来越先进的,但是并不是AIO是最好的选择,具体还要根据业务场景进行选择。
git
https://github.com/Steve8seven/Netty-seed
源代码在nio/aio/bio文件下
参考资料
《Java高并发程序设计》葛一鸣/郭超