
近日学习Netty,在看书和实践的时候对于书上只言片语的那些话不是十分懂,导致尝试写例子的时候遭遇各种不顺,比如decoder和encoder还有HttpObjectAggregator的添加顺序,研究了一番之后和大家分享一下自己的理解,希望后来人可以少走弯路。
IO与NIO的区别
IO是Input与Output的缩写,主要意思就是输入输出,主要以及经常使用到的包括网络,文件中的IO。 传统IO完整的名称是同步阻塞IO,特点是IO过程中线程阻塞,等待IO返回,Java中普通的Sokcet通讯,都是同步阻塞IO。 NIO完整的名称是同步非阻塞IO,注意这里的区别,并不是异步。NIO需要操作系统底层支持,在Linux中以前有select和poll的机制,现在被更优秀的epoll代替,但这也产生了JavaNIO里selector关于epoll的一个Bug。
两者主要的区别和优缺点列举一下:
IO
传统IO即SIO等待IO时阻塞线程,所以面对高并发比较无力,连接数越多,需要的线程数多,基本1请求对应1线程,但延迟较低,线程等待数据返回后只需要再等到CPU时间片就可以马上处理在Java中API简单易懂,方便使用,线程模型简单,可以轻松完成1请求1返回的对应
NIO
通过epoll等机制,可以做到少数线程只轮询事件,轮询到事件后再交由其他线程处理,线程利用率高,面对高并发表现优秀,更少的线程数处理更多的连接,线程无需阻塞等待IO只需处理已经触发的事件,延迟稍高,虽然epoll在事件调度上比select和poll优秀了很多,但是应用程序还是需要遍历事件依次处理,在Java中API看似简单,实际坑多,需要好的线程模型支持,请求与返回之间较难1对1对应
Java中的NIO
首先大概解释下Java的NIO中比较重要的几个概念
SocketChannel:包括ServerSocketChannel这类,可以简单的认为是代表了连接,事实上使用过程中它一般表现的也像个连接。
Selector:可以认为是一种工具,执行selector可以从连接中获取各个连接就绪好的事件,如读(Read)写(Write)就绪,开启关闭,从ServerSocketChannel中可以接收到新进连接(Accept)事件。
ByteBuffer:封装了一些字节操作方法,可以认为是一个byte数组,类似于IO中读时常定义的byte[] buffer这种,区别是方法更多,可以操作堆内内存,也可以使用堆外内存。
下面上一个简单例子,说明这3个东西具体怎么使用
public static void main(String[] args) throws IOException { int channelId=0; //开启ServerSocketChannel和Selector ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); Selector selector = Selector.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress( InetAddress.getLocalHost(), 4700); //使用ServerSocketChannel绑定端口 serverSocketChannel.socket().bind(inetSocketAddress); //设置非阻塞模式 serverSocketChannel.configureBlocking(false); //设置非阻塞模式将selector注册到该Channel上,并设置该selector在该channel上监听Accept事件,该事件标致一个连接建立 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT).attach( channelId++); //循环遍历事件 for (;;) { //执行事件查询,去各个注册到该selector的channel上检查是否有就绪事件,该方法会阻塞,可以设置等待最长时间 selector.select(); //获取selector检查到的就绪事件 Set<SelectionKey> readySelectionKey = selector .selectedKeys(); Iterator<SelectionKey> it = readySelectionKey.iterator(); while (it.hasNext()) { SelectionKey selectionKey = it.next(); //事件判断 if (selectionKey.isAcceptable()) {// 客户请求连接 //从key中获取channel ServerSocketChannel channel = (ServerSocketChannel) selectionKey .channel(); //因为是Accept事件,调用方法获取具体的连接Channel,SocketChannel SocketChannel socketChannel=channel.accept(); //同样,给具体的连接注册读写事件,直接使用同一个selector去注册 socketChannel.configureBlocking(false) .register( selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE).attach(channelId++); } if (selectionKey.isReadable()) { //获取附加对象 int nowChannelId= (int) selectionKey.attachment(); //可读事件,通过key的方法获取到可读的channel SocketChannel clientChannel=(SocketChannel)selectionKey.channel(); //创建一个大小为64字节 ByteBuffer receiveBuf = ByteBuffer.allocate(64); //从channel中读取数据 clientChannel.read(receiveBuf); } if (selectionKey.isWritable()) {// 写数据 //同读取 SocketChannel clientChannel = (SocketChannel) selectionKey.channel(); ByteBuffer sendBuf = ByteBuffer.allocate(64); String sendText = "hello,world"; sendBuf.put(sendText.getBytes()); sendBuf.flip(); clientChannel.write(sendBuf); } if (selectionKey.isConnectable()) {
} //处理完后remove该事件 it.remove(); } } }解释几个关键点:
- 常用的有2种channel,一个ServerSocketChannel,用来绑定网络端口,获得具体链接,从他的Accept事件中获取具体的连接,另一种则可以认为是具体的某个连接,可以在其之上处理读写事件
- selector的注册,参数为(channel,监听的事件,还有附加对象),这里附加对象可以在注册时创建一个对象附加,可以通过key去获取,一般用来标识该channel的种类或者用来持有该channel事件的具体处理对象。
- 流程简而言之就是不断的select获取连接,再针对每个连接注册读写事件,然后如此循环处理
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 2176 天
部分内容可能已过时