Java NIO 笔记 04

Java NIO 中Channel与Channel的传输

更多内容:我的博客

  • transferFrom()
  • transferTo()

在 Java NIO 中,如果其中一个Channel为FileChannel,那么您可以将数据直接从一个通道传输到另一个通道,FileChannel类中有一个transferTo()和一个transferFrom()方法,可以为我们执行这样的操作。

transferFrom()

FileChannel.tranferFrom()方法将数据从源通道传输到FileChannel,例如下面这样:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt","rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt","rs");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(fromChannel,position,count);

参数含义:

  • fromChannel:源文件Channel
  • position: 从源文件的那个索引位置开始写入
  • count:传输多少字节
transferTo()

transferTo()方法时从FileChannel传输数据到其他的通道。例如:

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);

注意:一些SoocketChannel实现可能只传输SocketChannel在其内部缓冲区准备好的数据,及时SocketChannel稍后可能有更多的可用数据。

Java NIO Selector

Java NIO Selector 是一个组件,它可以检查一个或多个Java NIO Channel实例,并确定那些Channel准备好用于例如读取或写入的操作。通过这种方式,单个线程可以管理多个通道,从而管理多个网络连接。

为什么选择使用Selector?

它的优点是我们可以使用更少的线程来处理多个通道。事实上我们可以只使用一个线程来处理我们所有的Channel,因为线程也占用操作系统中的一些资源(内存),所以线程之间的切换对于操作系统来说是昂贵的。因此,我们使用的线程越少越好。(当然了,如今的CPU性能越来越好,如果是多核CPU,多线程更能是CPU发挥作用)

下面是一个单线程使用Selector来处理三个Channel的示意图:

selector1

创建一个Selector

我们可以通过调用Selector.open()方法来创建一个selector,像下面这样:

Selector selector = Selector.open();
将Channel注册到Selector

为了使用Selector来处理通道,我们必须向Selector注入要处理的Channel,我们可以使用SelectableChannel.register()方法注入,像下面这样:

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

注意,Channel必须处于非阻塞模式下才能与Selector一起使用,这意味着我们不能将FileChannel与Selector一起使用,因为FileChannel无法切换到非阻塞模式。不过,SocketChannel可以正常工作。

注意register方法的第二个参数,它是一个“关注集”,意思是您关注Channel收到那些事件。通过Selector,我们可以监听四种不同的事件:

  1. Connect
  2. Accept
  3. Read
  4. Write

“触发事件”的通道也被称为“准备好”该事件。因此,已成功连接到另一台服务器的通道是处于“connect ready”也就是连接就绪状态。接受传入连接的服务器套接字通道为“accept”也就是已接受就绪状态。已准备好读取数据的通道为“read”,也就是已读取就绪状态。同理,已准备好写入数据为“write”,也就是已准备好写入就绪状态。这四个事件由四个SelectionKey表示:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

如果想关注多个事件,我们可以将多个常量进行与操作,如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey

正如上面所看到的,当我们为Selector中注册Channel时,register方法返回一个SelectionKey对象。这个对象包含有如下的属性:

  • Interest Set
  • ready set
  • Channel
  • attached object(可选)
Interest Set

Interest Set 是我们关注“选择”的事件集,我们可以通过SelectionKey读取和写入该集,如下:

int interests = selectionKey.interestOps();
boolean isInterestedInAccept = SelectionKey.OP_ACCEPT == (interests & SelectionKey.OP_ACCEPT);
boolean isInterestedInConnect = SelectionKey.OP_CONNECT == (interests & SelectionKey.OP_CONNECT);
boolean isInterestedInRead = SelectionKey.OP_READ == (interests & SelectionKey.OP_READ);
boolean isInterestedInWrite = SelectionKey.OP_write == (insterests & SelectionKey.OP_WRITE);

向上面这样,我们可以通过与操作来确定某个事件是否在我们的interest set中

Ready Set

ready set 也可以理解为就绪集,是通道准备好的操作集。选择(Selection)后,我们将主要访问ready set。Selection会在稍后介绍。

int readySet = selectionKey.readyOps();

这里我们可以使用与interest set相同测试方法来检测通道准备好那些事件或操作。但是,我们也可以改用下面这四种方式来检测,它们同样返回的是boolean类型的值。

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
channel+Selector

从SelectionKey中访问channel+selector如下:

Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
Attaching Objects

我们可以将对象附加到SelectionKey,这是识别给定Channel或将更多信息附加到Channel的便捷方式。例如:我们可以将正在使用的Buffer附加到通道中,或者附加一个包含更多聚合数据的对象。如下:

selectionKey.attach(theObject);
Object attachedObj = selectionKey.attachment();

我们还可以在注册channel时附加对象,如下:

SelectionKey key = channel.register(selector, SelectionKey_OP_READ, theObject);
通过选择器选择Channel

一旦我们注册了一个和多个channel到Selector中,我们可以调用其中之一select()方法.,这些方法返回我们关注的事件中已经准备好的通道(connect、accept、read 或 write),也就是如果我们对准备好读取的Channel感兴趣,我们可以从select()方法中收到准备好读取的Channel。

  • int select();
  • int select(long timeout);
  • int selectNow();

select():会阻塞,直到至少有一个通道为注册的事件做好准备。

select(long timeout):它的作用于select()相同,只是它设置了最长阻塞时间(单位:ms)。

selectNow():它不会阻塞,会立即返回任何准备好的通道。

select方法返回的int值表示有多少通道准备就绪。也就是,自上次调用select以来,准备就绪的通道数量。例如,如果你调用select()并且它返回1,因为一个通道准备就绪,你再次调用select()并且还还有一个通道已经准备好,那么它会再次返回1.

selectedKeys()

一旦调用了select方法后,并且返回值表明有一个或多个通道准备好,那么就可以调用选择器selectedKeys()方法,通过“selected key set”访问就绪通道,如下:

Set<SelectionKey> selectKeys = selector.selectedKyes();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()){
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()){
        //一个连接被一个ServerSocketChannel接受
    }else if(key.isConnectable()){
        //已与远程服务器建立连接
    }else if(key.isReadable()){
		//Channel已经准备好读取
    }else if(key.isWritable()){
        //Channel已经准备好写入
    }
    keyIterator.remove();
}

上面的程序中会循环所选键集中的键,对于每个键,它会测试该键以确定改建所引用的通道准备好什么事件。注意调用keyIterator.remove()方法,Selector不会从所选键集本身中删除SelectionKey实例。当我们处理完Channel后必须这样做。下次通道变为就绪时,选择器将再次将其添加到选定键集中。

SelectionKey.channel()方法返回的通道应该转换为对应需要使用的Channel,例如,ServerSocketChannel或SocketChannel等。

wakeUp()

调用了被阻塞的select()方法的线程可以离开select()方法,及时没有通道准备好。这是通过让不同线程在第一个线程调用select()的selector上调用Selector.wakeUp()方法来完成的。然后在select()方法中等待的线程将立即返回。

如果另外一个线程调用了wakeup()方法,而select()中当前没有线程被阻塞,那么下一个调用select()的线程将立即唤醒。

close()

当完成Selector后,将调用其close()方法,这将关闭Selector并是所有注册到此Selector的SelectionKey实例无效。通道本身并未关闭。

示例

这个例子它打开了一个选择器,用它注册了一个通道(通道实例化部分省略),并持续监视选择器以了解四个事件的准备情况。

Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
  int readyChannels = selector.selectNow();
  if(readyChannels == 0) continue;
  Set<SelectionKey> selectedKeys = selector.selectedKeys();
  Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if(key.isAcceptable()) {
        // a connection was accepted by a ServerSocketChannel.
    } else if (key.isConnectable()) {
        // a connection was established with a remote server.
    } else if (key.isReadable()) {
        // a channel is ready for reading
    } else if (key.isWritable()) {
        // a channel is ready for writing
    }
    keyIterator.remove();
  }
}

更多内容:我的博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@胡海龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值