Java NIO--(3)Channel通道类

633 阅读3分钟

Java NIO 系列文章

  1. 高并发IO的底层原理及4种主要IO模型
  2. Buffer的4个属性及重要方法
  3. Channel通道类
  4. Selector选择器

大家知道,从广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。然而,远远不止如此,除了可以对应到底层文件描述符,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();
    }
}