zoukankan      html  css  js  c++  java
  • Java NIO学习系列二:Channel

      上文总结了Java NIO中的Buffer相关知识点,本文中我们来总结一下它的好兄弟:Channel。上文有说到,Java NIO中的Buffer一般和Channel配对使用,NIO中的所有IO都起始于一个Channel,一个Channel就相当于一个流,,可以从Channel中读取数据到Buffer,或者写数据到Channel中。

      Channel简介

      FileChannel

      SocketChannel

      ServerSocketChannel

      DatagramChannel

      总结

    1. Channel简介

      Java NIO中的Channel类似流,但是有一些不同:

    • Channel既可以支持写也可以支持读,而流则是单向的,只能支持写或者读;
    • Channel支持异步读写;
    • Channel一般和Buffer配套使用,从Channel中读取数据到Buffer中,或从Buffer写入到Channel中;

      Channel的主要实现类有如下几种:

    • FileChannel,可以对文件读/写数据;
    • DatagramChannel,通过UDP从网络读/写数据;
    • SocketChannel,通过TCP从网络读/写数据;
    • ServerSocketChannel,允许你监听TCP连接,对于每个TCP连接都创建一个SocketChannel;

    2. FileChannel

      Java NIO FileChannel是一类文件相连的channel,通过它可以从文件读取数据,或向文件写数据。FileChannel类是Java NIO类库提供的用于代替标准Java IO API来读写文件。FileChannel不能设置为非阻塞模式,只能工作在阻塞模式下。

    2.1 打开FileChannel

      在使用FileChannel之前先要打开它,就I/O类库中有三个类被修改了,用以产生FileChannel,这三个类是:InputStream、OutputStream、RandomAccessFile,如下是如何从RandomAccessFile获取FileChannel:

    RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
    FileChannel inChannel = aFile.getChannel();

    2.2 从FileChannel读取数据

      首先需要分配一个Buffer,从FileChannel读取的数据会读到Buffer中(是不是有点绕)。然后调用FileChannel的read()方法来读数据,这个方法会把数据读到Buffer中,返回的int代表读取了多少字节,返回-1代表文件末尾。

    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = inChannel.read(buf);

    2.3 往FileChannel写数据

      通过调用FileChannel的write()方法可以往其中写数据,参数是一个Buffer:

    String newData = "New String to write to file..." + System.currentTimeMillis();
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    while(buf.hasRemaining()) {
        channel.write(buf);
    }

      这里write()方法也没有保证一次写入多少数据,我们只是不断重复直到写完。

    2.4 关闭FileChannel

      用完FileChannel之后记得要将其关闭:

    channel.close();

    2.5 FileChannel位置

      调用FileChannel对象的position()方法可以获取其position,也可以调用position(long pos)设置其position:

    // 获取position
    long pos channel.position();
    // 设置position
    channel.position(pos +123);

      如果将position设置到文件末尾后面,然后尝试读取文件,会返回-1;

      如果将position设置到文件末尾后面,然后尝试向文件中写数据,则文件会自动扩展,并且从设置的position位置处开始写数据,这会导致“file hole”,即物理文件会有间隙。

    2.6 FileChannel尺寸

      size()方法返回filechannel连接的文件的尺寸大小:

    long fileSize = channel.size(); 

    2.7 截短FileChannel

      truncate()方法可以截短文件:

    channel.truncate(1024);

      如上,将文件截取为1024字节长度。

    2.8 FileChannel Force

      FileChannel.force()方法会刷新所有未写入到磁盘的数据到磁盘上。因为操作系统会先将数据缓存到内存中,再一次性写入磁盘,所以不能保证写到channel中的数据是否写入到磁盘上,所以可以调用flush()方法强制将数据写入磁盘。

      force()方法有一个boolean参数,代表是否要写入文件的元数据(比如权限):

    channel.force(true);

    3. SocketChannel

      Java NIO SocketChannel用于和TCP网络socket相连,等同于Java网络包中的Socket。可以通过两种方式来创建一个SocketChannel:

    • 打开一个SocketChannel并且将其和网络上的某台服务器相连;
    • 当有一个连接到达一个ServerSocketChannel时会自动创建一个SocketChannel;

    3.1 打开Socket通道

      如下示例说明了如何打开一个SocketChannel:

    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

    3.2 关闭Socket通道

      对于这种资源类的使用,是要记得及时关闭的:

    socketChannel.close();

    3.3 从Socket通道读数据

      调用read()方法可以读取数据:

    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = socketChannel.read(buf);

    3.4 往Socket通道写数据

      调用其写方法write()可以向其中写数据,使用一个Buffer作为其参数:

    String newData = "New String to write to file..." + System.currentTimeMillis();
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    while(buf.hasRemaining()) {
        channel.write(buf);
    }

    3.5 工作在非阻塞模式下

      可以将SocketChannel设置为非阻塞模式,设置之后可以异步地调用其connect()、read()和write()方法。

    connect()

      对于处于非阻塞模式下的SocketChannel,调用其connect()方法之后会立即返回,即使没有成功建立连接。可以调用finishConnect()方法来获知是否成功建立连接:

    socketChannel.configureBlocking(false);
    socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
    while(! socketChannel.finishConnect() ){
        //wait, or do something else...    
    }

    write()

      对于处于非阻塞模式的SocketChannel,调用其write()也会返回,但是可能还没有写入数据。因此你需要在一个循环中调用它,就像前面的例子中看到的那样。

    read()

      对于处于非阻塞模式的SocketChannel,调用其read()有可能出现返回int值,但是还没有任何数据读入到buffer中。因此需要关注返回int值,这个可以告诉我们有多少数据是已经读取的。

    非阻塞模式下和Selectors一起工作

      非阻塞模式下的SocketChannel适合和Selector一起搭配使用。通过往Selector中注册一个或多个SocketChannel,可以通过Selector选择已经就绪的Channel。具体使用稍后会详述。

     

    4. ServerSocketChannel

      Java NIO ServerSocketChannel可以监听TCP连接,就像标准Java网络库中的ServerSocket。

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(9999));
    while(true){
        SocketChannel socketChannel = serverSocketChannel.accept();
        //do something with socketChannel...
    }

    4.1 打开ServerSocketChannel

      很简单,调用其open()方法就可以开启一个ServerSocketChannel:

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    4.2 关闭ServerSocketChannel

    serverSocketChannel.close();

    4.3 监听连接

      调用其accept()方法之后可以监听连接。在一个新的连接到来之前,都处于阻塞状态,一旦新的连接到来,accept()方法将返回一个和这个连接对应的SocketChannel。

      将其放在一个while循环中就可以不断监听新的连接:

    while(true){
        SocketChannel socketChannel =
                serverSocketChannel.accept();
        //do something with socketChannel...
    }

      当然实际中while循环应该使用其他的判断条件而不是true。

    4.4 工作在非阻塞模式下

      ServerSocketChannel同样可设置为非阻塞模式,此时调用其accept()会立即返回,如果没有新的连接可能返回null,需要对返回值是否为空进行校验:

    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(9999));
    serverSocketChannel.configureBlocking(false);
    while(true){
        SocketChannel socketChannel = serverSocketChannel.accept();
        if(socketChannel != null){
            //do something with socketChannel...
            }
    }

    5. DatagramChannel

      DatagramChannel用于发送和接收UDP包。因为UDP是一个无连接协议,不是像其他channel一样进行读写操作,而是通过数据包来交换数据。

    5.1 打开DatagramChannel

    DatagramChannel channel = DatagramChannel.open();
    channel.socket().bind(new InetSocketAddress(9999));

      这个例子中打开了一个DatagramChannel,可以通过端口9999接收UDP数据包。

    5.2 接收数据

    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    channel.receive(buf);

      调用DatagramChannel的receive()方法可以将接收到的数据包中的数据复制到指定的Buffer中。如果收到的数据大于Buffer容量,默认将其抛弃。

    5.3 发送数据

    String newData = "New String to write to file..."
                        + System.currentTimeMillis();    
    ByteBuffer buf = ByteBuffer.allocate(48);
    buf.clear();
    buf.put(newData.getBytes());
    buf.flip();
    int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

      在这个例子中是向“jenkov.com”服务器的80端口发送UDP数据包。因为UDP不保证数据传输,所以也不会知道发送的数据包是否被收到。

    5.4 连接到指定地址

      可以将DatagramChannel“连接”到一个指定的网络地址。因为UDP协议是无连接的,所以通过这种方式创建连接并不会像基于TCP的channel那样创建一个真实的连接。但是,这样可以“锁定”这个DatagramChannel,使得它只能和一个指定的ip地址交换数据。

    channel.connect(new InetSocketAddress("jenkov.com", 80)); 

      在这种情况下(锁定),也可以像其他Channel那样调用read()和write()方法,只不过不能够保证数据一定能够收到。

    int bytesRead = channel.read(buf); 
    int bytesWritten = channel.write(buf);

    6. 总结

      本文主要总结了Channel的相关知识,Channel是通道,和Buffer进行交互数据,可以读数据到Buffer中,也可以从Buffer往Channel写数据。Channel主要有下面几种:

    • FileChannel
    • SocketChannel
    • ServerSocketChannel
    • DatagramChannel

      其中FileChannel是和文件交互、SocketChannel和ServerSocketChannel是基于TCP的网络Channel,DatagramChannel是基于UDP的网络Channel。

  • 相关阅读:
    将10进制数字转成62进制数字(转)
    admin添加用户时报错:(1452, 'Cannot add or update a child row: a foreign key constraint fails (`mxonline`.`django_admin_l
    Django admin 中抛出 'WSGIRequest' object has no attribute 'user'的错误
    分布式爬虫
    Scrapy之CrawlSpider
    Scrapy之Cookie和代理
    Scrapy核心组件
    scrapy之持久化存储
    Scrapy框架的基本使用
    爬虫之request模块高级
  • 原文地址:https://www.cnblogs.com/volcano-liu/p/11099418.html
Copyright © 2011-2022 走看看