0%

java | nio-selector 基础以及 accept 事件处理

使用 nio-selector

引入自写的方法类。

下载 JAVA 文件

netty 包

1
2
3
4
5
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</dependency>

事件

  • accept
    • 会在有连接请求时触发
  • connect
    • 客户端连接建立后触发
  • read
    • 可读事件
  • write
    • 可写事件

事件发生后,要么处理,要么取消,不能置之不理。

代码

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package com.redisc;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static com.redisc.ByteBufferUtil.debugRead;

@Slf4j(topic = "c.Test")
public class Run {

public static void main(String[] args) throws IOException {
// 创建 selector,管理多个 channel
Selector selector = Selector.open();
// 创建服务器
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);//将阻塞模式切换为非阻塞模式

// 建立 selector 和 channel 的联系「注册」
// SelectionKey 事件发生后,通过它知道事件和哪个 channel 事件
SelectionKey sscKey = ssc.register(selector, 0, null);
// key 只关注 accept 事件
sscKey.interestOps(SelectionKey.OP_ACCEPT);
log.debug("register key:{}", sscKey);

//绑定端口
ssc.bind(new InetSocketAddress(8090));
while (true) {
// selector 方法,没有事件发生,线程阻塞,有事件发生,线程才会恢复运行
// selector 在事件未处理的时候,不会阻塞
selector.select();
// 处理事件,selectorKeys 内部包含了所有发生事件
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
log.debug("key:{}", key);
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();
log.debug("{}", sc);
}

}
}

}

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.redisc;

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

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

使用

开启服务端,并开启两个客户端。

输出

1
2
3
4
5
10:22:35.361 [main] DEBUG c.Test - register key:sun.nio.ch.SelectionKeyImpl@497470ed
10:22:38.740 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed
10:22:38.744 [main] DEBUG c.Test - java.nio.channels.SocketChannel[connected local=/127.0.0.1:8090 remote=/127.0.0.1:59237]
10:22:41.689 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed
10:22:41.689 [main] DEBUG c.Test - java.nio.channels.SocketChannel[connected local=/127.0.0.1:8090 remote=/127.0.0.1:59242]

假设不处理事件

如果,我们把 while 循环中的事件处理给注释掉。服务端代码的 46 - 48

1
2
3
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel sc = channel.accept();
log.debug("{}", sc);

开启服务端,开启一个客户端。

会进行无限循环。

1
2
3
4
5
10:25:30.210 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed
10:25:30.210 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed
10:25:30.210 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed
10:25:30.210 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed
10:25:30.210 [main] DEBUG c.Test - key:sun.nio.ch.SelectionKeyImpl@497470ed

这是因为,selector.select(); 在事件未处理的时候不会阻塞。当有时间没有处理,会被重新添加,直到时间处理了。

事件取消

如果,就是不想处理事件,可以进行事件取消,将服务器 45 - 48 代码替换为

1
key.cancel();

这样就不会进入无限循环了。

请我喝杯咖啡吧~