java nio channel 学习笔记
Channel直译就是通道的意思,通道表示对数据源头和数据目标流经途径的抽象描述,和io中的InputStream和OutputStream类似。
首先借用网络上一张Channel的类图:
从channel的类层次结构来看在接口层面有区分读和写两种操作(ReadableByteChannel和WritableByteChannel),这点类似InputStream和OutputStream。但在实现类FileChannel和SocketChannel都实现了读写的接口,也就是既可以从通道中读取数据,又可以写数据到通道,这和io中单向的流是不同的,即nio中的通道是可以双向读写。
从类图中也可以看到nio中几个重要的通道实现:
FileChannel, SocketChannel,ServerSocketChannel, DatagramChannel。
1. FileChannel
文件的读写通道。可从现有的 FileInputStream 、 FileOutputStream 或 RandomAccessFile 对象获得文件通道,方法是调用该对象的 getChannel 方法,这会返回一个连接到相同底层文件的文件通道。
文件通道的状态与其 getChannel 返回该通道的对象密切相关。
在各种情况下指定要求“允许读取操作”、“允许写入操作”或“允许读取和写入操作”的某个实例。
通过 FileInputStream 实例的 getChannel 方法所获得的通道将允许进行读取操作。
通过 FileOutputStream 实例的 getChannel 方法所获得的通道将允许进行写入操作。
如果使用模式 "r" 创建 RandomAccessFile 实例,则通过该实例的 getChannel 方法所获得的通道将允许进行读取操作,如果使用模式 "rw" 创建实例,则获得的通道将允许进行读取和写入操作。
FileInputStream fi = new FileInputStream(infile);
FileOutputStream fo = new FileOutputStream(outfile);
FileChannel fiChannel = fi.getChannel();
FileChannel foChannel = fo.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (fiChannel.read(buffer) != -1) {
buffer.flip();
foChannel.write(buffer);
buffer.clear();
}
RandomAccessFile raf = new RandomAccessFile(fileName , "rw");
FileChannel fc = raf.getChannel();
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
mbb.put(0, (byte) 97);
mbb.put(1023, (byte) 122);
2. SocketChannel 和 ServerSocketChannel 是对原io包中的 Socket 和 ServerSocket 的可选择的无阻塞通道实现。从类结构来看都实现了SelectableChannel,结合selector实现无阻塞io,Selector为ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel 监控连接服务器就绪, 读就绪和写就绪事件。
ServerSocketChannel稍微特殊,并没有实现ReadableByteChannel,专门负责监听传入的连接和创建新的 SocketChannel对象.由于ServerSocketChannel没有bind( )方法,因此有必要取出对等的socket并使用它来绑定到一个端口以开始监听连接。
下面一个简单的例子是一阻塞等待请求连接,单线程处理的方式。无阻塞模式要结合Selector事件驱动模型使用。
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(port));
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
//...
}
}
结合Selector使用的简单无阻塞模式:
selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.socket().bind(new InetSocketAddress(port));
ssc.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
int selected = selector.select();
if (selected > 0) {
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = selectedKeys.next();
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
selectedKeys.remove();
} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
SocketChannel sc = (SocketChannel) key.channel();
IoBuffer buf = IoBuffer.allocate(1024);
int readBytes = 0;
try {
int ret;
while ((ret = sc.read(buf.buf())) > 0) {
readBytes += ret;
if (!buf.hasRemaining()) {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
buf.flip();
}
// 处理buf中的数据。。。
sc.register(selector, SelectionKey.OP_WRITE);
selectedKeys.remove();
} else if ((key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
SocketChannel sc = (SocketChannel) key.channel();
String writeInfo = "Server received success !";
sc.write(Charset.forName("UTF-8").encode(writeInfo));
sc.close();
key.cancel();
}
}
}
}
3. DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入,它发送和接收的是数据包。