Noções básicas do Netty --- Componentes NIO --- Seletor (4)

Seletor

O seletor pode ser usado quando usamos um thread para processar eventos de conexão de vários clientes. O seletor usa um mecanismo de pesquisa para detectar se um evento ocorreu em cada canal de conexão. Se ocorrer um evento, podemos agir de acordo. Depois de usar o seletor, o thread de leitura e gravação são iniciadas apenas quando um evento de leitura e gravação realmente ocorre, o que percebe que um thread gerencia vários canais e reduz a sobrecarga da troca de contexto de vários threads

Vamos dar uma olhada no diagrama do modelo do Seletor
Insira a descrição da imagem aqui

O acima é um diagrama esquemático, o que significa que depois que vários clientes são registrados no Seletor, quando ocorre um evento de leitura ou gravação, o Seletor inicia um encadeamento para concluir as operações de leitura e gravação

1. Como não bloqueamos, quando não há tarefa de leitura ou gravação, o thread pode fazer outro trabalho

2. Por exemplo, este canal não tem operações de leitura e gravação, mas outros canais têm, e a thread pode realizar operações de leitura e gravação em outros canais, evitando a sobrecarga da troca de contexto causada pelo bloqueio frequente de thread de E / S

3. O trabalho de comutação de canais é feito pelo Seletor, que usa um mecanismo de votação para detectar se há dados no canal

4. Assim como o thread de IO do Netty, NioEventLoop integra o Selector, apresentaremos o Netty a você nos próximos capítulos, vamos estabelecer a base primeiro

Antes de olhar para o código-fonte do Selector, tenho que apresentar o SelectionKey para você primeiro

SelectionKey

SelectionKey Identifica a relação de registro entre o Seletor e o Canal, e atualmente existem 4 tipos exibidos no código-fonte:

	//发生读事件
	public static final int OP_READ = 1 << 0;
	
	//发生写事件
    public static final int OP_WRITE = 1 << 2;
    
	//表示已连接
    public static final int OP_CONNECT = 1 << 3;
    
	//表示有新的网络可以连接,一般用在serverSocketChannel
    public static final int OP_ACCEPT = 1 << 4;

Vamos dar uma olhada no código-fonte do SelectionKey

Código-fonte do SelectionKey

public abstract class SelectionKey {
    
    
    /**
     * Constructs an instance of this class.
     */
    protected SelectionKey() {
    
     }

	//得到与SelectionKey关联的Channel(通道)
    public abstract SelectableChannel channel();

	//得到Selector
    public abstract Selector selector();

	//判断此SelectionKey是否有效
    public abstract boolean isValid();

	//取消该SelectionKey,也就是断掉了Channel与Selector的关联
    public abstract void cancel();

	/**
	*检索此SelectionKey的兴趣集。
	*
	*<p>保证返回集只包含对该SelectionKey通道有效的操作位。
	*
	*此方法可随时调用。它是否阻塞以及阻塞多长时间取决于实现。你知道吗
	*
	*如果此SelectionKey已被取消则抛出CancelledKeyException
	*/
    public abstract int interestOps();

	//设置或改变监听事件
    public abstract SelectionKey interestOps(int ops);

	//返回该SelectionKey已就绪的操作级
    public abstract int readyOps();

	//是否可读
	public final boolean isReadable() {
    
    
        return (readyOps() & OP_READ) != 0;
    }

	//是否可写
    public final boolean isWritable() {
    
    
        return (readyOps() & OP_WRITE) != 0;
    }

	//是否已连接
    public final boolean isConnectable() {
    
    
        return (readyOps() & OP_CONNECT) != 0;
    }

	//是否可以连接
    public final boolean isAcceptable() {
    
    
        return (readyOps() & OP_ACCEPT) != 0;
    }

	//得到与SelectionKey关联的共享数据
	public final Object attachment() {
    
    
	        return attachment;
    }

Código fonte do seletor

public abstract class Selector implements Closeable {
    
    


    protected Selector() {
    
     }

	//获取一个选择器
    public static Selector open() throws IOException {
    
    
        return SelectorProvider.provider().openSelector();
    }
    
    //获得SelectionKey的数据集
    public abstract Set<SelectionKey> selectedKeys();

	//不阻塞直接返回
	public abstract int selectNow() throws IOException;

	//设置监听事件的超时时间,阻塞
    public abstract int select(long timeout)
        throws IOException;

	//唤醒Selector
    public abstract Selector wakeup();

	//关闭该Selector
    public abstract void close() throws IOException;

}

Combate real

Depois de entender os três principais componentes do NIO, vamos digitar o código para consolidar nosso conhecimento existente. Agora vamos simular a comunicação simples entre o servidor e o cliente. Primeiro, vamos tirar uma foto para mostrar a arquitetura geral.

Insira a descrição da imagem aqui

Deixe-me explicar a você o seguinte diagrama de arquitetura acima

1. Crie uma classe de implementação do Seletor, crie um ServerSocketChannel, defina o número da porta que precisa ser monitorado e, em seguida, registre o ServerSocketChannel no Seletor, concentre-se no evento para criar uma nova conexão e, em seguida, o Seletor faz um loop para obter a SelectionKey para ver se há uma nova conexão

2. Quando um cliente se conecta ao servidor, ou seja, quando uma nova conexão é criada, o seletor escuta um evento de conexão e obtém o SocketChannel chamando ServerSocketChannel.accept (), e então registra este canal no Seletor. O seletor pode registrar vários SocketChannel

3. Após o registro, haverá um SelectionKey associado ao Seletor e SocketChannel, e posteriormente usaremos o SelectionKey para obter os dados reversamente no Canal e no buffer

Vamos ao código para ter uma ideia

public class Server {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            //得到一个Selector对象
            Selector selector = Selector.open();

            //得到一个ServerSocketChannel对象
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //绑定端口号
            serverSocketChannel.socket().bind(new InetSocketAddress(7001));
            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
            //注册进selector
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            //循环监听事件
            while (true){
    
    
				//设置阻塞时间!!重要,可做心跳检测
                while (selector.select(5000) == 0){
    
    
                    System.out.println("服务器等待5s未有新连接~~~");
                    continue;
                }
                //获取SelectionKey的Set集合
                Set<SelectionKey> selectionKeySet = selector.selectedKeys();
                //遍历selectionKeySet
                Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();

                while(selectionKeyIterator.hasNext()){
    
    
                    //获取SelectionKey
                    SelectionKey selectionKey = selectionKeyIterator.next();

                    //有连接事件发生
                    if(selectionKey.isAcceptable()){
    
    
                        //获取连接到服务器的SocketChannel
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        //设置为非阻塞
                        socketChannel.configureBlocking(false);
                        //注册进selector,并为其设置缓冲区
                        socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(2048));
                        System.out.println("有新连接创建!!!");
                    }
                    //有读事件发生
                    if(selectionKey.isReadable()){
    
    
                        //获取SocketChannel
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                        //获取缓冲区内的数据
                        ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                        //将缓冲区中的数据读到Channel中
                        socketChannel.read(byteBuffer);
                        //打印出来看看
                        System.out.println(new Date() + "服务端发送数据: " + new String(byteBuffer.array()));
                    }
                    //移除处理过的SelectionKey
                    selectionKeyIterator.remove();
                }
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

Em seguida, defina um cliente

public class Client {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            //得到一个SocketChannel对象
            SocketChannel socketChannel = SocketChannel.open();
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //设置连接端口
            InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost",7001);
            //测试是否连接成功
            if(!socketChannel.connect(inetSocketAddress)){
    
    
                while (!socketChannel.finishConnect()){
    
    
                    System.out.println("等待连接中~~~");
                }
            }
            //创建一个缓冲区,并写入数据
            ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
            byteBuffer.put("客户端测试数据,测试数据----".getBytes());
            System.out.println("客户端测试数据,测试数据----");
            byteBuffer.flip();
            //将数据写入Channel
            socketChannel.write(byteBuffer);
            while (true){
    
    
                byteBuffer.flip();
                String str = String.valueOf(System.in.read());
                byteBuffer.clear();
                byteBuffer.put(str.getBytes());
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
            }

        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

Eu marquei a função de cada método no método. Usando NIO, podemos realizar uma comunicação de mensagem simples entre o servidor e o cliente. Vamos dar uma olhada nos resultados.
Backend do cliente Backend do
Insira a descrição da imagem aqui
servidor
Insira a descrição da imagem aqui
Vamos escrever um no próximo artigo Mensagem comunicação entre o servidor e vários clientes para completar a função de chat em grupo

Término: 2021/3/28 15:37 ALiangX

Marque o autor original para reimpressão. Obrigado por seu apoio. Você pode ter cuidado?
Insira a descrição da imagem aqui

Acho que você gosta

Origin blog.csdn.net/ALiangXLogic/article/details/115281814
Recomendado
Clasificación