加入收藏 | 设为首页 | 会员中心 | 我要投稿 汽车网 (https://www.0577qiche.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

Java IO学习笔记六:NIO到多路复用

发布时间:2023-04-18 14:19:35 所属栏目:教程 来源:
导读:虽然NIO性能上比BIO要好,参考:Java IO学习笔记五:BIO到NIO

但是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();
    }
}
 

(编辑:汽车网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章