Java NIO之Channel详细理解

蔚落 2023-02-17 03:16 214阅读 0赞

介绍

理解:通道是一个连接I/O服务的管道并提供与该服务交互的方法。

Channel类似于传统的”流”,但是Channel不能直接访问数据,需要和缓冲区Buffer进行交互。

通道和传统流的区别:

  1. 通道可以是双向的,既可以读取数据,又可以写数据到通道。但流的读写通常是单项的

  2. 通道可以异步的读写

  3. 通道不能直接访问数据,需要和Buffer进行交互

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NraDIwMTVqYXZh_size_16_color_FFFFFF_t_70

通道可以简单的分为两大类:文件通道和Socket通道

文件通道

  1. 文件通道的主要实现是FileChannel。文件通道总是阻塞的,因此不能被置于非阻塞模式。

FileChannel的创建

  1. FileChannel对象不能直接创建。一个FileChannel实例只能通过一个打开的file对象(RandomAccessFilFileInputStraemFileOutputStream等)上调用getChannel(0方法获取。调用getChannel()方法返回一个连接到相同文件的FileChannel对象且该FileChannel对象具有于file对象相同的访问权限。

创建FileChannel示例:

  1. //创建一个RandomAccessFile(随机访问文件)对象,
  2. RandomAccessFile raf=new RandomAccessFile("D:\\test.txt", "rw");
  3. //通过RandomAccessFile对象的getChannel()方法。FileChannel是抽象类。
  4. FileChannel inChannel=raf.getChannel();

常用方法

  1. package java.nio.channels;
  2. public abstract class FileChannel extends AbstractChannel implements ByteChannel, GatheringByteChannel, ScatteringByteChannel
  3. {
  4. // This is a partial API listing
  5. // All methods listed here can throw java.io.IOException
  6. //从FileChannel读取数据
  7. public abstract int read(ByteBuffer dst)
  8. public abstract int read (ByteBuffer dst, long position);
  9. //向FileChannel写数据
  10. public abstract int write(ByteBuffer src)
  11. public abstract int write (ByteBuffer src, long position);
  12. //获取文件大小
  13. public abstract long size();
  14. //获取位置
  15. public abstract long position();
  16. //设置位置
  17. public abstract void position (long newPosition);
  18. //用于文件截取
  19. public abstract void truncate (long size);
  20. //将通道里尚未写入磁盘的数据强制写到磁盘上
  21. public abstract void force (boolean metaData);
  22. //文件锁定,position-开始位置,size-锁定区域的大小,shared-表示锁是否共享(false为独占),lock()锁定整个文件
  23. public final FileLock lock();
  24. public abstract FileLock lock (long position, long size, boolean shared);
  25. public final FileLock tryLock();
  26. public abstract FileLock tryLock (long position, long size, boolean shared);
  27. //内存映射文件
  28. public abstract MappedByteBuffer map (MapMode mode, long position, long size);
  29. public static class MapMode;
  30. public static final MapMode READ_ONLY;
  31. public static final MapMode READ_WRITE;
  32. public static final MapMode PRIVATE;
  33. //用于通道之间的数据传输
  34. public abstract long transferTo (long position, long count, WritableByteChannel target);
  35. public abstract long transferFrom (ReadableByteChannel src, long position, long count);
  36. }

public abstract MappedByteBuffer map (MapMode mode, long position, long size)

  1. map()方法可以在一个打开的文件和一个特殊类型的ByteBuffer之间建立虚拟内存映射。
  2. 通过内存映射机制来访问一个文件会比使用常规方法读写高效得多,甚至比使用通道的效率都高。

transferTo()将数据从FileChannel传输到其他的Channel中。

transferFrom()从其他Channel获取数据

transferTo()和transferFrom()方法允许将一个通道交叉连接到另一个通道,而不需要通过一个中间缓存来传递数据。只有FileChannel类有这两个方法。

transferFrom()示例

  1. package test;
  2. import java.io.IOException;
  3. import java.io.RandomAccessFile;
  4. import java.nio.channels.FileChannel;
  5. public class FileChannelTest2 {
  6. public static void main(String[] args) throws IOException {
  7. RandomAccessFile aFile = new RandomAccessFile("d:\\ fromFile.txt", "rw");
  8. FileChannel fromChannel = aFile.getChannel();
  9. RandomAccessFile bFile = new RandomAccessFile("d:\\ toFile.txt", "rw");
  10. FileChannel toChannel = bFile.getChannel();
  11. long position = 0;
  12. long count = fromChannel.size();
  13. toChannel.transferFrom(fromChannel, position, count);
  14. aFile.close();
  15. bFile.close();
  16. System.out.println("over!");
  17. }
  18. }

transferTo()示例

  1. package test;
  2. import java.io.IOException;
  3. import java.io.RandomAccessFile;
  4. import java.nio.channels.FileChannel;
  5. public class FileChannelTest3 {
  6. public static void main(String[] args) throws IOException {
  7. RandomAccessFile aFile = new RandomAccessFile("d:\\fromFile.txt", "rw");
  8. FileChannel fromChannel = aFile.getChannel();
  9. RandomAccessFile bFile = new RandomAccessFile("d:\\toFile.txt", "rw");
  10. FileChannel toChannel = bFile.getChannel();
  11. long position = 0;
  12. long count = fromChannel.size();
  13. fromChannel.transferTo(position, count, toChannel);
  14. aFile.close();
  15. bFile.close();
  16. System.out.println("over!");
  17. }
  18. }

FileChannel示例

  1. package test;
  2. import java.io.IOException;
  3. import java.io.RandomAccessFile;
  4. import java.nio.ByteBuffer;
  5. import java.nio.channels.FileChannel;
  6. public class FileChannelTest {
  7. public static void main(String[] args) throws IOException {
  8. RandomAccessFile aFile = new RandomAccessFile("d:\\test.txt", "rw");
  9. FileChannel inChannel = aFile.getChannel();
  10. ByteBuffer buf = ByteBuffer.allocate(48);
  11. int bytesRead = inChannel.read(buf);
  12. while (bytesRead != -1) {
  13. System.out.println("Read " + bytesRead);
  14. buf.flip();
  15. while (buf.hasRemaining()) {
  16. System.out.print((char) buf.get());
  17. }
  18. buf.clear();
  19. bytesRead = inChannel.read(buf);
  20. }
  21. aFile.close();
  22. System.out.println("wan");
  23. }
  24. }

Socket**通道**

常用的Socket通道

DatagramChannel:用于UDP的数据读写

SocketChannel: 用于TCP的数据读写,一般是客户端实现

ServerSocketChannel: 允许我们监听TCP连接请求,每个请求会创建会一个SocketChannel,一般是服务器实现

以上Channel都继承AbstractSelectableChannel,于是这三个Channel都是可以设置成非阻塞模式的。Socket通道(DatagramChannel、SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对等的socket对象,即我们熟悉的java.net的类型(Socket、ServerSocket和DatagramSocket)。

1.非阻塞模式

Socket通道可以在非阻塞模式下运行,跟非阻塞/阻塞相关的函数(SelecableChannel类下)

//配置是否是阻塞模式(block为true,则为阻塞模式。block为false,则设置为非阻塞模式)

  1. public abstract SelectableChannel configureBlocking(boolean block)
  2. //获取当前是否是阻塞模式
  3. public abstract boolean isBlocking();
  4. //获取 configureBlocking和register方法同步的锁
  5. public abstract Object blockingLock();

2.ServerSocketChannel

ServerSocketChannel用于监听TCP连接请求,常用的API如下:

  1. public abstract class ServerSocketChannel
  2. extends AbstractSelectableChannel
  3. implements NetworkChannel
  4. {
  5. //静态方法,用于创建一个新的ServerSocketChannel对象,后续还需要跟ServerSocket进行绑定操作
  6. public static ServerSocketChannel open()
  7. //获取关联该ServerSocketChannel的server socket
  8. public abstract ServerSocket socket()
  9. //当创建ServerSocketChannel对象并绑定一个ServerSocket关联的通道之后,
  10. //调用该方法可以监听客户端的连接请求
  11. public abstract SocketChannel accept()
  12. //并绑定指定端口的ServerSocket,(jdk1.7以上才有)
  13. public final ServerSocketChannel bind(SocketAddress local)
  14. //同选择器一起使用,获取感兴趣的操作
  15. public final int validOps()
  16. }

静态方法open()用于创建一个新的ServerSocketChannel对象,将会返回一个未绑定的ServerSocket关联的通道。该对等ServerSocket可以通过在返回的ServerSocketChannel上调用socket()方法获取。jdk1.7以前ServerSocketChannel没有bind()方法,因此必须取出对等的socket并使用它来绑定一个端口开始监听连接

  1. ServerSocketChannel ssc = ServerSocketChannel.open();
  2. ServerSocket serverSocket = ssc.socket();
  3. serverSocket.bind(new InetSocketAddress(1234));

jdk1.7以后ServerSocketChannel提供了bind()方法,所以以上可以简化为

  1. ServerSocketChannel ssc = ServerSocketChannel.open().bind(new InetSocketAddress(1234));

简单的ServerSocketChannel示例:

  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.ServerSocketChannel;
  5. import java.nio.channels.SocketChannel;
  6. public class ServerSocketChannelDemo {
  7. public static final String GREETING = "Hello I must be going.\r\n";
  8. public static void main(String[] args) throws IOException, InterruptedException {
  9. int port = 8088;
  10. if (args.length > 0){
  11. port = Integer.parseInt(args[0]);
  12. }
  13. ByteBuffer buffer = ByteBuffer.wrap(GREETING.getBytes());
  14. ServerSocketChannel ssc = ServerSocketChannel.open();
  15. ssc.socket().bind(new InetSocketAddress(port));
  16. ssc.configureBlocking(false);
  17. while (true){
  18. System.out.println("Waiting for connections");
  19. SocketChannel sc = ssc.accept();
  20. if (sc == null){
  21. Thread.sleep(2000);
  22. }else {
  23. System.out.println("Incoming connection form:"+sc.socket().getRemoteSocketAddress());
  24. buffer.rewind();
  25. sc.write(buffer);
  26. sc.close();
  27. }
  28. }
  29. }
  30. }

3. SocketChannel

SocketChannel 常见的API如下:

  1. public abstract class SocketChannel extends AbstractSelectableChannel
  2. implements ByteChannel, ScatteringByteChannel, GatheringByteChannel,NetworkChannel
  3. {
  4. //静态方法,打开套接字通道(创建SocketChannel实例)
  5. public static SocketChannel open()
  6. //静态方法,打开套接字通道并将其连接到远程地址
  7. public static SocketChannel open(SocketAddress remote)
  8. //返回一个操作集,标识此通道所支持的操作
  9. public final int validOps()
  10. //用于将Socket绑定到一个端口
  11. public abstract SocketChannel bind(SocketAddress local)
  12. //获取该SocketChannel关联的Socket(套接字)
  13. public abstract Socket socket()
  14. //判断是否已连接此通道的网络套接字
  15. public abstract boolean isConnected()
  16. //判断此通道上是否正在进行连接操作。
  17. public abstract boolean isConnectionPending()
  18. //用于SocketChannel连接到远程地址
  19. public abstract boolean connect(SocketAddress remote)
  20. //从通道中读取数据到缓冲区中
  21. public abstract int read(ByteBuffer dst)
  22. public abstract long read(ByteBuffer[] dsts, int offset, int length)
  23. //将缓冲区的数据写入到通道中
  24. public abstract int write(ByteBuffer src)
  25. public abstract long write(ByteBuffer[] srcs, int offset, int length)
  26. }

创建SocketChannel对象并连接到远程地址

  1. SocketChannel sc = SocketChannel.open(new InetSocketAddress(ip,port));

等价于

  1. SocketChannel sc = SocketChannel.open();
  2. sc.connect(new InetSocketAddress(ip,port));
  3. 线程在连接建立好或超时之前都保持阻塞。
  4. 示例:
  5. import java.io.IOException;
  6. import java.net.InetSocketAddress;
  7. import java.nio.ByteBuffer;
  8. import java.nio.channels.SocketChannel;
  9. public class SocketChannelDemo {
  10. public static void main(String[] args) {
  11. int port = 8088;
  12. String ip = "127.0.0.1";
  13. ByteBuffer readBuffer = ByteBuffer.allocate(512);
  14. try {
  15. SocketChannel socketChannel = SocketChannel.open();//创建一个socketChannel
  16. socketChannel.connect(new InetSocketAddress(ip,port));
  17. while (!socketChannel.isConnected()){
  18. System.out.println("connecting ...");
  19. Thread.sleep(1000);
  20. }
  21. System.out.println("connected");
  22. socketChannel.write(ByteBuffer.wrap("Hello,I am Client".getBytes()));
  23. while (socketChannel.read(readBuffer) > 0){
  24. System.out.println(readBuffer.toString());
  25. readBuffer.flip();
  26. }
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }

4. DatagramChannel

  1. SocketChannel模拟面向连接的流协议(如TCP/IP),而DatagramChannel则面向无连接的协议(如UDP/IP)
  2. public abstract class DatagramChannel
  3. extends AbstractSelectableChannel
  4. implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, MulticastChannel
  5. {
  6. //创建DatagramChannel实例
  7. public static DatagramChannel open()
  8. public static DatagramChannel open(ProtocolFamily family)
  9. public final int validOps()
  10. //将通道的套接字绑定到本地地址
  11. public abstract DatagramChannel bind(SocketAddress local)
  12. //获取该DatagramChannel关联的DatagramSocket对象
  13. public abstract DatagramSocket socket()
  14. //是否已连接到套接字
  15. public abstract boolean isConnected()
  16. //用于DatagramChannel连接到远程地址
  17. public abstract DatagramChannel connect(SocketAddress remote)
  18. //通道此DatagramChannel接受到的数据包
  19. public abstract SocketAddress receive(ByteBuffer dst)
  20. //通过此DatagramChannel发送数据包
  21. public abstract int send(ByteBuffer src, SocketAddress target)
  22. //从此通道读取数据包
  23. public abstract int read(ByteBuffer dst)
  24. public abstract long read(ByteBuffer[] dsts, int offset, int length)
  25. //将数据写入到此通道
  26. public abstract int write(ByteBuffer src)
  27. public final long write(ByteBuffer[] srcs) throws IOException
  28. }

打开DatagramChannel

DatagramChannel channel = DatagramChannel.open();

channel.socket().bind(new InetAddress(8088));

DatagramChannel对应的DatagramSocket如果未调用bind()绑定端口号,也是可以通讯的,因为系统默认会动态分配一个端口号

receive()方法,接受数据包

  1. ByteBuffer buffer = ByteBuffer.allocate(48);
  2. buffer.clear();
  3. channel.receive(buffer);

send()方法,发送数据包

  1. String newData = "New String to send,time:" + System.currentTimeMillis();
  2. ByteBuffer buffer = ByteBuffer.allocate(48);
  3. buffer.clear();
  4. buffer.put(newData.getBytes());
  5. buffer.flip();
  6. int bytesSent = channel.send(buffer, new InetAddress("jenkov.com", 80));

Connecting to a Specific Address

DatagramChannel是可以“连接”到网络上特定地址的。因为UDP是无连接的,所以这种“连接”不是真正像TCP那样的和远程地址建立了一个连接。不如说是它将锁定你的DatagramChannel,以便你只能向一个特定的地址发送和接收数据包。

连接

channel.connect(new InetAddress(“jenkov.com”, 80));

当“连接”建立后,你可以像使用传统的Channel一样调用read()和write()方法。只是对于发送的数据,你不会得到任何关于交付的保证。下面是一些例子:

int bytesRead = channel.read(buffer); //读取数据

int bytesWrite = channel.write(buffer); //写数据

以上内容主要整理自:《Java NIO》

发表评论

表情:
评论列表 (有 0 条评论,214人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java nio channel

    Java NIO 通道类似于流,但又有一下不同: 1、既可以从通道中读取数据,也可以写数据到通道中。java .io 中的流是单向性。 2、通道数据读取可以异步。 3、通

    相关 Java NIOSelector详细理解

    介绍        Selector一般称为选择器。它是Java NIO核心组件之一,选择器管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,

    相关 Java NIOChannel详细理解

    介绍 理解:通道是一个连接I/O服务的管道并提供与该服务交互的方法。 Channel类似于传统的”流”,但是Channel不能直接访问数据,需要和缓冲区Buffer进行

    相关 Java NIO Channel

    定义 用于源节点和目标节点之间的连接。nio中负责缓冲区中数据传输,Channel本地并不存储数据,而是配合缓冲区进行数据传输。你可以把它理解成io中的流。 结

    相关 NIOChannel

    1、基本概念 Java NIO中,channel用于数据的传输,类似于传统BIO中的流(IOStream)的概念。 我们都知道,系统的I/O都分为两个阶段: 等