通道(Channel )定义
通道是连接IO服务(如:文件、套接字)的管道,并提供与该服务进行交互的方法。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
Channel相比IO中的Stream更加高效,可以异步双向传输,但是需要和buffer缓冲区一起使用。
通道与传统IO流的区别
- 通道可以是双向的,既可以读取数据,又可以写数据到通道。但流只能读取或者只能写入。
- 通道可以异步的读写。
- 通道不能直接访问数据,需要和Buffer进行交互。
通道的分类
- File通道:FileChannel
- 获取方式:
1.通过RandomAccessFile、FileOutPutSteam、FileInputStream这样的打开的对象的getChannel方法获取。
2.通过FileChannel的open方式获取
- Socket通道:SocketChannel、ServerSocketChannel、DatagramSocketChannel
- 获取方式:
1、通过Socket、ServerSocket、DatagramSocket的getChannel()方法获取
2、通过SocketChannel、ServerSocketChannel、DatagramSocketChannel的open方法获取
主要实现类
- FileChannel:用于读写文件中的数据。
- SocketChannel:用于TCP的数据读写,一般是客户端实现
- ServerSocketChannel:一般是服务器实现,允许我们监听TCP连接请求,每个请求会创建会一个SocketChannel
- DatagramChannel:用于UDP的数据读写
通道直接的数据传输方式
- read和write
outChannel.write(buffer)//将数据从缓冲区写入通道
inChannel.read(buffer)//将数据从通道写入缓冲区
public static void readAndWrite() throws Exception{
RandomAccessFile readFile = new RandomAccessFile("F://readFile.txt", "rw");
RandomAccessFile writeFile = new RandomAccessFile("F://readFile.txt", "rw");
FileChannel inChannel = readFile.getChannel();
FileChannel outChannel = writeFile.getChannel();
ByteBuffer buff = ByteBuffer.allocate(1024);
while(inChannel.read(buff)!=-1){
buff.flip();
outChannel.write(buff);
buff.clear();
}
outChannel.close();
inChannel.close();
readFile.close();
writeFile.close();
}
- TransFrom
从源信道读取字节到这个通道的文件中。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数。这种方法可能比从源通道读取并写入此通道的简单循环更有效率。
public static void transferFrom() throws Exception {
RandomAccessFile rw = new RandomAccessFile("F://readFile.txt", "rw");
FileChannel readFile = rw.getChannel();
RandomAccessFile rwd = new RandomAccessFile("F://writeFile.txt", "rw");
FileChannel writeFile = rwd.getChannel();
writeFile.transferFrom(readFile, 0, readFile.size());
readFile.close();
writeFile.close();
}
- TransTo
将字节从这个通道的文件传输到给定的可写字节通道。
public static void transferTo() throws Exception {
RandomAccessFile rw = new RandomAccessFile("F://readFile.txt", "rw");
FileChannel readFile = rw.getChannel();
RandomAccessFile rwd = new RandomAccessFile("F://writeFile.txt", "rw");
FileChannel writeFile = rwd.getChannel();
readFile.transferTo(0, readFile.size(), writeFile);
readFile.close();
writeFile.close();
}
常用方法说明
方 法 | 描 述 |
---|
int read(ByteBuffer dst) | 从Channel到中读取数据到ByteBuffer |
long read(ByteBuffer[] dsts) | 将Channel到中的数据“分散”到ByteBuffer[] |
int write(ByteBuffer src) | 将ByteBuffer到中的数据写入到Channel |
long write(ByteBuffer[] srcs) | 将ByteBuffer[]到中的数据“聚集”到Channel |
long position() | 返回此通道的文件位置 |
FileChannel position(long p) | 设置此通道的文件位置 |
long size() | 返回此通道的文件的当前大小 |
FileChannel truncate(long s) | 将此通道的文件截取为给定大小 |
void force(boolean metaData) | 强制将所有对此通道的文件更新写入到存储设备中 |
MappedByteBuffer map (MapMode mode, long position, long size) | 内存映射文件 |
Socket通道
- 非阻塞模式
Socket通道可以在非阻塞,需要我们手动设置configureBlocking,true-阻塞、false-非阻塞
socketChannel.configureBlocking(false);
判断当前socket通道是否阻塞
socketChannel.isBlocking();
获取 configureBlocking和register方法同步的锁
socketChannel.blockingLock(); - ServerSocketChannel的Api说明
方 法 | 描 述 |
---|
ServerSocketChannel open() | 用于创建ServerSocketChannel对象,使用时需要与SocketChannel绑定 |
final int validOps() | 同选择器一起使用,获取感兴趣的操作 |
ServerSocketChannel bind(SocketAddress local) | 绑定并指定端口的ServerSocket |
abstract SocketChannel accept() | 当创建ServerSocketChannel对象并绑定一个ServerSocket关联的通道之后,调用该方法可以监听客户端的连接请求 |
SocketAddress getLocalAddress() | 获取绑定的Socket地址 |
public static void serverSocketChannelTest() throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress("127.0.0.1",8080));
serverSocketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.wrap("Hello".getBytes());
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel == null) {
Thread.sleep(2000);
} else {
buffer.rewind();
socketChannel.write(buffer);
}
}
}
- SocketChannel的API说明
方 法 | 描 述 |
---|
SocketChannel open() | 静态方法,打开套接字通道(创建SocketChannel实例) |
SocketChannel open(SocketAddress remote) | 静态方法,打开套接字通道并将其连接到远程地址 |
final int validOps() | 返回一个操作集,标识此通道所支持的操作 |
abstract SocketChannel bind(SocketAddress local) | 用于将Socket绑定到一个端口 |
abstract Socket socket() | 获取该SocketChannel关联的Socket(套接字) |
abstract boolean isConnected() | 判断是否已连接此通道的网络套接字 |
abstract boolean isConnectionPending() | 判断此通道上是否正在进行连接操作。 |
abstract boolean connect(SocketAddress remote) | 用于SocketChannel连接到远程地址 |
abstract int read(ByteBuffer dst)、abstract long read(ByteBuffer[] dsts, int offset, int length) | 从通道中读取数据到缓冲区中 |
abstract int write(ByteBuffer src)、abstract long write(ByteBuffer[] srcs, int offset, int length) | 将缓冲区的数据写入到通道中 |
public static void socketChannelTest() throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));
while (true) {
if (!socketChannel.finishConnect()) {
Thread.sleep(1000);
} else {
ByteBuffer readBuffer = ByteBuffer.allocate(512);
while (socketChannel.read(readBuffer) > 0){
byte[] bytes = new byte[readBuffer.position()];
readBuffer.get(bytes);
System.out.println(new String(bytes));
readBuffer.clear();
}
}
}
}
- DatagramChannel的API说明
方法 | 描述 |
---|
static DatagramChannel open() | 创建DatagramChannel实例 |
abstract DatagramChannel bind(SocketAddress local) | 将通道的套接字绑定到本地地址 |
abstract DatagramSocket socket() | 获取该DatagramChannel关联的DatagramSocket对象 |
abstract boolean isConnected() | 是否已连接到套接字 |
abstract DatagramChannel connect(SocketAddress remote) | 用于DatagramChannel连接到远程地址 |
abstract SocketAddress receive(ByteBuffer dst) | 通道此DatagramChannel接受到的数据包 |
abstract int send(ByteBuffer src, SocketAddress target) | 通过此DatagramChannel发送数据包 |
abstract int read(ByteBuffer dst)、abstract long read(ByteBuffer[] dsts, int offset, int length) | 从此通道读取数据包 |
abstract int write(ByteBuffer src)、final long write(ByteBuffer[] srcs) | 将数据写入到此通道 |
public static void datagramChannelTest() throws Exception {
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress("127.0.0.1",8088));
String newData = "This time:" + System.currentTimeMillis();
ByteBuffer buffer2 = ByteBuffer.allocate(48);
buffer2.clear();
buffer2.put(newData.getBytes());
buffer2.flip();
int bytesSent = channel.send(buffer2, new InetSocketAddress("127.0.0.1", 8090));
ByteBuffer buffer = ByteBuffer.allocate(48);
buffer.clear();
channel.receive(buffer);
byte[] bytes = new byte[buffer.position()];
buffer.get(bytes);
System.out.println(new String(bytes));
}