Java IO学习笔记六:NIO到多路复用
发布时间:2023-04-18 14:19:35 所属栏目:教程 来源:
导读:虽然NIO性能上比BIO要好,参考:Java IO学习笔记五:BIO到NIO
但是NIO也有问题,NIO服务端的示例代码中往往会包括如下代码:
....
//遍历已经链接进来的客户端能不能读写数据
for (SocketChanne
但是NIO也有问题,NIO服务端的示例代码中往往会包括如下代码:
....
//遍历已经链接进来的客户端能不能读写数据
for (SocketChanne
|
虽然NIO性能上比BIO要好,参考:Java IO学习笔记五:BIO到NIO 但是NIO也有问题,NIO服务端的示例代码中往往会包括如下代码: .... //遍历已经链接进来的客户端能不能读写数据 for (SocketChannel c : clients) { int num = c.read(buffer); if (num > 0) { buffer.flip(); byte[] aaa = new byte[buffer.limit()]; buffer.get(aaa); String b = new String(aaa); System.out.println(c.socket().getPort() + " : " + b); buffer.clear(); } } ... 即:遍历所有的SocketChannel,获取能读写数据的客户端,当客户端数量非常多的时候,服务端要轮询所有连接的客户端拿数据(recv调用),很多调用是无意义的,这样会导致频繁的用户态切换成内核态,导致性能变差。 多路复用技术可以解决NIO的这个问题,多个IO通过一个系统调用获得其中的IO状态,然后由程序对有状态的IO进行读写操作。在Linux系统中,多路复用的实现有: 基于POSIX标准的SELECT POLL (select只支持最大fd < 1024,如果单个进程的文件句柄数超过1024,select就不能用了。poll在接口上无限制) EPOLL 其中SELECT和POLL类似,但是有一些区别,参考select和poll的区别 无论NIO,SELECT还是POLL,都是要遍历所有IO,询问状态,只不过遍历这件事到底是内核来做还是应用程序来做而已。 而epoll,可以看成是SELECT和POLL的增强,在调用select/poll时候,都需要把fd集合从用户态拷贝到内核态,但是epoll调用epoll_ctl时拷贝进内核并保存,之后每次epoll_wait不做拷贝,而且epoll采用的是事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到rdllist里面。时间复杂度O(1)。 更多内容可以参考: 深入理解 Epoll Select、Poll、Epoll详解 Java的Selector封装了底层epoll和poll的API,可以通过指定如下参数来调用执行的内核调用, 在Linux平台,如果指定 -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.PollSelectorProvider 则底层调用poll, 指定为: -Djava.nio.channels.spi.SelectorProvider=sun.nio.ch.EPollSelectorProvider 或者不指定,则底层调用epoll。 源码参考:jdk8u-jdk image 接下来,我们使用一套服务端代码,在Linux服务器上运行,分别指定底层用epoll和poll,并用strace工具来追踪其内核调用。 准备服务端代码: import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; public class SocketMultiplexingV1 { private Selector selector = null; int port = 9090; public void initServer() { try { ServerSocketChannel server = ServerSocketChannel.open(); server.configureBlocking(false); server.bind(new InetSocketAddress(port)); selector = Selector.open(); server.register(selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { e.printstacktrace(); } } public void start() { initServer(); System.out.println("服务器启动了。。。。。"); try { while (true) { Set<SelectionKey> keys = selector.keys(); System.out.println(keys.size() + " size"); while (selector.select() > 0) { //返回的有状态的fd集合 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> iter = selectionKeys.iterator(); while (iter.hasNext()) { SelectionKey key = iter.next(); iter.remove(); if (key.isAcceptable()) { acceptHandler(key); } else if (key.isReadable()) { readHandler(key); } } } } } catch (IOException e) { e.printstacktrace(); } } public void acceptHandler(SelectionKey key) { try { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel client = ssc.accept(); client.configureBlocking(false); ByteBuffer buffer = ByteBuffer.allocate(8192); client.register(selector, SelectionKey.OP_READ, buffer); System.out.println("-------------------------------------------"); System.out.println("新客户端:" + client.getRemoteAddress()); System.out.println("-------------------------------------------"); } catch (IOException e) { e.printstacktrace(); } } public void readHandler(SelectionKey key) { SocketChannel client = (SocketChannel) key.channel(); ByteBuffer buffer = (ByteBuffer) key.attachment(); buffer.clear(); int read; try { while (true) { read = client.read(buffer); if (read > 0) { buffer.flip(); while (buffer.hasRemaining()) { client.write(buffer); } buffer.clear(); } else if (read == 0) { break; } else { client.close(); break; } } } catch (IOException e) { e.printstacktrace(); } } public static void main(String[] args) { SocketMultiplexingV1 service = new SocketMultiplexingV1(); service.start(); } } (编辑:汽车网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
推荐文章
站长推荐
