NIO 原理

参考:
https://zhuanlan.zhihu.com/p/345808940
https://blog.csdn.net/u013857458/article/details/82424104

1. Java 实现 Selector

实现代码

Server

public static void main(String[] args) throws IOException {
    //创建一个 SocketServerChannel 类似于 listen
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //创建一个 Selector 对象
    Selector selector = Selector.open();
    //绑定端口 类似 listen
    serverSocketChannel.socket().bind(new InetSocketAddress(6666));
    //设置非阻塞的
    serverSocketChannel.configureBlocking(false);
    //把 listen 注册到 selector,关心的事件为 OP_ACCEPT 读写
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    //循环等待客户端连接
    while (true) {
        //使用 selector 等待一秒
        //slelect() 将返回可以操作 socket 的个数 >= 0
        if (selector.select(1000) == 0) {
            System.out.print(".");
            continue;
        }
        //如果有可以操作的 socket
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
        //遍历可以操作的 socket
        while (keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            if (key.isAcceptable() && !key.isReadable()) {
                //怎么又生成一个 channel ? 对 之前的 没有生成,之前是 server .
                SocketChannel channel = serverSocketChannel.accept();
                channel.configureBlocking(false);
                //关联一个 buffer
                channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
            }
            if (key.isReadable()) {
                //拿到 channel.
                SocketChannel channel = (SocketChannel) key.channel();
                ByteBuffer buffer = (ByteBuffer) key.attachment();
                channel.read(buffer);
                System.out.println("客户端发送数据:"+new String(buffer.array()));
            }
            //手动移除 selector 中的 key 防止重复操作.
            keyIterator.remove();
        }
    }
}
}

Client

public class Client {
    public static void main(String[] args) throws IOException, InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Runnable() {
                @Override
                public void run() {
                    try {
                        SocketChannel channel = SocketChannel.open();
                        channel.configureBlocking(false);
                        InetSocketAddress address = new InetSocketAddress(6666);
                        //连接
                        if (!channel.connect(address)) {
                            System.out.println("没有连接成功,连接需要时间,可以做其他事情");
                            while (!channel.finishConnect()) {
                                Thread.sleep(1000);
                                System.out.print(".");
                            }
                        }
                        //连接成功的事情.
                        String str = "Hi There";
                        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
                        channel.write(buffer);
                    } catch (Exception e) {
                    }
                }
            }.run();
        }
        while (true) {
            Thread.sleep(1000);
            System.out.print(".");
        }
    }
}
slectorepoll

2. C 实现

需要在 linux 上面运行

3. golang 实现

其实 golang 和 C 语言实现差不多,都是调用 linux 系统调用

需要在 linux 上面运行

问题:多路复用有哪几种方式,有什么区别?

linux中有3种常见的多路复用方式1.selector 2.poll 3.epoll

就绪链表

但是在 fd 比较少且比较活跃的时候,epoll 因为需要给每个连接注册回调函数,所以性能也许会差一些。

问题 Goland 需要 NIO 吗?为什么?

因为 goland 并不会阻塞线程,他的协程本来就是非阻塞的,具体参考 golang GMP 模型和原理

golang 的网络 IO 本来就是底层使用的 epoll
参考:
https://zhuanlan.zhihu.com/p/108509080