Java 代码之 NIO

1.wiki

传统 IO 是一次一个字节地处理数据,NIO 是以块(缓冲区)的形式处理数据。最主要的是,NIO可以实现非阻塞,而传统IO只能是阻塞的。三个核心部分:

  • Buffer 是存储数据的地方
  • Channel 是运输数据的载体
  • Selector 用于检查多个 Channel 的状态变更情况

2.代码

server

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class NoBlockServer {

public static void main(String[] args) throws IOException {

// 1. 获取通道
ServerSocketChannel server = ServerSocketChannel.open();

// 2. 切换成非阻塞模式
server.configureBlocking(false);

// 3. 绑定连接
server.bind(new InetSocketAddress(6666));

// 4. 获取选择器
Selector selector = Selector.open();

// 4.1. 将通道注册到选择器上,指定接收“监听通道”事件
server.register(selector, SelectionKey.OP_ACCEPT);

// 5. 轮训地获取选择器上已“就绪”的事件--->只要 select()>0,说明已就绪
while (selector.select() > 0) {
// 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

// 7. 获取已“就绪”的事件,(不同的事件做不同的事)
while (iterator.hasNext()) {

SelectionKey selectionKey = iterator.next();

// 接收事件就绪
if (selectionKey.isAcceptable()) {

// 8. 获取客户端的链接
SocketChannel client = server.accept();

// 8.1. 切换成非阻塞状态
client.configureBlocking(false);

// 8.2. 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
client.register(selector, SelectionKey.OP_READ);

} else if (selectionKey.isReadable()) { // 读事件就绪

// 9. 获取当前选择器读就绪状态的通道
SocketChannel client = (SocketChannel) selectionKey.channel();

// 9.1. 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 9.2. 得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

while (client.read(buffer) > 0) {
// 在读之前都要切换成读模式
buffer.flip();

outChannel.write(buffer);

// 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
}
}
// 10. 取消选择键(已经处理过的事件,就应该取消掉了)
iterator.remove();
}
}

}
}

client

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class NoBlockClient {

public static void main(String[] args) throws IOException {

// 1. 获取通道
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

// 1.1. 切换成非阻塞模式
socketChannel.configureBlocking(false);

// 1.2. 获取选择器
Selector selector = Selector.open();

// 1.3. 将通道注册到选择器中,获取服务端返回的数据
socketChannel.register(selector, SelectionKey.OP_READ);

// 2. 发送一张图片给服务端吧
FileChannel fileChannel = FileChannel.open(Paths.get("D:\\1.png"), StandardOpenOption.READ);

// 3. 要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
ByteBuffer buffer = ByteBuffer.allocate(1024);

// 4. 读取本地文件(图片),发送到服务器
while (fileChannel.read(buffer) != -1) {

// 在读之前都要切换成读模式
buffer.flip();

socketChannel.write(buffer);

// 读完切换成写模式,能让管道继续读取文件的数据
buffer.clear();
}

// 5. 轮训地获取选择器上已“就绪”的事件--->只要 select()>0,说明已就绪
while (selector.select() > 0) {
// 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

// 7. 获取已“就绪”的事件,(不同的事件做不同的事)
while (iterator.hasNext()) {

SelectionKey selectionKey = iterator.next();

// 8. 读事件就绪
if (selectionKey.isReadable()) {

// 8.1. 得到对应的通道
SocketChannel channel = (SocketChannel) selectionKey.channel();

ByteBuffer responseBuffer = ByteBuffer.allocate(1024);

// 9. 知道服务端要返回响应的数据给客户端,客户端在这里接收
int readBytes = channel.read(responseBuffer);

if (readBytes > 0) {
// 切换读模式
responseBuffer.flip();
System.out.println(new String(responseBuffer.array(), 0, readBytes));
}
}

// 10. 取消选择键(已经处理过的事件,就应该取消掉了)
iterator.remove();
}
}
}

}