java BIO和NIO的使用方式

本文记录 java BIO 和 NIO 的使用方式,以方便查阅。

BIO

使用的是 ServerSocket 和 Socket

1
2
3
4
5
6
7
8
// 创建一个ServerSocket
ServerSocket server = new ServerSocket(port);

// 接受远程连接,创建Socket
Socket socket = server.accept();

// 创建线程处理请求
new Thread(new TimeServerHandler(socket)).start();

当接受到连接请求创建 Socket 对象,每个 Socket 交给一个线程处理,为提高资源利用率,可使用线程池处理 Socket 事件

1
2
3
4
5
6
7
8
9
10
11
12
BufferedReader in = null;
PrintWriter out = null;

// 获取输入流与输出流
in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
out = new PrintWriter(this.socket.getOutputStream(), true);

String body = in.readLine();
// 处理读入的数据
// ...
// 输出结果
out.println(...);

NIO

使用的是 ServerSocketChannel 和 Selector,其中 Selector 用于实现多路复用,可以将多个 channel 注册到上去

1
2
3
4
5
6
7
8
9
10
11
12
13
private Selector selector;
private ServerSocketChannel serverSocketChannel;

// 创建Selector
selector = Selector.open();
// 创建ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
// 配置为非阻塞
serverSocketChannel.configureBlocking(false);
// 注册到selector上,关注accept事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
// 绑定地址端口
serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);

必须将 ServerSocketChannel 配置为非阻塞,否则会报如下错误

1
2
3
Exception in thread "main" java.nio.channels.IllegalBlockingModeException
at java.nio.channels.spi.AbstractSelectableChannel.register(AbstractSelectableChannel.java:201)
at java.nio.channels.SelectableChannel.register(SelectableChannel.java:280)

从 selector 上获取事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 指定超时时间的select操作
selector.select(1000);
// 从上一步返回,如果有事件则selectedKeys返回事件对于的key
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
SelectionKey key = null;
// 遍历key并处理
while (iterator.hasNext()) {
key = iterator.next();
iterator.remove();
try {
handleInput(key);
} catch (IOException e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}

拿到事件 key 后,需要判断事件的类型,然后进行相应的处理。

如果是 accept 事件,则创建 SocketChannel 并注册到 selector 上,等待 read 事件

1
2
3
4
5
6
7
8
9
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
// 接受请求
SocketChannel sc = ssc.accept();
// 配置为非阻塞
sc.configureBlocking(false);
// 注册到selector上,关注read事件
sc.register(selector, SelectionKey.OP_READ);
}

如果是 read 事件,则通过 ByteBuffer 读取数据

1
2
3
4
5
6
7
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
// 读取数据到readBuffer中
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
//...
}

若要输出数据,可调用 SocketChannel 的 write 方法写入所需数据

1
2
3
4
5
6
7
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
// 往writeBuffer写数据

// 切换为读模式
writeBuffer.flip();
// 写到SocketChannel
sc.write(writeBuffer);