Java NIO之Channel

作者: 匠丶 | 来源:发表于2018-12-06 23:53 被阅读6次

    本文开始讲解Java NIO 的三个核心组件,Channel,Buffer,Selector。先从Channel开始,Channel指的是通道。Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。通道是一种途径,借助该途径,可以用最小的总开销来访问操作系统本身的I/O服务。

    通道基础

    首先,看一下基本的Channel接口,下面是Channel接口的完整源码:

    public interface Channel extends Closeable {
    
        /**
         * Tells whether or not this channel is open.  </p>
         *
         * @return <tt>true</tt> if, and only if, this channel is open
         */
        public boolean isOpen();
    
        /**
         * Closes this channel.
         *
         * <p> After a channel is closed, any further attempt to invoke I/O
         * operations upon it will cause a {@link ClosedChannelException} to be
         * thrown.
         *
         * <p> If this channel is already closed then invoking this method has no
         * effect.
         *
         * <p> This method may be invoked at any time.  If some other thread has
         * already invoked it, however, then another invocation will block until
         * the first invocation is complete, after which it will return without
         * effect. </p>
         *
         * @throws  IOException  If an I/O error occurs
         */
        public void close() throws IOException;
    
    }
    

    可以从底层的Channel接口看到,对所有通道来说只有两种共同的操作:检查一个通道是否打开isOpen()和关闭一个打开的通道close(),其余所有的东西都是那些实现Channel接口以及它的子接口的类。

    从Channel接口引申出的其他接口都是面向字节的子接口:


    通道可以是单向的也可以是双向的。一个Channel类可能实现定义read()方法的ReadableByteChannel接口,而另一个Channel类也许实现WritableByteChannel接口以提供write()方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据,就像下面的ByteChannel。

    public interface ReadableByteChannel extends Channel {
    
        public int read(ByteBuffer dst) throws IOException;
    
    }
    ****************************************************************
    public interface WritableByteChannel extends Channel{
    
        public int write(ByteBuffer src) throws IOException;
    
    }
    ****************************************************************
    public interface ByteChannel
        extends ReadableByteChannel, WritableByteChannel
    {
    
    }
    

    通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行,非阻塞模式的通道永远不会让调用的线程休眠,请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流的(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式。

    比方说非阻塞的通道SocketChannel。可以看出,socket通道类从SelectableChannel类引申而来,从SelectableChannel引申而来的类可以和支持有条件的选择的选择器(Selectors)一起使用。将非阻塞I/O和选择器组合起来可以使开发者的程序利用多路复用I/O

    public abstract class SocketChannel
        extends AbstractSelectableChannel
        implements ByteChannel, ScatteringByteChannel, GatheringByteChannel
    {
        ...
    }
    
    public abstract class AbstractSelectableChannel
        extends SelectableChannel
    {
       ...
    }
    

    文件通道

    通道是访问I/O服务的导管,I/O可以分为广义的两大类:File I/O和Stream I/O。那么相应的,通道也有两种类型,它们是文件(File)通道和套接字(Socket)通道。文件通道指的是FileChannel,套接字通道则有三个,分别是SocketChannel、ServerSocketChannel和DatagramChannel。

    通道可以以多种方式创建。Socket通道可以有直接创建Socket通道的工厂方法,但是一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取,开发者不能直接创建一个FileChannel。并且文件通道总是阻塞式的,因此不能被置于非阻塞模式下。

    public static void main(String[] args) throws Exception
    {
    //读文件
        File file = new File("D:/files/readchannel.txt");
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteBuffer bb = ByteBuffer.allocate(35);
        fc.read(bb);
        bb.flip();
        while (bb.hasRemaining())
        {
            System.out.print((char)bb.get());
        }
        bb.clear();
        fc.close();
    //写文件
    File file = new File("D:/files/writechannel.txt");
        RandomAccessFile raf = new RandomAccessFile(file, "rw");
        FileChannel fc = raf.getChannel();
        ByteBuffer bb = ByteBuffer.allocate(10);
        String str = "abcdefghij";
        bb.put(str.getBytes());
        bb.flip();
        fc.write(bb);
        bb.clear();
        fc.close();
    }
    

    Socket通道

    Socket通道与文件通道有着不一样的特征:
    1、NIO的Socket通道类可以运行于非阻塞模式并且是可选择的,因此,再也没有为每个Socket连接使用一个线程的必要了。这一特性避免了管理大量线程所需的上下文交换总开销,借助NIO类,一个或几个线程就可以管理成百上千的活动Socket连接了并且只有很少甚至没有性能损失

    2、全部Socket通道类(DatagramChannel、SocketChannel和ServerSocketChannel)在被实例化时都会创建一个对应的Socket对象,就是我们所熟悉的来自java.net的类(Socket、ServerSocket和DatagramSocket),这些Socket可以通过调用socket()方法从通道类获取,此外,这三个java.net类现在都有getChannel()方法

    3、每个Socket通道(在java.nio.channels包中)都有一个关联的java.net.socket对象,反之却不是如此,如果使用传统方式(直接实例化)创建了一个Socket对象,它就不会有关联的SocketChannel并且它的getChannel()方法将总是返回null。

    现在简单写一下Socket服务端代码和客户端代码:

    public class NonBlockingSocketServer
     2 {
     3     public static void main(String[] args) throws Exception
     4     {
     5         int port = 1234;
     6         if (args != null && args.length > 0)
     7         {
     8             port = Integer.parseInt(args[0]);
     9         }
    10         ServerSocketChannel ssc = ServerSocketChannel.open();
    11         ssc.configureBlocking(false);
    12         ServerSocket ss = ssc.socket();
    13         ss.bind(new InetSocketAddress(port));
    14         System.out.println("开始等待客户端的数据!时间为" + System.currentTimeMillis());
    15         while (true)
    16         {
    17             SocketChannel sc = ssc.accept();
    18             if (sc == null)
    19             {
    20                 // 如果当前没有数据,等待1秒钟再次轮询是否有数据,在学习了Selector之后此处可以使用Selector
    21                 Thread.sleep(1000);
    22             }
    23             else
    24             {
    25                 System.out.println("客户端已有数据到来,客户端ip为:" + sc.socket().getRemoteSocketAddress() 
    26                         + ", 时间为" + System.currentTimeMillis()) ;
    27                 ByteBuffer bb = ByteBuffer.allocate(100);
    28                 sc.read(bb);
    29                 bb.flip();
    30                 while (bb.hasRemaining())
    31                 {
    32                     System.out.print((char)bb.get());
    33                 }
    34                 sc.close();
    35                 System.exit(0);
    36             }
    37         }
    38     }
    39 }
    

    和BIO的区别可以理解为,在ServerSocket做了一层wrapper,并且数据的读写要通过ByteBuffer。

    1 public class NonBlockingSocketClient
     2 {
     3     private static final String STR = "Hello World!";
     4     private static final String REMOTE_IP= "127.0.0.1";
     5     
     6     public static void main(String[] args) throws Exception
     7     {
     8         int port = 1234;
     9         if (args != null && args.length > 0)
    10         {
    11             port = Integer.parseInt(args[0]);
    12         }
    13         SocketChannel sc = SocketChannel.open();
    14         sc.configureBlocking(false);
    15         sc.connect(new InetSocketAddress(REMOTE_IP, port));
    16         while (!sc.finishConnect())
    17         {
    18             System.out.println("同" + REMOTE_IP+ "的连接正在建立,请稍等!");
    19             Thread.sleep(10);
    20         }
    21         System.out.println("连接已建立,待写入内容至指定ip+端口!时间为" + System.currentTimeMillis());
    22         ByteBuffer bb = ByteBuffer.allocate(STR.length());
    23         bb.put(STR.getBytes());
    24         bb.flip(); // 写缓冲区的数据之前一定要先反转(flip)
    25         sc.write(bb);
    26         bb.clear();
    27         sc.close();
    28     }
    29 }
    

    相关文章

      网友评论

        本文标题:Java NIO之Channel

        本文链接:https://www.haomeiwen.com/subject/glzacqtx.html