上一节已经讲过,Buffer的源码及使用,这节讲解以下Java NIO中的通道。
一、简介
通道是Java NIO中非常重要的一部分。它需要配合ByteBffer来完成读写等操作。与传统的流式 I / O 中的数据单向流动不同,通道中的数据可以双向流动。通道既可以读又可以写,想一下上节的例子,卡车既可以从矿藏拉矿石,也可以将矿石运送到矿藏。
二、相关操作
通道类型分为两种,一种是面向文件的,一种是面向网络的。
- FileChannel
- DatagramChannel
- SockerChannel
- ServerSocketChannel
如上所示,NIO通道覆盖了文件IO、TCP和UDP网络IO等通道类型。接下来一一介绍。
1 文件通道(FileChannel)
FileChannel是一个用于连接文件的通道,通过该通道,既可以从文件中读取,也可以向文件中写入数据。与SocketChannel不同,FileChannel无法设置为非阻塞模式,这意味它只能运行在阻塞模式下。
(1)创建通道
由于FileChannel是一个抽象类,可以通过InputStream、OutputStream或RandomAccessFile等实例获取一个FileChannel实例。
(2)读写操作
在读写操纵之前,需要了解以下知识:
现在使用的操作系统,将内存分为内核空间和用户空间。操作系统的内核和一些硬件的驱动程序就在内核空间中,而用户态空间就是我们自己编写程序以及运行的空间。当我们调用read()从磁盘中读取数据时,内核会首先将数据读取到内核空间中,然后再将数据从内核中复制到用户空间中,也就是说,需要两次复制才能到用户空间。同理,将用户空间的数据复制到磁盘中,也需要两次的复制。
- 常规的读写操作
前一节已经讲过,通过allocate()方法分配的内存在jvm中,在进行读写操作的时候,需要先将jvm中的数据复制到内核态的内存中,然后再有操作系统进行写出操作。
- 快捷的读写操作
可以通过TransferTo()、TransferFrom()方法,进行更加快捷的读写操作。这种方法直接在底层直接调用底层的本地方法,在内核空间直接分配地址,减少了复制的次数,提高了数据读写的效率。
(3) 内存映射
这个概念源自操作系统,指将虚拟空间的地址映射到实际的物理内存中。
二、套接字通道
套接字即socket,很多操作系统都提供了自己的socket接口实现。通过socket接口,就可以和不同地址的计算机实现通信。
在Unix/Linux系统下的socket接口:
对于TCP服务端,接口调用的顺序为
socket() --> bind() -->listen() --> accept() --> 其他操作 --> close()
客户端的顺序为:
socket() --> connect() --> 其他操作 --> close()
1、通道类型
Java 套接字通道包含三种类型,对应于两种通信协议TCP和UDP,分别是:
2、读写操作(以TCP网络套接字通道为例)
SocketChannel 和ServeSocketChannel都是抽象类,需要通过open()方法创建。
服务端代码:
public class TcpServerChannel {
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8080));
System.out.println("服务端启动:监听8080端口:");;
SocketChannel channel = ssc.accept();
System.out.println("接受来自" + channel.getRemoteAddress().toString().replace("/", "") + " 请求");
while (true){
ByteBuffer buffer = ByteBuffer.allocate(128);
channel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes));
}
}
客户端代码:
/**
* @author Time
* @created 2019/8/26
*/
public class TcpChannel {
public static void main(String[] args) throws IOException {
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost",8080));
Scanner sc = new Scanner(System.in);
while (true){
System.out.println("请输入:");
String msg = sc.nextLine();
// 向服务端发送消息
ByteBuffer buffer = ByteBuffer.allocate(128);
buffer.put(msg.getBytes());
buffer.flip();
channel.write(buffer);
}
}
}
效果:
三、总结
到这里关于通道的相关内容就结束了,==但其实这里我们使用的仍然时阻塞式I/O,一个TCP服务端只能处理一个客户端连接,关于非阻塞式在下节进行讲解学习。==本文仅从使用的角度的分析了通道的用法。现在的高级语言提供的很多类无非是对操作系统底层一些系统调用的封装, 想往更高层次的发展,应该多关注一些底层的原理。