阅读nio文档

This commit is contained in:
asahi
2024-08-28 00:37:24 +08:00
parent 8563c9aca0
commit 6edbdbd79e

View File

@@ -251,3 +251,129 @@ public class CopyFile {
``` ```
在上述示例中,通过`channel.read(buffer)`的返回值来判断是否已经读取到文件末尾,如果返回值为-1那么代表文件读取已经完成。 在上述示例中,通过`channel.read(buffer)`的返回值来判断是否已经读取到文件末尾,如果返回值为-1那么代表文件读取已经完成。
## File Lock
可以针对整个文件或文件的部分进行加锁。文件锁分为独占锁和共享锁,如果获取了独占锁,那么其他进程将无法获取同一文件加锁;如果获取了共享锁,那么其他进程也可以获取同一文件的共享锁,但是无法获取到独占锁。
### 对文件进行上锁
在获取锁时需要在FileChannel上调用lock方法
```java
RandomAccessFile raf = new RandomAccessFile("usefilelocks.txt", "rw");
FileChannel fc = raf.getChannel();
FileLock lock = fc.lock(start, end, false);
```
释放锁方法如下所示:
```java
lock.release();
```
## Network和异步IO
通过异步io可以在没有blocking的情况下读取和写入数据在通常情况下调用read方法会一直阻塞到数据可以被读取而write方法会一直阻塞到数据可以被写入。
异步IO通过注册io事件来实现当例如新的可读数据、新的网络连接到来时系统会根据注册的io时间监听来发送消息提醒。
异步io的好处是可以在同一线程内处理大量的io操作。在传统的io操作中如果要处理大量io操作通常需要轮询、创建大量线程来针对io操作进行处理。
- 轮询对io请求进行排队处理完一个io请求后再处理别的请求
- 创建大量线程针对每个io请求为其创建一个线程进行处理
通过nio可以通过单个线程来监听多个channel的io事件无需轮询也无需额外的事件。
### Selector
nio的核心对象为selector将channel及其感兴趣的io事件类型注册到selector后如果io事件触发那么`selector.select`方法将会返回对应的SelectionKey。
Selector创建如下
```java
Selector selector = Selector.open();
```
### 开启ServerSocketChannel
为了接收外部连接需要一个ServerSocketChannel创建ServerSocketChannel并配置对应ServerSocket的示例如下所示
```java
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
ss.bind(address);
```
### Selection keys
在创建完SelectableChannel之后需要将channel注册到selector可以调用`channel.register`来执行注册操作:
```java
SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
```
上述方法为ServerSocketChannel注册了OP_ACCEPT的事件监听OP_ACCEPT事件当新连接到来时将会被触发OP_ACCEPT是适用于ServerSocketChannel的唯一io事件类型。
register方法的返回值SelectionKey代表channel和selector的注册关系当io事件触发时selector也会返回事件关联的SelectionKey。
除此之外SelectionKey也可以被用于取消channel对selector的注册。
### inner loop
在向selector注册完channel之后会进入到selector的循环
```java
while (true) {
selector.select();
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
// ... deal with I/O event ...
}
}
```
在调用`selector.select`方法时改方法会阻塞直到有至少一个注册的io事件被触发。`selector.select`会返回触发事件的个数。
`select.selectedKeys()`方法则是会返回被触发事件关联的SelectionKey集合。
对于每个SelectionKey需要通过`key.readyOps()`来判断事件的类型,并以此对其进行处理。
### 为新连接注册
当新连接到来后需要将新连接注册到selector中
```java
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// 监听新连接的READ事件类型
sc.register(selector, SelectionKey.OP_READ);
```
### remove SelectionKey
在完成对io事件的处理之后必须要将SelectionKey从selectedKeys之中移除否则selectionKey将会被一直作为活跃的io事件
```java
it.remove();
```
### 新连接有可读数据
当注册的新连接传来可读数据之后,可读数据处理方式如下:
```java
else if ((key.readyOps() & SelectionKey.OP_READ)
== SelectionKey.OP_READ) {
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
```
## CharsetEncoder/CharsetDecoder
通过`CharsetEncoder/CharsetDecoder`,可以进行`CharBuffer``ByteBuffer`之间的转化。
```java
// 获取字符集
Charset latin1 = Charset.forName("ISO-8859-1");
// 通过字符集创建encoder/decoder
CharsetDecoder decoder = latin1.newDecoder();
CharsetEncoder encoder = latin1.newEncoder();
CharBuffer cb = decoder.decode(inputData);
ByteBuffer outputData = encoder.encode(cb);
```