• JAVA NIO系列(二) Channel解读


    Channel就是一个通道,用于传输数据,两端分别是缓冲区和实体(文件或者套接字),通道的特点(也是NIO的特点):通道中的数据总是要先读到一个缓冲区,或者总是要从一个缓冲区中读入。

    Channel的分类

    1) FileChannel:从文件中读写数据

    2) SocketChannel:通过TCP协议读写网络中的数据

    3) ServerSocketChannel:在服务器端可以监听新进来的TCP连接,像WEB服务器那样,对每一个新进来的请求创建一个SocketChannel

    4) DatagramChannel:通过UDP协议读写网络中的数据

    上面众多的分类,是对应了不同的实体,这些通道包括了文件IO、TCP和UDP网络IO。

    下面来看看Channel的源码:

     1 public interface Channel extends Closeable {
     2 
     8     public boolean isOpen();
     9 
    27     public void close() throws IOException;
    28 
    29 }

    从这里我们可以看到,Channel接口只提供了关闭通道和检测通道是否打开这两个方法,剩下方法的都是由子接口和实现类来定义提供。

        

    我们选择其中几个来看看这些接口的源码:

    1 public interface WritableByteChannel
    2     extends Channel
    3 {
    4 
    5     public int write(ByteBuffer src) throws IOException;
    6 
    7 }
    public interface ReadableByteChannel extends Channel 
    {
    public int read(ByteBuffer dst) throws IOException; }
    public interface ByteChannel
        extends ReadableByteChannel, WritableByteChannel
    {
    
    }

    前面我提到过:通道可以只读、只写或者同时读写,因为Channel类可以只实现只读接口ReadableByteChannel或者只实现只写接口WritableByteChannel而我们常用的Channel类FileChannel、SocketChannel、DatagramChannel是双向通信的, 因为实现了ByteChannel接口。

    Channel的获取

    IO在广义上可以分为:文件IO和网络IO。文件IO对应的通道为FileChannel,而网络IO对应的通道则有三个:SocketChannel、ServerSoketChannel和DatagramChannel。

    一、文件通道

    FileChannel对象不能直接创建,只能通过FileInputStream、OutputStream、RandomAccessFile对象的getChannel()来获取,如:

    FileInputStream fis = new FileInputStream("c:/in.txt");
    FileChannel fic = fis.getChannel();

     FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

    1)使用通道读取文件

     1 public class NIOFileReadTest
     2 {
     3     public static void main(String[] args) throws IOException
     4     {
     5         RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw");
     6         FileChannel fis = raf.getChannel();
     7         ByteBuffer buffer = ByteBuffer.allocate(1024);
     8         fis.read(buffer);
     9         buffer.flip();
    10         while(buffer.hasRemaining())
    11         {
    12             System.out.print((char)buffer.get());
    13         }
    14         buffer.clear();
    15         fis.close();
    16     }
    17 }

    执行结果:

    FileChannel
    ByteBuffer
    SelectorPicked

    2)使用通道写入文件

    public class NIOFileWriteTest
    {
        public static void main(String[] args) throws Exception
        {
            FileOutputStream fos = new FileOutputStream("d:/out.txt");
            FileChannel fc = fos.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.clear();
            String str = "Channel";
            buffer.put(str.getBytes());
            buffer.flip();
            while(buffer.hasRemaining())
            {
                fc.write(buffer);
            }
            fc.close();
            fos.close();
        }
    }

    在这里总是要记住channel是要关闭的。

    ByteBuffer中的方法我在下一章再详细介绍,这里只要注意这点即可:通道只能使用ByteBuffer,不管是读还是写,通道都要对接缓冲区

    3)通道的常用方法

    position();返回通道的文件位置

    position(long newPosition):设置通道的文件位置

    将上面读文件的程序修改下,来观察这几个方法:

     1 public class NIOFileReadTest
     2 {
     3     public static void main(String[] args) throws IOException
     4     {
     5         RandomAccessFile raf = new RandomAccessFile("D:/in.txt","rw");
     6         FileChannel fis = raf.getChannel();
     7         System.out.println("此通道文件的总长度:" +fis.size());
     8         //当前通道的文件位置
     9         long position = fis.position();
    10         System.out.println("通道当前的位置:" + position);
    11         //设置新的通道文件位置,从这个位置开始读取
    12         fis.position(position + 8);
    13         ByteBuffer buffer = ByteBuffer.allocate(50);
    14         fis.read(buffer);
    15         buffer.flip();
    16         while(buffer.hasRemaining())
    17         {
    18             System.out.print((char)buffer.get());
    19         }
    20         buffer.clear();
    21         fis.close();
    22     }
    23 }

    执行结果:

    此通道文件的总长度:33
    通道当前的位置:0
    nel
    ByteBuffer
    Selector

    FileChannel是线程安全的,可以多个线程在同一个实例上并发操作,但是其中有些方法(改变文件通道位置或者文件大小的方法)必须是单线程操作。

    二、网络通道

     SocketChannel是一个连接到TCP套接字的通道,获取的方式有两种:

    1、打开一个SocketChannel并连接到互联网上某台服务器。

    2、一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

    上面这两种模式跟IO的Socket、ServerSocket类似,下面分别来看看客户端和服务器端:

    一、SocketChannel

    从通道中读取数据

     1 public class SocketChannelTest
     2 {
     3     public static void main(String[] args) throws Exception
     4     {
     5         //获取socket通道
     6         SocketChannel sc = SocketChannel.open();
     7         //设置为非阻塞模式
     8         sc.configureBlocking(false);
     9         //建立连接,非阻塞模式下,该方法可能在连接建立之前就返回了
    10         sc.connect(new InetSocketAddress("wap.cmread.com",80));
    11         //判断连接是否建立
    12         while(!sc.finishConnect())
    13         {
    14             System.out.println("连接未建立");
    15             Thread.sleep(5);
    16         }
    17         ByteBuffer buffer = ByteBuffer.allocate(48);
    18         int byteRead = sc.read(buffer);
    19         System.out.println(byteRead);
    20         sc.close();
    21         buffer.clear();  
    22     }
    23 }

    执行结果;

    连接未建立
    连接未建立
    0

    1、第6、7行是获取一个socket通道,并且设置为非阻塞模式。

    2、由于是非阻塞模式,通道在调用方法connect/read/writer这三个方法时,会出现这些情况:连接未建立,connect方法就返回了;尚未读取任何数据时,read方法就返回;尚未写出任何内容时,writer就返回

    3、在12行的循环代码中,是判断连接是否建立,从执行结果来看,循环执行了两次连接才建立(在循环里线程还有休眠)。

    4、由于只是建立了连接,通道里面其实没有任何的数据。

    5、第18行调用read方法,由于是非阻塞模式,所以在并未读取任何数据的情况下就返回0(尽管通道里面没有数据)。

    将数据写入通道

     1 public class SocketChannelTest
     2 {
     3     public static void main(String[] args) throws Exception
     4     {
     5         SocketChannel sc = SocketChannel.open();
     6         String str = "non-blocking socket channel";
     7         ByteBuffer buffer = ByteBuffer.allocate(100);
     8         buffer.put(str.getBytes());
     9         buffer.flip();
    10         while(buffer.hasRemaining())
    11         {
    12             sc.write(buffer);
    13         }
    14         sc.close();
    15         buffer.clear();
    16     }
    17 }

    1、SocketChannel.write()方法的调用是在一个while循环中的。Write()方法无法保证能写多少字节到SocketChannel。所以,我们重复调用write()直到Buffer没有要写的字节为止。

    二、ServerSocketChannel

    ServerSocketChannel是一个可以监听新进来的TCP连接的通道。

     1 public class ServerSocketChannelTest
     2 {
     3     public static void main(String[] args) throws Exception
     4     {
     5         ServerSocketChannel ssc = ServerSocketChannel.open();
     6         ssc.socket().bind(new InetSocketAddress(80));
     7         ssc.configureBlocking(false);
     8         while(true)
     9         {
    10             SocketChannel sc = ssc.accept();
    11             if(null != sc)
    12             {
    13                 //do something;
    14             }
    15         }
    16     }
    17 }

    1、第5、6、7行,获取一个ServerSocketChannel,并且监听80端口,设置为非阻塞模式。

    2、通过accept方法监听新接入进来的连接,这个方法会返回一个包含新进来的连接的SocketChannel(服务器端的通道的获取方式)。如果是阻塞模式,该方法会一直阻塞直到有新的连接进来。如果是非阻塞模式,则accept方法会立刻返回,返回值是null。

    3、第11行,是因为在非阻塞模式下,需要检查SocketChannel是否为null。

    三、socket通道与socket

    1 ServerSocketChannel ssc = ServerSocketChannel.open();
    2 ServerSocket socket = ssc.socket();
    3 ServerSocketChannel ssc1 = socket.getChannel();

    1、从这代码片段可以大概看到这样一种关系:所有socket通道(SocketChannel/ServerSocketChanne/DatagramSocketChannel)在被实例化之后,都是伴随生成对应的socket对象,就是前面IO章节介绍的java.net类(Socket/ServerSocket/DatagramSocket)。通过通道类的socket方法来获取。

    2、java.net类(Socket/ServerSocket/DatagramSocket)现在可以通过getChannel方法来获取对应的通道。前提是这些socket对象不是使用传统方式(直接实例化)创建的。否则它就没有关联的socket通道,调用getChannel方法返回总是null。

  • 相关阅读:
    Java map双括号初始化方式的问题
    Koa 中间件的执行
    JavaScript 实现页面中录音功能
    Koa 中实现 chunked 数据传输
    WebAssembly 上手
    TypeScript `infer` 关键字
    Vim 插件的安装
    MySQL EXPLAIN 语句
    面向切面编程(AOP)
    CSS 类名的问题
  • 原文地址:https://www.cnblogs.com/dongguacai/p/5797103.html
Copyright © 2020-2023  润新知