学习完nio的一个小笔记吧
阅读原文时间:2023年07月08日阅读:3
  • 这是一个nio网络通信服务端的demo,主要就学习了selector的一些用法,以及它里面的事件类型
  • selector是对nio的一个优化,它能保证既能高效处理线程中的事件,又能保证线程不会一直占用cpu
其中我认为最重要的是selector的执行流程,机制
  • 将 channel和 selector建立联系。(联系就是指客户端向服务端发送的某个事件会被selector给监听到)。

  • 当客户端给服务器发送了相应事件后,selector就会将这个事件的 selectionKey加入到集合 selectionKeys中,并且处理该事件。

  • 在处理事件的时候,可以根据事件类型来进行处理。比如说使用 channel.accept()、channel.read(xx)来处理。

  • 对一些客户端异常关闭,或者说正常关闭要特殊处理。客户端关闭,可以直接调 key.cancel()将该key从 selector中删除。

服务端
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

import static com.pzistart.utils.ByteBufferUtil.debugRead;

/**
 * @author Pzi
 * @create 2022-09-12 17:47
 */
@Slf4j
public class ServerUp {

    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel
                .open()
                .bind(new InetSocketAddress(8080));

        //  1.创建 selector,管理多个 channel
        Selector selector = Selector.open();

        //  2.开启非阻塞模式,并且将ssc注册到selector中
        ssc.configureBlocking(false);

        //  3.建立 ssc(channel) 和 selector 的联系
        SelectionKey sscKey = ssc.register(selector, SelectionKey.OP_ACCEPT);
        log.debug("regist key:{}", sscKey);

        while (true) {
            //  如果selector监听到有事件发生,那么就向 selectionKeys 这个集合中加入key。
            //  并且执行下面的操作,否则就阻塞线程
            int count = selector.select();

            // selectionKeys中包含所有的事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();

            //  4.处理事件
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //  在selectionKeys中即使是处理完毕事件,也不会自动移除key。
                //  导致下一次处理事件的时候,还会找到事件类型为 accept 的那个 SelectionKey
                //  但是由于accept事件已经被消耗,所以获取不到对应的 ServerSocketChannel。所以在用完该key,就要立马移除集合中该key
                iterator.remove();

                //  如果客户端发过来的是 accept相关的事件,那么就会被 selector 识别并且将该时间加入到 selectionKeys集合中。从而在下面的事件处理分支进行处理。
                //  如果客户端发过来的是 read相关的之间,同样的事件处理方式
                if (key.isAcceptable()) {
                    log.debug("key{}", key);
                    ServerSocketChannel c = (ServerSocketChannel) key.channel();
                    //  调用accept()方法处理事件
                    SocketChannel sc = c.accept();
                    sc.configureBlocking(false);
                    //  将sc注册到selector中,建立sc(channel)和selector的联系
                    SelectionKey scKey = sc.register(selector, SelectionKey.OP_READ);
                    log.debug("connected... {}", sc);
                    // 也可以调用cancel()方法处理事件 key.cancel();
                } else if (key.isReadable()) {
                    try {
                        SocketChannel channel = (SocketChannel) key.channel();
                        //  将sc注册到selector中
                        ByteBuffer buffer = ByteBuffer.allocate(16);
                        int read = channel.read(buffer);
                        if (read == -1) {
                            key.cancel();
                        } else {
                            buffer.flip();
                            debugRead(buffer);
                            buffer.clear();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        //  在客户端异常断开连接时,客户端会向服务器发送一个 read()事件
                        //  read()事件没有得到处理,那么就会多次被 selector监听到,从而循环抛异常。
                        //  解决方法就是将该 key删除,就是直接从 selector中反注册,那么 selector自然不会监听到该 channel的相应事件
                        //  key.cancel();
                    }
                }
            }
        }
    }
}
客户端
package com.pzistart.netcoding.bio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/**
 * @author Pzi
 * @create 2022-09-12 15:04
 */
public class Client {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost", 8080));
        System.out.println("ccc");
    }

}