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
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.
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
servidor
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?