Java NIO 中的 Channel 详解(中)

简介: Channel是一个通道,可以通过它读取和写入数据,它就像是水管一样,网络数据通过 Channel 进行读取和写入。通道和流的不同之处在与通道是双向的,流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStram 的子类),而且通道上可以用于读,写或者同事用于读写。因为 Channel 是全双工的,所以它可以比流更好的映射底层操作系统的 API。

FileChannel 的 truncate 方法


可以使用 FileChannel.truncate() 方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:


channel.truncate(1024);


这个例子是截取文件前 1024 个字节


FileChannel 的 force 方法


FileChannel.force() 方法将通道里尚未写入磁盘的数据强制写到磁盘上。处于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到 FileChannel 里的数据一定会即时写入到磁盘上。要保证这一点需要调用 force 方法。


force() 方法有一个 boolean 类型的参数,指明是否将文件元数据(权限信息等)写到磁盘上。


FileChannel 的 transferTo 和 transferFrom


通道之前的两数据传输:


如果两个通道中有一个是 FileChannel , 那你可以直接将数据从一个 channel 传输到另外一个 channel 。


(1)transferForm() 方法


fileChannel 的 transforFrom() 方法可以将数据重源通道传输到 FileChannel 中(译者注:这个方法在 JDK 文档中的解释为将字节从给定的可读取字节通道传输到此通道的文件中)。下面是一个 FileChannel 完成文件间的复制的例子:


public class FileChannelWrite {
  public static void main(String[] args) throws IOException {
    // 创建 FileChannel
    RandomAccessFile fromFile = new RandomAccessFile("C:\a.txt", "rw");
    FileChannel formChannel = fromFile.getChannel();
    // 创建 FileChannel
    RandomAccessFile toFile = new RandomAccessFile("C:\c.txt", "rw");
    FileChannel toChannel = toFile.getChannel();
    long position = 0;
    long count = formChannel.size();
    toChannel.transferFrom(formChannel, position, count);
    formChannel.close();
    toChannel.close();
    System.out.println("end");
  }
}


方法的输入参数 position 表示从 position 处于开始向目标文件写入数据,count 表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则传输的自己数要小于请求的字节数。此外还要注意,在 SocketChannel 实现中。SocketChannel 只会传输此刻已经准备好的数据(可能不足 count 字节)。因此 SocketChannel 可能不会将请求的所有数据( count 个字节)全部传输到 FileChannel 中。


(1)transferTo() 方法


transferTo() 方法是将数据从 FileChannel 传输到其他的 channel 中

下面是一个 transferTo() 方法的例子:


public class FileChannelWrite2 {
  public static void main(String[] args) throws IOException {
    // 创建 FileChannel
    RandomAccessFile fromFile = new RandomAccessFile("C:\a.txt", "rw");
    FileChannel formChannel = fromFile.getChannel();
    // 创建 FileChannel
    RandomAccessFile toFile = new RandomAccessFile("C:\c.txt", "rw");
    FileChannel toChannel = toFile.getChannel();
    long position = 0;
    long count = formChannel.size();
    formChannel.transferTo(position, count, toChannel);
    formChannel.close();
    toChannel.close();
    System.out.println("end");
  }
}


Socket 通道


(1)新的 Socket 通道类可以运行非阻塞模式并且是可选择的,可以激活大程序(如网络服务器和中间件组件)巨大的可伸缩性和灵活性。本节中我们会看到,再也没有为每个 socket 连接使用一个线程的必要了,也避免了管理大量线程所需的上下文交换开销。借助新的 NIO 类。一个或几个线程就可以管理成百上千的活动 socket 连接了并且只有很少甚至可能没有性能损失。所有的 socket 通道类(DatagramChannel 、SocketChannel和 ServeSocketChannel ). 都继承了 java.nio.channel.spi 包中的 AbstractSelectableChannel . 这意味着我们可以用一个 Selector 对象来执行 socket 通道的就绪选择 (readiness selection)。


(2) 请注意 DatagramChannel 和 SocketChannel 实现定义读和写功能的接口而 ServerSocketChannel 不实现。ServerSocketChannel 负责监听传入的连接和创建新的 SocketChannel 对象,它本身不传输数据。


(3) 我们在具体讨论每一种 socket 通道前,您还应该了解 sokcet 和 socket 通道之间的关系。通道是一个连接 I/O 服务导管并提供与该服务交互的方法。就某个 Socket 而言,它不会再次实现与之对应的 socket 通道类中的 socket API 协议,而 java.net 中已经存在的 socket 通道都可以被大多数协议操作重复使用


全部 socket 通道类(DatagramChannel 、SocketChannel、ServerSocketChannel)在被实例化都会创建一个对等的 Socket 对象。这些使我们所熟悉的来自 java.net 的类(Socket、ServerSocket 和 DatagramSocket), 它们已经被更新以识别通道。对等 socket 可以通过 socket() 方法从一个通道上获取。此外,这三个 java.net 类都有

getChannel() 方法。


(4)把一个 socket 通道置于非阻塞模式,我们要依靠所有 socket 通道类的公有超类:SelectableChannel 。就绪选择(readiness selection)是一种可以用来查询通道的机制,该查询可以判断通道是否准备好执行一个目标操作,如读或写。非阻塞 I/O 和可选择性是紧密连接的,那也是正是管理阻塞模式的 API 代码要在 SelectableChannel 超级类中定义的原因。


设置或重新设置一个通道的阻塞模式是简单的,只要调用configureBlocking() 方法即可,传递参数值为 true 则设为阻塞模式,参数值为 flase 值设为非阻塞模式。可以通过调用 isBlocking() 方法来判断某个 socket 同党当前处于那种模式。


AbstractSelectableChannel.java 中实现的configureBlocking ( boolean block) 方法如下:


public final SelectableChannel configureBlocking(boolean block)
    throws IOException
    {
        synchronized (regLock) {
            if (!isOpen())
                throw new ClosedChannelException();
            if (blocking == block)
                return this;
            if (block && haveValidKeys())
                throw new IllegalBlockingModeException();
            implConfigureBlocking(block);
            blocking = block;
        }
        return this;
}


非阻塞 socket 通常被认定为是服务端使用的, 因为它们使同时管理很多 socket 通道变得更加容易。但是,在客户端使用一个或几个非阻塞模式的 socket 通道也是有益处的,例如,借助非阻塞 socket 通道, GUI 程序可以专注于用户请求并且同时维护与一个或多个服务器的会话。在很多程序上,非阻塞模式都是有用的。


偶尔地,我们也会需要防止 socket 通道的阻塞模式被更改。 API 中有一个 blockinglock 方法,该方法会返回一个非透明的对象引用。返回的对象是通道实现修改阻塞模式时内部使用的。只有拥有此对象的锁的线程才能更改通道的阻塞模式。

下面分别是这3个通道的介绍:


ServerSocketChannel


ServerSocketChannel 是一个基于通道的 socket 监听器。它同我们所熟悉的 java.net.ServerSocket 执行相同的任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。


由于 ServerSocketChannel 没有 bind() 方法,因此有必要取出对等的 socket 并使用它来绑定到一个端口以开启监听连接。我们也就是使用 ServerSocket 的 API 来根据需要设置其他的 socket 选项。


同 java.net.ServeerSocket 一样,ServerScocketChannel 也有 accept() 方法。一旦创建了一个 ServerSocketChannel 并用对等的 socket 绑定了它,然后您就可以在其中一个上调用 accept() 。 如果您选择 ServerSocket 上调用 accept() 方法,那么它会同任何其他的 ServerSocket 变现一样的行为:总是阻塞并返回一个 java.net.Socket 对象。如果您选择在 ServerSocketChannel 上调用 accept() 方法则会返回

ServerSocketChannel 类型的对象,返回的对象能在非阻塞模式下运行。


换句话说:


ServerSocketChannel 的 accept() 方法会返回 SocketChannel 类型对象

SocketChannel 可以在非阻塞模式下运行。


其他 Socket 的 accept() 方法会阻塞返回 Socket 对象。


如果 ServerSocketChannel 以非阻塞模式被调用,当没有传入连接等待时

ServerSocketChannel.accept() 会立即返回 null. 正是这种检查连接而不阻塞的能力实现了伸缩性并且降低了复杂性。可选择性因此得到实现。我们可以使用一个选择器实例来注册 ServerSocketChannel 对象以实现新连接达到自动通知的功能。


public class ServerSocketChannelDemo {
  public static void main(String[] args) throws IOException, InterruptedException {
    int port = 8888;
    ByteBuffer buffer = ByteBuffer.wrap("hell0 world!".getBytes(StandardCharsets.UTF_8));
    ServerSocketChannel socketChannel = ServerSocketChannel.open();
    socketChannel.socket().bind(new InetSocketAddress(port));
    socketChannel.configureBlocking(false);
    while (true) {
      System.out.println("Waiting for connections");
      SocketChannel sc = socketChannel.accept();
      if (sc == null) {
        System.out.println("null");
        TimeUnit.SECONDS.sleep(2);
      } else {
        System.out.println("Incoming conection form: " +
            sc.socket().getRemoteSocketAddress());
        buffer.rewind();
        sc.write(buffer);
        sc.close();
      }
    }
  }
}


访问的结果


image.png


(1)打开 ServerSocketChannel


调用 ServerSocketChannel.open() 方法来打开 ServerSocketChannel


ServerSocketChannel socketChannel = ServerSocketChannel.open();


(2)关闭 ServerSocketChannel


调用 ServerSocketChannel.close() 方法来打开 ServerSocketChannel


socketChannel.close()


(3)监听新的连接


通过调用 ServerSocketChannel.close() 方法来监听新进的连接。当 accept() 方法返回时,它返回一个包含进来新的 SocketChannel . 因此, accept() 方法会一直阻塞知道有新的连接到达。


通常不仅仅只监听一个连接。在 while 循环中调用 accept 方法。如下面的例子:


while (true) {
      System.out.println("Waiting for connections");
      SocketChannel sc = socketChannel.accept();
      if (sc == null) {
        System.out.println("null");
        TimeUnit.SECONDS.sleep(2);
      } else {
        System.out.println("Incoming conection form: " +
            sc.socket().getRemoteSocketAddress());
        buffer.rewind();
        sc.write(buffer);
        sc.close();
      }
    }


(4)阻塞模式


image.png


会在 SocketChannel sc = socketChannel.accept();这里阻塞住进程。


(5)非阻塞模式


ServerSocketChannel 可以设置为非阻塞模式。在非阻塞模式下,accept() 方法会立刻返回, 如果没有新的连接进来将会是返回 null , 因此,需要检查返回的 SocketChannel 是否是 null , 如:


SocketChannel sc = socketChannel.accept();
if (sc == null) {
    System.out.println("null");
    TimeUnit.SECONDS.sleep(2);
} else {
    System.out.println("Incoming conection form: " +
                       sc.socket().getRemoteSocketAddress());
    buffer.rewind();
    sc.write(buffer);
    sc.close();
}


SocketChannel


SocketChannel 介绍


JAVA NIO 中 SocketChannel 是一个连接到 TCP 网络套接字的通道

A selectable channel for stream-oriented connection sockets

以上是 Java docs 中对于 SocketChannel 的描述: SocketChannel 是一种面向连接 socket 涛姐字的可选择通道从这里可以看出:


  • SocketChannel 是用来连接 Socket 套接字


  • SocketChannel 主要用途是用来处理网络 I/O 通道


  • SocketChannel 是基于 TCP 连接传输


  • SocketChannel 实现了可选择通道,可以被多路复用


相关文章
|
5月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
20天前
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
2月前
|
Java
让星星⭐月亮告诉你,Java NIO之Buffer详解 属性capacity/position/limit/mark 方法put(X)/get()/flip()/compact()/clear()
这段代码演示了Java NIO中`ByteBuffer`的基本操作,包括分配、写入、翻转、读取、压缩和清空缓冲区。通过示例展示了`position`、`limit`和`mark`属性的变化过程,帮助理解缓冲区的工作原理。
31 2
|
3月前
|
存储 网络协议 Java
Java NIO 开发
本文介绍了Java NIO(New IO)及其主要组件,包括Channel、Buffer和Selector,并对比了NIO与传统IO的优势。文章详细讲解了FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel及Pipe.SinkChannel和Pipe.SourceChannel等Channel实现类,并提供了示例代码。通过这些示例,读者可以了解如何使用不同类型的通道进行数据读写操作。
Java NIO 开发
|
4月前
|
Java
"揭秘Java IO三大模式:BIO、NIO、AIO背后的秘密!为何AIO成为高并发时代的宠儿,你的选择对了吗?"
【8月更文挑战第19天】在Java的IO编程中,BIO、NIO与AIO代表了三种不同的IO处理机制。BIO采用同步阻塞模型,每个连接需单独线程处理,适用于连接少且稳定的场景。NIO引入了非阻塞性质,利用Channel、Buffer与Selector实现多路复用,提升了效率与吞吐量。AIO则是真正的异步IO,在JDK 7中引入,通过回调或Future机制在IO操作完成后通知应用,适合高并发场景。选择合适的模型对构建高效网络应用至关重要。
89 2
|
4月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
121 0
|
5月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
167 1
|
4月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
75 0
|
5月前
|
安全 Java
【Java】已解决java.nio.channels.OverlappingFileLockException异常
【Java】已解决java.nio.channels.OverlappingFileLockException异常
138 1
|
5月前
|
Java
【Java】已解决java.nio.channels.ClosedChannelException异常
【Java】已解决java.nio.channels.ClosedChannelException异常
419 1
下一篇
无影云桌面