黑马程序员_O‘Reilly java nio学习笔记之选择器

---------------------- android培训java培训、期待与您交流! ---------------------- 

1.1    选择器,可选择通道和选择键类 

                            选择器(Selector) 

    选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册

的,并且使用选择器来更新通道的就绪状态。

                            可选择通道(SelectableChannel) 

这个抽象类提供了通道的可选择性所需要的公共方法。FileChannel 对象不是可选择的,因为它们没有继承 SelectableChannel。所有 socket通道都是可选择的,包括从管道(Pipe)对象的中获得的通道。SelectableChannel 可以被注册到 Selector 对象上,一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。 

                            选择键(SelectionKey) 

选择键封装了通道与选择器的注册关系。选择键对象被SelectableChannel.register返回并提供一个表示这种注册关系的标记。通道在被注册到一个选择器上之前,必须先设置为非阻塞模式(通过调用configureBlocking(false))。 

调用可选择通道的 register( )方法会将它注册到一个选择器上。如果试图注册一个处于阻塞状态的通道,register()将抛出未检查的 IllegalBlockingModeException 异常。此外,通道一旦被注册,就不能回到阻塞状态。试图这么做的话,将在调用 configureBlocking( )方法时将抛出IllegalBlockingModeException 异常。 并且,试图注册一个已经关闭的 SelectableChannel 实例的话,也将抛出losedChannelException 异常。

键的 interest(感兴趣的操作)集合和 ready(已经准备好的操作)集合是和特定的通道相关的。每个通道的实现,将定义它自己的选择键类。在register()方法中可以构造它并将它传递给所提供的选择器对象。 

1.2   建立选择器

通过以下代码我们可以建立监控三个Socket通道的选择器:

Selector selector = Selector.open( ); 

channel1.register (selector, SelectionKey.OP_READ); 

channel2.register (selector, SelectionKey.OP_WRITE); 

channel3.register (selector, 

                                SelectionKey.OP_READ | SelectionKey.OP_WRITE); 

// Wait up to 10 seconds for a channel to become ready 

readyCount = selector.select (10000); 

    select 方法是阻塞方法,直到过了十秒或者至少有一个通道的I/O操作准备好。下面是选择器有关的方法有:

abstract  void close()          关闭此选择器。 

abstract  boolean isOpen()      判断选择器是否已打开。 

static Selector open()          打开一个选择器。 

abstract  SelectorProvider provider()     返回创建此通道的提供者。 

abstract  int select()    返回一组键的个数,其相应的通道已为 I/O 操作准备就绪。 

abstract  int select(long timeout)   同上,指定了阻塞时间。

abstract  int selectNow()   select()方法的非阻塞形式。不等于select(0)(无限期阻塞)。

abstract  Set<SelectionKey> keys()        返回此选择器的键集。 

abstract  Set<SelectionKey> selectedKeys()     返回此选择器上相应的通道 I/O 操作准备就 

                                               绪的选择键集。 

abstract  Selector wakeup()     使尚未返回的第一个选择操作立即返回。 

     当您不再使用Selector时,可以调用close( )方法来释放它可能占用的资源并将所有相关的选择键设置为无效。一旦一个选择器被关闭,试图调用它的大多数方法都将导致 ClosedSelectorException,可以通过 isOpen( )方法来测试一个选择器是否处于被打开的状态。 

1. 使用选择键

关于选择键的方法:

Object attach(Object ob)         将给定的对象附加到此键。 

Object attachment()              获取当前的附加对象。 

abstract  void cancel()          请求取消此键的通道到其选择器的注册。 

abstract  boolean isValid()      告知此键是否有效。 

abstract  SelectableChannel channel()         返回与此键相关的通道。 

abstract  Selector selector()    返回为此选择器创建的键。 

abstract  int interestOps()      获取此键的interest 集合,是通道被注册时传进来的值。 

abstract  SelectionKey interestOps(int ops)   将此键的 interest 集合设置为给定值。 

abstract  int readyOps()         获取此键的ready操作集合,为interest的子集。 

boolean isAcceptable()           测试此键的通道是否已准备好接受新的套接字连接。 

boolean isConnectable()     测试此键的通道是否已完成其套接字连接操作。 

boolean isReadable()        测试此键的通道是否已准备好进行读取。 

boolean isWritable()        测试此键的通道是否已准备好进行写入。 

在SelectionKey中,用静态常量定义了四种IO操作:OP_READ  1、OP_WRITE  4、OP_CONNECT  8、

OP_ACCEPT  16,这4个值任何2、3、4个相加结果都不相同,因此可以用validOps()方法返回值确定SelectableChannel支持的操作。

当通道关闭时,所有相关的键会自动取消。当选择器关闭时,所有被注册到该选择器的通道都将被注销,并且相关的键将立即被无效化(取消)。一旦键被无效化,调用它的与选择相关的方法就将抛出CancelledKeyException。 

通过attach(Object ob)方法可以在键对象中保存所提供的对象的引用,新应用会替换旧引用。SelectionKey 类除了保存它之外,不会将它用于任何其他用途。可以通过传递null值来清除附件,可以通过调用 attachment( )方法来获取与键关联的对象引用。如果没有附件,或者显式地通过null方法进行过设置,这个方法将返回null。 

SelectableChannel 类的一个register( )方法的重载版本接受一个 Object 类型的参数。这

是一个方便您在注册时附加一个对象到新生成的键上的方法。以下代码: 

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

等价于: 

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

key.attach (myObject); 

SelectionKey 对象是线程安全的。

2. 使用选择器

    前面已经列出了关于选择器方法列表,下面介绍基本用法。

通过keys( )方法已注册键的集合,集合可能是空的。这个已注册的键的集合不是可以直接修改的,试图这么做的话将引java.lang.UnsupportedOperationException。 

已选择的键的集合(Selected key set) 是已注册的键的集合的子集,这个集合的每个成员都是相关的通道被选择器判断为已经准备好的。这个集合通过 selectedKeys()方法返回(可能是空的)。  

已取消的键的集合(Cancelled key set) 是已注册的键的集合的子集,这个集合包含了 cancel()方法被调用过的键(这个键已经被无效化),但它们还没有被注销 。这个集合是选择器对象的私有成员,因而无法直接访问。 

以下三种方式可以唤醒在select()方法中睡眠的线程: 

a.调用 Selector 对象的 wakeup( )方法将使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有在进行中的选择,那么下一次对 select( )方法的一种形式的调用将立即返回。后续的选择操作将正常进行。在选择操作之间多次调用wakeup( )方法与调用它一次没有什么不同。 

b.如果选择器的 close()方法被调用,那么任何一个在选择操作中阻塞的线程都将被唤醒,就像wakeup()方法被调用了一样。但是,与选择器相关的通道将被注销,键将被取消。 

c.如果睡眠中的线程的 interrupt( )方法被调用,它的返回状态将被设置。如果被唤醒的线程之后将试图在通道上执行 I/O 操作,通道将立即关闭,然后线程将捕捉到一个异常。Selector对象将捕捉 InterruptedException异常并调用wakeup( )方法。 

选择是累积的,一旦一个选择器将一个键添加到它的已选择的键的集合中,它就不会移除这个

键。并且,一旦一个键处于已选择的键的集合中,这个键的 ready 集合将只能被设置,而不会被清

理。当通道上的至少一个感兴趣的操作就绪时,键的ready 集合就应该先被被清空,然后当前已经就绪的操作将会被添加到ready集合中。该键之后将被添加到已选择的键的集合中。清空一个 SelectKey 的 ready 集合的方式是将这个键从已选择的键的集合中移除。

选择键的就绪状态只有在选择器对象在选择操作过程中才会修改。在选择器上调用一次 select 操作(这将更新已选择的键的集合),然后遍历 selectKeys( )方法返回的键的集合。在按顺序进行检查每个键的过程中,相关的通道也根据键的就绪集合进行处理。然后键将从已选择的键的集合中被移除(通过在 Iterator对象上调用 remove()方法),然后检查下一个键。完成后,通过再次调用 select( )方法重复这个循环。下面代码是典型的服务器的例子。

public class SelectSockets {
	public static void main(String[] argv) throws Exception {
		new SelectSockets().go(argv);
	}
	public void go(String[] argv) throws Exception {
		int port=10000;
		System.out.println("Listening on port " + port);
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		ServerSocket serverSocket = serverChannel.socket();
		Selector selector = Selector.open();
		serverSocket.bind(new InetSocketAddress("127.0.0.1",port));
		serverChannel.configureBlocking(false);
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		while (true) {
			int n = selector.select();
			if (n == 0) {
				continue; 
			}
			Iterator it = selector.selectedKeys().iterator();
			while (it.hasNext()) {
				SelectionKey key = (SelectionKey) it.next();
				if (key.isAcceptable()) {
					ServerSocketChannel server = 
                                      (ServerSocketChannel) key.channel();
					SocketChannel channel = server.accept();
					registerChannel(selector, channel,  
                                       SelectionKey.OP_READ);
					sayHello(channel);
				}
				if (key.isReadable()) {
					readDataFromSocket(key);
				}
				//实际上是为了清除该选择键的ready集合
				it.remove();
			}
		}
	}

	protected void registerChannel(Selector selector,
			SelectableChannel channel, int ops) throws Exception {
		if (channel == null) {
			return; 
		}
		channel.configureBlocking(false);
		channel.register(selector, ops);
	}

	private ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
	protected void readDataFromSocket(SelectionKey key) throws Exception {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		int count;
		buffer.clear(); 
		while ((count = socketChannel.read(buffer)) > 0) {
			buffer.flip(); 
			while (buffer.hasRemaining()) {
				socketChannel.write(buffer);
			}
			buffer.clear(); 
		}
		if (count < 0) {
			socketChannel.close();
		}
	}
	private void sayHello(SocketChannel channel) throws Exception {
		buffer.clear();
		buffer.put("Hi there!\r\n".getBytes());
		buffer.flip();
		channel.write(buffer);
	}
} 

选择器对象是线程安全的,但它们包含的键集合不是。如果多个线程并发地访问一个选择器的键的集合,要注意同步及死锁问题。

基于通道服务器开发策略:a.对所有的可选择通道使用一个选择器,并将对就绪通道的服务委托给其他线程。如果某些通道要求比其他通道更高的响应速度,可以通过使用两个选择器来解决:

一个为快速连接服务,另一个为普通连接服务。



---------------------- android培训java培训、期待与您交流! ----------------------

详细请查看:http://edu.csdn.net/heima

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值