码农的世界程序员

Java NIO之Channel

2018-12-06  本文已影响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 }
上一篇 下一篇

猜你喜欢

热点阅读