Java Netty 学习(四) - NIO基础知识Channel和Pipe

版权声明:本博客所有的原创文章,转载请注明出处,作者皆保留版权。 https://blog.csdn.net/anLA_/article/details/79631038

前面说过,在NIO中,是利用Channel和Buffer进行数据传送的。
Channel主要包括四种:

  • FileChannel:从文件中读写数据。
  • DatagramChannel:能通过UDP读写网络中的数据。
  • SocketChannel:能通过TCP读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

本文主要先介绍下四种Channel的用法,然后再探究在底层原理上有何差别。

FileChannel

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

在上一篇文章中也简单讲了FileChannel的一些原理及用法:
Java Netty 学习(三)- BIO,AIO,NIO深入浅出
首先看一个例子:

private static void buffer() throws IOException{
    RandomAccessFile aFile = new RandomAccessFile("/home/anla7856/workspace/io.examples/pom.xml", "rw");
    FileChannel inChannel = aFile.getChannel();
    ByteBuffer buf = ByteBuffer.allocate(48);
    int bytesRead = inChannel.read(buf); 
    while (bytesRead != -1) {
      buf.flip();  //准备读
      while(buf.hasRemaining()){
          System.out.print((char) buf.get()); // 一次从buf中读取一个字节,并且转化为char输出
      }
      buf.clear(); //清空buf,让其处于待写状态。
      bytesRead = inChannel.read(buf);
    }
    aFile.close();
}

上面代码简单的从RandomAccessFile中获取一个FileChannel,然后读取数据到buffer中。可以理解为以下几个步骤:
1. 打开Channel:FileChannel inChannel = aFile.getChannel();
2. 从Channel中读取数据:int bytesRead = inChannel.read(buf);

而对于向Channel中写数据则为:

while(buf.hasRemaining()) {
    channel.write(buf);
}

或许这个形式,和不同IO很相似,难道也是一个个字节往里面输入的?
因为无法保证write()方法一次能向FileChannel写入多少字节,因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。

Channel其他操作

  1. 关闭Channel:channel.close();
  2. position方法:类似于RandomAccessFile,可能需要在某个特定位置对Channel进行读写,则可以把position设置为一个特定值后进行读写:channel.position(specialPos +123);
  3. size方法:可以得到关联文件大小long fileSize = channel.size();
  4. truncate方法:channel.truncate(1024); ,方法返回值是当前channel本,这个方法什么意思呢?看一个图:
    这里写图片描述
    首先,truncate方法对文件自身也是有效果的,所以如果重要文件需要备份,如果size小于文件的filesize,则执行玩方法后FileChannel数据只有0~size大小,其他被删除了,如果大于filesize,则不回产生任何变化。
  5. force方法:FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。
    出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。
    下面的例子同时将文件数据和元数据强制写到磁盘上:channel.force(true);

SocketChannel

以前Java网络编程时候,都是用的Socket,和ServerSocket,但是它们的读取是阻塞式的,也就是一个Socket一个线程,有数据时读才会返回。
SocketChannel是一个连接到TCP网络套接字的通道,主要有两种方式连通:

  • 打开一个SocketChannel并连接到互联网上的某台服务器。
  • 一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。

如下方式打开:

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

SocketChannel的读写可分为阻塞模式和非阻塞模式:
使用上设置:

SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("127.0.0.1", 8001));
socketChannel.configureBlocking(true);    //设置为阻塞,默认为true

阻塞模式

阻塞模式下read和write与FileChannel一致,此处就不细讲,代码一致。

非阻塞模式

在异步模式下调用connect(), read() 和write()
1. connect方法:此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。如下:

socketChannel.connect(new InetSocketAddress("http://localhost", 80));
while(! socketChannel.finishConnect() ){     //如果没有连接上就一直尝试
    //wait, or do something else..
}
  1. write方法:可能未写出任何东西就返回了,所以需要用while判定
  2. read方法:可能为读出任何东西就返回了,所以同样需要while判定

通常,在非阻塞模式下,channel需要与selector配合使用,由selector来判定哪个channel准备好了,可以读/写了。

ServerSocketChannel

ServerSocketChannel在用法上,和SocketChannel一致,也可以设置为阻塞和非阻塞,使用方法参看SocketChannel,
看看ServerSocketChannel接受新的SocketChannel,这也是一个非阻塞情况,当立即返回而没有任何SocketChannel时,
需要while循环判断:

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...
    }
}

DatagramChannel

针对UDP的Channel,DatagramChannel是一个能收发UDP包的通道。因为UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。
同样它也有阻塞和非阻塞之分,这里就部分开分析了,可以结合上文分析。
看下面使用:
1. 打开一个DatagramChannel

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(8888));
  1. 接收数据
    即从buffer中读取数据到channel中。
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
  1. 发送数据
int bytesSent = channel.send(buf, new InetSocketAddress("localhost", 80));

因为UDP是无连接的网络协议,虽然它又阻塞和非阻塞式,但是实际使用时,是默许UDP丢包的,例如视频聊天(基于UDP),丢包并不影响。

Channel之间传递数据

如果两个Channel中有一个是FileChannel,那你可以直接将数据从一个channel,传输到另外一个channel。
有两个方法,transferFrom和transferTo。

  1. transferFrom是从其他channel传输到fileChannel,用法为:
destChannel.transferFrom(position, count, srcChannel);

当然,最终传输也是按照从position到count的数量来传输的,如果设定后position=capacity(譬如srcChannel也是FileChannel,可以设置position),则传输字节为0.
另外,在SoketChannel的实现中,SocketChannel只会传输此刻准备好的数据(可能不足count字节)。
因此,SocketChannel可能不会将请求的所有数据(count个字节)全部传输到FileChannel中。

  1. transferTo,是从FileChannel传输到其他channel中,如下例子:
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count    = fromChannel.size();

fromChannel.transferTo(position, count, toChannel);

两个方法用法很相似是吧~

#

Pipe和Channel相比更加简单,Channel是双向的,而Pipe则是单项传递的。
Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。

这里写图片描述

创建Pipe

如下去创建Pipe:

Pipe pipe = Pipe.open();

向Pipe写数据sinkChannel

如下示例:

Pipe.SinkChannel sinkChannel = pipe.sink();
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()) {
    sinkChannel.write(buf);
}

从Pipe中读取数据sourceChannel

如下示例:

Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

参考资料:
1. http://tutorials.jenkov.com/java-nio/channel-to-channel-transfers.html
2. http://ifeve.com/java-nio-channel-to-channel/
3. http://ifeve.com/pipe/

猜你喜欢

转载自blog.csdn.net/anLA_/article/details/79631038