IO模型中,一个连接来了,会创建一个线程,对应一个while死循环,死循环的目的就是不断监测这个连接上是否有数据可以读。在大多数情况下,1万个连接里面同一时刻只有少量的连接有数据可读,因此,很多while死循环都白白浪费掉了,因为读不出数据。数据读写是以字节流为单位的。
NIO模型中,这么多while死循环转换为一个死循环,这个死循环由一个线程控制,那么NIO又是如何做到一个线程一个while死循环就能监测1万个连接是否有数据可读的呢?
这就是NIO模型中Selector的作用,一个连接来了之后,不会创建一个while死循环去监听是否有数据可读,而是直接把这条连接注册到Selector上。然后,通过检查这个Selector,就可以批量监测出有数据可读的连接,进而读取数据。
通常:
NIO模型中会有两个线程,每个线程都绑定一个轮询器Selector。serverSelector负责轮询是否有新连接,clientSelector负责轮询连接是否有数据可读。
服务端监测到新连接之后,不再创建一个新线程,而是直接将新连接绑定到clientSelector上,这样就不用IO模型中的1万个while循环死等。
clientSelector被一个while死循环包裹着,如果在某一时刻有多个连接有数据可读,那么通过clientSelector.select(1)方法可以轮询出来,进而批量处理。
数据的读写是面向Buffer的。
直接基于JDK原生NIO来进行网络开发十分复杂而且很容易出bug,Netty封装了NIO,不用再写一大堆复杂代码了。
使用JDK原生NIO需要了解太多概念,编程复杂,一不小心就Bug横飞。
Netty底层IO模型随意切换,而这一切只需要做微小的改动,改改参数,Netty可以直接从NIO模型变身为IO模型。
Netty自带的拆包/粘包、异常检测等机制让你从NIO的繁重细节中脱离出来,只需要关心业务逻辑即可。
Netty解决了JDK很多包括空轮询在内的Bug。
Netty底层对线程、Selector做了很多细小的优化,精心设计的Reactor线程模型可以做到非常高效的并发处理。
自带各种协议栈,让你处理任何一种通用协议都几乎不用亲自动手。
Netty社区活跃,遇到问题随时邮件列表或者Issue。
Netty已经历各大RPC框架、消息中间件、分布式通信中间件线上的广泛验证,健壮性无比强大。
用Netty来实现一个简单的通信demo
Maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
服务端:
public static void main(String[] args) {
ServerBootstrap serverBootstrap =new ServerBootstrap();
NioEventLoopGroup boss =new NioEventLoopGroup();
NioEventLoopGroup worker =new NioEventLoopGroup();
serverBootstrap
.group(boss,worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler() {
@Override
protected void channelRead0(ChannelHandlerContext ctx,String msg) {
System.out.println(msg);
}
});
}
})
.bind(8000);
}
客户端:
public static void main(String[] args)throws InterruptedException {
Bootstrap bootstrap =new Bootstrap();
NioEventLoopGroup group =new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new StringEncoder());
}
});
Channel channel =bootstrap.connect("127.0.0.1",8000).channel();
channel.close();
while (true) {
channel.writeAndFlush(new Date() +": hello world!");
Thread.sleep(2000);
}
}