JavaNIO中的Channel类似Stream,可以读写,通常与Buffer进行交互,不存储数据,只负责在具体的io设备与buffer之间搬运数据。

概述

分类

Java NIO中Channel的实现类,分为:

  • FileChannel,连接一个文件的通道,对文件进行读,写,映射到直接内存操作;
  • DatagramChannel,通过 UDP 读写网络中的数据通道;
  • SocketChannel,通过 TCP 读写网络中的数据通道;
  • ServerSocketChannel,可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。

Channel和Buffer的交互关系

注: 这里所说的I/O设备为文件、套接字等。

从I/O设备中读数据:

  1. 创建对应的通道,如FileChannel创建一个文件读写的通道;
  2. 调用通道的read(buffer)方法,通道会向Buffer中填充I/O设备中的数据;
  3. 填充完成后,应用程序从buffer中读取数据。

向I/O设备中写数据:

  1. 创建对应的通道和缓存;
  2. 应用程序将要写的数据,先put进buffer中;
  3. 调用通道的write()方法,通道会将数据输出到I/O设备中。

FileChannel

一个简单的例子:

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
private static void fileChannelDemo() {  
try (RandomAccessFile file = new RandomAccessFile(new File("Benchmark.log"), "rw")){
// 打开一个文件并建立channel
FileChannel fileChannel = file.getChannel();
// 创建一个buffer
ByteBuffer buffer = ByteBuffer.allocate(512);
// 读channel并填充到buffer中
int readLen = fileChannel.read(buffer);
while (readLen !=-1) {
byte[] temp = new byte[512];
log.info("读取长度:{}", readLen);
// 读buffer
buffer.flip();
buffer.get(temp, 0, buffer.limit());
log.info("数据:{}", new String(temp));
buffer.clear();
readLen = fileChannel.read(buffer);
}
buffer.clear();
buffer.put("完成".getBytes());
// 切换成读模式
buffer.flip();
fileChannel.write(buffer);
} catch (Exception e) {
log.error("channel 读写失败:{}", e.getMessage());
}
}

结果:打印出文件中的内容,并在文件的最后添加了完成。

ServerSocketChannel

ServerSocketChannel 负责监听连接,服务端使用,在监听到 TCP 连接时会产生一个 SocketChannel 实例与客户端进行连接和数据交互。一般为了支持并发,服务端在产生 SocketChannel 之后可以通道实例放到一个队列中,用一个线程池去处理队列中的通道。
使用步骤:
打开一个ServerSocketChannel通道,并绑定端口号:

1
2
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8190));

阻塞接收连接:

1
2
// 循环等待连接的到来  
SocketChannel client = ssc.accept();

处理SocketChannel,读取,与buffer的交互:

1
2
3
4
5
// 读客户端的数据  
ByteBuffer buffer = ByteBuffer.allocate(1024);
client.read(buffer);
buffer.flip();
System.out.println("client : "+new String(buffer.array()));

关闭资源:

1
client.close();

SocketChannel

SocketChannel 负责 TCP 套接字的连接和数据传输,客户端和服务端都需要用到。SocketChannel 是线程安全的,支持多线程访问。
SocketChannel 有阻塞连接和非阻塞连接两种模式。对于阻塞连接,读取数据时会阻塞,直到有数据过来或者连接被关闭;对于非阻塞连接,调用 read() 方法时无论是否有数据都会立即返回。可以调用 configureBlocking(boolean block) 来配置为阻塞通道或非阻塞通道。
使用步骤:
打开并连接服务端的套接字:

1
2
SocketChannel socketChannel =  
SocketChannel.open(new InetSocketAddress("localhost", 8190));

配置通道的阻塞还是非阻塞的方式:

1
2
3
4
5
6
// 配置是否阻塞,阻塞在读取数据的时候,没有数据是否阻塞等待  
socketChannel.configureBlocking(false);
// 如果是非阻塞的模式,需要保证已经完成了连接,才能往下处理,读取数据
while (!socketChannel.finishConnect()) {
ThreadUtil.sleep(100);
}

通道和buffer的读写:

1
2
3
4
5
6
7
ByteBuffer buffer = ByteBuffer.allocate(256);  
// 数据交互
buffer.put("发送给服务端的数据".getBytes());
buffer.flip();
socketChannel.write(buffer);
// 哦通知关闭
socketChannel.shutdownOutput();

DatagramChannel

数据报通道 DatagramChannel 表示 UDP 通道。UDP 是无连接协议,在收发数据时不需要进行连接。与 FileChannel 和 SocketChannel 使用 read()/write() 不同,DatagramChannel 通常使用 receive()/send() 来收发数据。receive() 在接收数据之后会返回发送方的地址,send() 方法在发送数据的时候需要指定接收方的地址。

DatagramChannel 支持阻塞模式和非阻塞模式。非阻塞模式时,receive(ByteBuffer dst) 方法会立即返回,如果有数据,则会返回发送方的地址;如果没有数据,则返回 null。类似地,非阻塞模式下 send(ByteBuffer src, SocketAddress) 也会立即返回,返回的结果为发送的字节数。

服务端使用步骤:
打开通道并且绑定端口:

1
2
DatagramChannel dc = DatagramChannel.open(); 
dc.bind(new InetSocketAddress(9190));

数据交互:
使用receive 和 send方法,

1
2
SocketAddress address = channel.receive(buf); 
channel.send(buf, address);

关闭通道

1
channel.close();

客户端使用步骤:
不需要绑定端口,但是要指定服务端的地址:

1
2
// 定义服务端的地址  
InetSocketAddress serverAddress = new InetSocketAddress("localhost", 9190);

调用send的时候传这个地址。