Java NIO 系列文章
大家知道,从广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。然而,远远不止如此,除了可以对应到底层文件描述符,Java NIO的通道还可以更加细化。例如,对应不同的网络传输协议类型(TCP&UDP),在Java中都有不同的NIO Channel(通道)实现。
Channel的主要类型
FileChannel文件通道,用于文件的数据读写SocketChannel套接字通道,用于Socket套接字TCP连接的数据读写ServerSocketChannel服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求,创建一个SocketChannel套接字通道。DatagramChannel数据报通道,用于UDP协议的数据读写。
这个四种通道,涵盖了文件IO、TCP网络IO、UDP网络IO。下面从Channel(通道)的获取、读取、写入、关闭四个重要的操作,来对四种通道进行简单的介绍。
FileChannel文件通道
FileChannel是专门操作文件的通道。通过FileChannel,既可以从一个文件中读取数据,也可以将数据写入到文件中。
需要注意的是,FileChannel是阻塞模式,不能设置为非阻塞模式。
FileChannel的获取、读取、写入、关闭
通过一个文件复制(a.txt -> b.txt)的例子来介绍FileChannel的获取、读取、写入、关闭
public static void copyFile() throws IOException {
String srcPath = ClassLoader.getSystemResource("").getPath() + "a.txt";
String destPath = ClassLoader.getSystemResource("").getPath() + "b.txt";
FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath);
// 1 获取FileChannel 通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 2 从通道中读取数据 放入 byteBuffer 缓冲区 中
while (inChannel.read(byteBuffer) != -1) {
// 切换到 读模式
byteBuffer.flip();
// 3 从byteBuffer读取数据 写入到 FileChannel 中
while (outChannel.write(byteBuffer) != 0) {
}
// 清空缓冲区 切换到写模式
byteBuffer.clear();
}
// 4 强制刷新到 硬盘
outChannel.force(true);
// 5 关闭通道
inChannel.close();
outChannel.close();
}
SocketChannel套接字通道
在NIO中,涉及网络连接的通道有两个
SocketChannel负责连接传输ServerSocketChannel负责连接的监听
无论是ServerSocketChannel,还是SocketChannel,都支持阻塞和非阻塞两种模式。如何进行模式的设置呢?调用configureBlocking方法,具体如下:
socketChannel.configureBlocking(false)设置为非阻塞模式。socketChannel.configureBlocking(true)设置为阻塞模式。
在阻塞模式下,SocketChannel通道的connect连接、read读、write写操作,都是同步的和阻塞式的,在效率上与Java旧的IO的面向流的阻塞式读写操作相同。
下面通过一个客户端向服务端发送消息的例子来介绍SocketChannel 的获取,读取,写入,关闭等操作
客户端
public class SocketChannelClient {
private static final String HOSTNAME = "127.0.0.1";
private static final int PORT = 8890;
private static final SocketAddress socketAddress = new InetSocketAddress(HOSTNAME, PORT);
public static void main(String[] args) throws IOException {
client();
}
private static void client() throws IOException {
// 获得 套接字传输通道
SocketChannel socketChannel = SocketChannel.open(socketAddress);
// 设置为 非阻塞
socketChannel.configureBlocking(false);
while (!socketChannel.finishConnect()) {
// 非阻塞情况下,connect会立即返回 客户端连接服务端的结果,可能并未建立连接,connect就已经返回了
// 因此需要不断的自旋,检查当前是否连接到了服务器
}
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("hello world".getBytes());
// 写入前需要读取缓冲区,要求ByteBuffer是读取模式
byteBuffer.flip();
socketChannel.write(byteBuffer);
//终止输出方法,向对方发送一个输出的结束标志
socketChannel.shutdownOutput();
// 关闭套接字连接
socketChannel.close();
}
}
服务端
public class SocketChannelServer {
private static final String HOSTNAME = "127.0.0.1";
private static final int PORT = 8890;
private static final SocketAddress socketAddress = new InetSocketAddress(HOSTNAME, PORT);
public static void main(String[] args) throws IOException {
server();
}
private static void server() throws IOException {
// 获取服务器套接字
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 改为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 绑定ip+port
serverSocketChannel.bind(socketAddress);
// 自旋 等待 客户端连接
SocketChannel socketChannel = null;
while ((socketChannel = serverSocketChannel.accept()) == null) {
}
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 从socketChannel中读取数据 写入到 byteBuffer中
while (socketChannel.read(byteBuffer) != -1) {
// 切换到 读模式
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
// 情况缓冲区 切换到 写模式
byteBuffer.clear();
}
// 关闭套接字
socketChannel.close();
serverSocketChannel.close();
}
}