Java NIO之Channel

本文开始讲解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 }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容