这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
channel简介
NIO中Selector是中央控制器,Buffer是承载数据的容器,而Channel是通道。channel是打开到IO设备的连接。类似流,但又有些不同:它既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。 通道可以异步地读写。通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
NIO中通过channel封装了对数据源的操作,通过 channel 我们可以操作数据源,但又不必关心数据源的具体物理结构。这个数据源可能是多种的。比如,可以是文件,也可以是网络socket。在大多数应用中,channel 与文件描述符或者socket 是一一对应的。
channel分类
Java IO可以分为文件类和Stream类两大类。Channel 也相应地分为了FileChannel 和 Socket Channel,其中 socket channel 又分为三大类,一个是用于监听端口的ServerSocketChannel,第二类是用于TCP通信的SocketChannel,第三类是用于UDP通信的DatagramChannel。
Channel的类别与应用
FileChannel: 文件的数据读写;
DatagramChannel: UDP的数据读写;
SocketChannel: TCP的数据读写,一般是客户端实现;
ServerSocketChannel: 可以监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现。
FileChannel
使用FileChannel读取数据到Buffer(缓冲区)以及利用Buffer(缓冲区)写入数据到FileChannel。
FileChannel是抽象类,无法直接打开FileChannel,需要通过InputStream、OutputStream或RandomAccessFile获取FileChannel。然后从FileChannel读取/写入数据, 再关闭FileChannel。
public class FileChannel {
public static void main(String[] args) throws IOException {
//创建一个RandomAccessFile对象
RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\user.txt", "rw");
java.nio.channels.FileChannel fileChannel = randomAccessFile.getChannel();
//创建一个读数据缓冲区对象
ByteBuffer byteBufferRead = ByteBuffer.allocate(48);
//从通道中读取数据
int bytesRead = fileChannel.read(byteBufferRead);
//创建一个写数据缓冲区对象
ByteBuffer byteBufferWrite = ByteBuffer.allocate(48);
//写数据
byteBufferWrite.put("FileChannel test".getBytes());
byteBufferWrite.flip();
fileChannel.write(byteBufferWrite);
while( bytesRead != -1) {
System.out.println("Read "+bytesRead);
//Buffer在写模式下调用flip()之后,Buffer从写模式变成读模式
byteBufferRead.flip();
//读取buffer中的数据
while (byteBufferRead.hasRemaining()) {
System.out.print((char) byteBufferRead.get());
}
//清空缓冲区(切换到写模式)
byteBufferRead.clear();
bytesRead = fileChannel.read(byteBufferRead);
}
//关闭RandomAccessFile对象
randomAccessFile.close();
}
}
SocketChannel和ServerSocketChannel的使用
服务器端
1.通过ServerSocketChannel 绑定ip地址和端口号;
2.通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据;
3.创建读数据/写数据缓冲区对象来读取客户端数据或向客户端发送数据;
- 关闭SocketChannel和ServerSocketChannel;
public class Server {
public static void main(String[] args) {
try {
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannelOne = ServerSocketChannel.open();
serverSocketChannelOne.socket().bind(new InetSocketAddress("127.0.0.1",8080));
serverSocketChannelOne.configureBlocking(false);
//注册channel并指定监听的事件
serverSocketChannelOne.register(selector, SelectionKey.OP_ACCEPT);
ServerSocketChannel serverSocketChannelTwo = ServerSocketChannel.open();
serverSocketChannelTwo.socket().bind(new InetSocketAddress("127.0.0.1",8090));
serverSocketChannelTwo.configureBlocking(false);
//注册channel并指定监听的事件
serverSocketChannelTwo.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer readBuff = ByteBuffer.allocate(1024);
ByteBuffer writeBuff = ByteBuffer.allocate(1024);
writeBuff.put("received".getBytes());
writeBuff.flip();
while (true) {
int nReady = selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isConnectable()) {
System.out.println(Thread.currentThread().getId()+"start Connectable....");
} else if (selectionKey.isAcceptable()) {
System.out.println(Thread.currentThread().getId()+"start Acceptable....");
ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}else if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
readBuff.clear();
socketChannel.read(readBuff);
readBuff.flip();
System.out.println(Thread.currentThread().getId()+"-received:"+ new String(readBuff.array()));
selectionKey.interestOps(SelectionKey.OP_WRITE);
} else if (selectionKey.isWritable()) {
writeBuff.rewind();
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.write(writeBuff);
selectionKey.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端
1.通过SocketChannel连接到远程服务器;
2.创建读数据/写数据缓冲区对象来读取服务端数据或向服务端发送数据;
3.关闭SocketChannel;
public class Client {
public static void main(String[] args) throws Exception{
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
ByteBuffer writeBuffer = ByteBuffer.allocate(32);
ByteBuffer readBuffer = ByteBuffer.allocate(32);
writeBuffer.put("hello".getBytes());
writeBuffer.flip();
while (true) {
writeBuffer.rewind();
socketChannel.write(writeBuffer);
readBuffer.clear();
socketChannel.read(readBuffer);
}
}
}
DatagramChannel使用
public class Server {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
DatagramChannel channel= DatagramChannel.open();
channel.configureBlocking(false);
channel.socket().bind(new InetSocketAddress(8080));
channel.register(selector, SelectionKey.OP_READ);
ByteBuffer byteBuffer = ByteBuffer.allocate(65535);
while (true) {
int n = selector.select();
if (n > 0) {
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = (SelectionKey) iterator.next();
//必须手动删除
iterator.remove();
if (selectionKey.isReadable()) {
DatagramChannel datagramChannel = (DatagramChannel) selectionKey.channel();
byteBuffer.clear();
//读取数据
InetSocketAddress inetSocketAddress = (InetSocketAddress)datagramChannel
.receive(byteBuffer);
System.out.println(new java.lang.String(byteBuffer.array()));
//删除缓冲区的数据
byteBuffer.clear();
java.lang.String message = "data come from server";
byteBuffer.put(message.getBytes());
byteBuffer.flip();
//发送数据
datagramChannel.send(byteBuffer, inetSocketAddress);
}
}
}
}
}
}
客户端
public class Client {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
DatagramChannel datagramChannel = DatagramChannel.open();
datagramChannel.configureBlocking(false);
SocketAddress socketAddress = new InetSocketAddress("localhost", 8080);
datagramChannel.connect(socketAddress);
datagramChannel.register(selector, SelectionKey.OP_READ);
datagramChannel.write(Charset.defaultCharset().encode("data come from client123!"));
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
while (true) {
int n = selector.select();
if (n > 0) {
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = (SelectionKey) iterator.next();
iterator.remove();
if (selectionKey.isReadable()) {
datagramChannel = (DatagramChannel) selectionKey.channel();
datagramChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array()));
byteBuffer.clear();
datagramChannel.write(Charset.defaultCharset()
.encode("data come from client456!"));
}
}
}
}
}
}
Scatter / Gather
聚集写入( Gathering Writes)是指将多个 Buffer 中的数据“聚集”到 Channel。(照缓冲区的顺序,写入 position 和 limit 之间的数据到 Channel )
分散读取( Scattering Reads)是指从 Channel 中读取的数据“分散” 到多个 Buffer 中。(按照缓冲区的顺序,从 Channel 中读取的数据依次将 Buffer 填满)
Scattering Reads “scattering read”是把数据从单个Channel写入到多个buffer,如下图所示:
Gathering Writes “gathering write”把多个buffer的数据写入到同一个channel中,下面是示意图:
接口定义如下
public interface ScatteringByteChannel extends ReadableByteChannel
{
public long read(ByteBuffer[] dsts) throws IOException;
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException;
}
public interface GatheringByteChannel extends WritableByteChannel
{
public long write(ByteBuffer[] srcs) throws IOException;
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException;
}
接口中定义的方法都传入了一个Buffer数组。 scatter/gather操作就是聚集(gather)这个Buffer数组并写入到一个通道,或读取通道数据并分散(scatter)到这个Buffer数组中。带offset和length参数的read()和write()方法可以让我们只使用缓冲区数组的子集(offset是缓冲区数组索引,不是Buffer数据的索引,length指的是要使用的缓冲区数量)。
通道之间的数据传输
如果一个channel是FileChannel类型的,那么可以直接把数据传输到另一个channel,方法如下:
- transferFrom() :transferFrom方法把数据从通道源传输到FileChannel;
- transferTo() :transferTo方法把FileChannel数据传输到另一个channel;
transferFrom
从源信道读取字节到这个通道的文件中。受限于count,源通道的剩余空间小于 count 个字节时,则所传输的字节数要小于请求的字节数。
public void transferFromTest() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("D:\\From01.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:\\From2.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
outChannel.transferFrom(inChannel,0, inChannel.size());
inChannel.close();
outChannel.close();
}
transferTo
将字节从这个通道的文件传输到给定的可写字节通道。
public void transferToTest() throws Exception{
FileChannel inChannel = FileChannel.open(Paths.get("D:\\to01.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:\\to02.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
}
}