JAVA NIO 选择器

转自:http://blog.csdn.net/aesop_wubo/article/details/9117655


为什么要使用选择器

通道处于就绪状态后,就可以在缓冲区之间传送数据。可以采用非阻塞模式来检查通道是否就绪,但非阻塞模式还会做别的任务,当有多个通道同时存在时,很难将检查通道是否就绪与其他任务剥离开来,或者说是这样做很复杂,即使完成了这样的功能,但每检查一次通道的就绪状态,就至少有一次系统调用,代价十分昂贵。当你轮询每个通道的就绪状态时,刚被检查的一个处于未就绪状态的通道,突然处于就绪状态,在下一次轮询之前是不会被察觉的。操作系统拥有这种检查就绪状态并通知就绪的能力,因此要充分利用操作系统提供的服务。在JAVA中,Selector类提供了这种抽象,拥有询问通道是否已经准备好执行每个I/0操作的能力,所以可以利用选择器来很好地解决以上问题。

如何使用选择器

使用选择器时,需要将一个或多个可选择的通道注册到选择器对象中,注册后会返回一个选择键,选择器会记住这些通道以及这些通道感兴趣的操作,还会追踪对应的通道是否已经就绪。调用选择器对象的select( )方法,当有通道就绪时,相关的键会被更新。可以获取选择键的集合,从而找到已经就绪的通道。

这里提到的选择器、选择键与可选择通道之间的关系如下图所示


先看一段使用选择器的代码

[java]  view plain copy print ?
  1. ServerSocketChannel serverChannel = ServerSocketChannel.open;  
  2. serverChannel.configureBlocking(false);  
  3. serverChannel.socket().bind(new InetSocketAddress(1234));  
  4. Selector selector = Selector.open();  
  5. serverChannel.register(selector, SelectionKey.OP_ACCEPT);  
  6.   
  7. while (true) {  
  8.     selector.select();  
  9.     Iterator<SelectionKey> itor = selector.selectedKeys().iterator();  
  10.     while (itor.hasNext()) {  
  11.         SelectionKey key = itor.next();  
  12.         itor.remove();  
  13.         if (key.isAcceptable()) {  
  14.             ServerSocketChannel server = (ServerSocketChannel) key.channel();  
  15.             SocketChannel channel = server.accept();  
  16.             channel.configureBlocking(false);  
  17.             channel.write(ByteBuffer.wrap("hello".getBytes()));  
  18.             channel.register(selector, SelectionKey.OP_READ);  
  19.         } else if (key.isReadable()) {  
  20.             //read();  
  21.         }  
  22.     }  
  23. }  
以上代码向选择器(selector)注册了两个可选择通道serverChannel和channel,其中serverChannel对accept感兴趣,channel对read感兴趣。当select()方法返回后,轮询选择键,找到准备就绪的通道,在这里是serverChannel的accept处于就绪状态,证明有连接到来,于是接受连接,得到一个通道并向这个通道写入"hello",同时对这个通道的read感兴趣,所以也将其注册到选择器上,当连接的另一端有数据到来时,key.isReadable()返回true,可以读取数据。

打开通道

从Selector源码中可以看到,open方法是交给selectorProvider处理的

[java]  view plain copy print ?
  1. public static Selector open() throws IOException {  
  2.     return SelectorProvider.provider().openSelector();  
  3.     }  
selectorProvider不同的操作系统有不同的实现,这里以windows为例
[java]  view plain copy print ?
  1. //WindowsSelectorProvider.java  
  2. public AbstractSelector openSelector() throws IOException {  
  3. <span style="white-space:pre">  </span>return new WindowsSelectorImpl(this);  
  4. }  
  5.   
  6. //WindowsSelectorImpl.java  
  7. WindowsSelectorImpl(SelectorProvider sp) throws IOException {  
  8.     super(sp);  
  9.     pollWrapper = new PollArrayWrapper(INIT_CAP);  
  10.     wakeupPipe = Pipe.open();  
  11.     wakeupSourceFd = ((SelChImpl)wakeupPipe.source()).getFDVal();  
  12.   
  13.     // Disable the Nagle algorithm so that the wakeup is more immediate  
  14.     SinkChannelImpl sink = (SinkChannelImpl)wakeupPipe.sink();  
  15.     (sink.sc).socket().setTcpNoDelay(true);  
  16.     wakeupSinkFd = ((SelChImpl)sink).getFDVal();  
  17.   
  18.     pollWrapper.addWakeupSocket(wakeupSourceFd, 0);  
  19. }  
  20.   
  21. //PollArrayWrapper.java  
  22. void addWakeupSocket(int fdVal, int index) {  
  23.     putDescriptor(index, fdVal);  
  24.     putEventOps(index, POLLIN);  
  25. }  
这里创建了一个管道pipe,并对pipe的source端的POLLIN事件感兴趣,addWakeupSocket方法将source的POLLIN事件标识为感兴趣的,当sink端有数据写入时,source对应的文件描述描wakeupSourceFd就会处于就绪状态。(事实上windows就是通过向管道中写数据来唤醒阻塞的选择器的)从以上代码可以看出:通道的打开实际上是构造了一个SelectorImpl对象。

注册通道

有了选择器后就可以注册通道了,注册通道的方法定义在SelectableChannel类中
[java]  view plain copy print ?
  1. public final SelectionKey register(Selector sel,int ops)  
  2.                             throws ClosedChannelException  
第二个参数ops指示选择键的interest集,可以是OP_READ、OP_WRITE、OP_ACCEPT等,分别表示对读、写、连接到来感兴趣。
注册通道的核心方法是implRegister,仍然以windows为例
[java]  view plain copy print ?
  1. protected void implRegister(SelectionKeyImpl ski) {  
  2.     synchronized (closeLock) {  
  3.         if (pollWrapper == null)  
  4.             throw new ClosedSelectorException();  
  5.         growIfNeeded();  
  6.         channelArray[totalChannels] = ski;  
  7.         ski.setIndex(totalChannels);  
  8.         fdMap.put(ski);  
  9.         keys.add(ski);  
  10.         pollWrapper.addEntry(totalChannels, ski);  
  11.         totalChannels++;  
  12.     }  
  13. }  
先看看growIfNeeded方法
[java]  view plain copy print ?
  1. private void growIfNeeded() {  
  2.       if (channelArray.length == totalChannels) {  
  3.           int newSize = totalChannels * 2// Make a larger array  
  4.           SelectionKeyImpl temp[] = new SelectionKeyImpl[newSize];  
  5.           System.arraycopy(channelArray, 1, temp, 1, totalChannels - 1);  
  6.           channelArray = temp;  
  7.           pollWrapper.grow(newSize);  
  8.       }  
  9.       if (totalChannels % MAX_SELECTABLE_FDS == 0) { // more threads needed  
  10.           pollWrapper.addWakeupSocket(wakeupSourceFd, totalChannels);  
  11.           totalChannels++;  
  12.           threadsCount++;  
  13.       }  
  14.   }  
做了两件事:
1、调整channelArray数组大小;
2、增加进行select的线程数,每达到MAX_SELECTABLE_FDS=1024个描述符,就增加一个线程,windows上select系统调用有最大文件描述符限制,一次只能轮询1024个文件描述符,如果多于1024个,需要多线程进行轮询。
implRegister方法设置选择键在数组中的位置,并将其加入已注册的键的集合(keys)中,fdMap是文件描述符到选择键的映射。

选择过程

有三种形式的select方法:select()、select(timeout)、selectNow(),最终都调用了doSelect()方法
[java]  view plain copy print ?
  1. protected int doSelect(long timeout) throws IOException {  
  2.   if (channelArray == null)  
  3.       throw new ClosedSelectorException();  
  4.   this.timeout = timeout; // set selector timeout  
  5.   processDeregisterQueue();  
  6.   if (interruptTriggered) {  
  7.       resetWakeupSocket();  
  8.       return 0;  
  9.   }  
  10.   // Calculate number of helper threads needed for poll. If necessary  
  11.   // threads are created here and start waiting on startLock  
  12.   adjustThreadsCount();  
  13.   finishLock.reset(); // reset finishLock  
  14.   // Wakeup helper threads, waiting on startLock, so they start polling.  
  15.   // Redundant threads will exit here after wakeup.  
  16.   startLock.startThreads();  
  17.   // do polling in the main thread. Main thread is responsible for  
  18.   // first MAX_SELECTABLE_FDS entries in pollArray.  
  19.   try {  
  20.       begin();  
  21.       try {  
  22.           subSelector.poll();  
  23.       } catch (IOException e) {  
  24.           finishLock.setException(e); // Save this exception  
  25.       }  
  26.       // Main thread is out of poll(). Wakeup others and wait for them  
  27.       if (threads.size() > 0)  
  28.           finishLock.waitForHelperThreads();  
  29.     } finally {  
  30.         end();  
  31.     }  
  32.   // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.  
  33.   finishLock.checkForException();  
  34.   processDeregisterQueue();  
  35.   int updated = updateSelectedKeys();  
  36.   // Done with poll(). Set wakeupSocket to nonsignaled  for the next run.  
  37.   resetWakeupSocket();  
  38.   return updated;  
  39. }  
processDeregisterQueue方法主要是对已取消的键集合进行处理,通过调用cancel()方法将选择键加入已取消的键集合中,这个键并不会立即注销,而是在下一次select操作时进行注销,注销操作在implDereg完成
[java]  view plain copy print ?
  1. protected void implDereg(SelectionKeyImpl ski) throws IOException{  
  2.     int i = ski.getIndex();  
  3.     assert (i >= 0);  
  4.     if (i != totalChannels - 1) {  
  5.         // Copy end one over it  
  6.         SelectionKeyImpl endChannel = channelArray[totalChannels-1];  
  7.         channelArray[i] = endChannel;  
  8.         endChannel.setIndex(i);  
  9.         pollWrapper.replaceEntry(pollWrapper, totalChannels - 1,  
  10.                                                             pollWrapper, i);  
  11.     }  
  12.     channelArray[totalChannels - 1] = null;  
  13.     totalChannels--;  
  14.     ski.setIndex(-1);  
  15.     if ( totalChannels != 1 && totalChannels % MAX_SELECTABLE_FDS == 1) {  
  16.         totalChannels--;  
  17.         threadsCount--; // The last thread has become redundant.  
  18.     }  
  19.     fdMap.remove(ski); // Remove the key from fdMap, keys and selectedKeys  
  20.     keys.remove(ski);  
  21.     selectedKeys.remove(ski);  
  22.     deregister(ski);  
  23.     SelectableChannel selch = ski.channel();  
  24.     if (!selch.isOpen() && !selch.isRegistered())  
  25.         ((SelChImpl)selch).kill();  
  26. }  

从channelArray中移除对应的通道,调整通道数和线程数,从map和keys中移除选择键,移除通道上的选择键并关闭通道

adjustThreadsCount这个方法前面提到过,与windows下select有文件描述符限制有关,需要多线程select

[java]  view plain copy print ?
  1. private void adjustThreadsCount() {  
  2.     if (threadsCount > threads.size()) {  
  3.         // More threads needed. Start more threads.  
  4.         for (int i = threads.size(); i < threadsCount; i++) {  
  5.             SelectThread newThread = new SelectThread(i);  
  6.             threads.add(newThread);  
  7.             newThread.setDaemon(true);  
  8.             newThread.start();  
  9.         }  
  10.     } else if (threadsCount < threads.size()) {  
  11.         // Some threads become redundant. Remove them from the threads List.  
  12.         for (int i = threads.size() - 1 ; i >= threadsCount; i--)  
  13.             threads.remove(i).makeZombie();  
  14.     }  
  15. }  
当前线程如果比threadsCount小就新建,如果比threadsCount大就移除,比较容易理解,来 看看线程的run方法

[java]  view plain copy print ?
  1. public void run() {  
  2.     while (true) { // poll loop  
  3.         // wait for the start of poll. If this thread has become  
  4.         // redundant, then exit.  
  5.         if (startLock.waitForStart(this))  
  6.             return;  
  7.         // call poll()  
  8.         try {  
  9.             subSelector.poll(index);  
  10.         } catch (IOException e) {  
  11.             // Save this exception and let other threads finish.  
  12.             finishLock.setException(e);  
  13.         }  
  14.         // notify main thread, that this thread has finished, and  
  15.         // wakeup others, if this thread is the first to finish.  
  16.         finishLock.threadFinished();  
  17.     }  
  18. }  

[java]  view plain copy print ?
  1. private synchronized boolean waitForStart(SelectThread thread) {  
  2.   while (true) {  
  3.       while (runsCounter == thread.lastRun) {  
  4.           try {  
  5.               startLock.wait();  
  6.           } catch (InterruptedException e) {  
  7.               Thread.currentThread().interrupt();  
  8.           }  
  9.       }  
  10.       if (thread.isZombie()) { // redundant thread  
  11.           return true// will cause run() to exit.  
  12.       } else {  
  13.           thread.lastRun = runsCounter; // update lastRun  
  14.           return false//   will cause run() to poll.  
  15.       }  
  16.   }  
  17. }  

可以看到这些helper线程创建好后,都阻塞在startLock.wait()上面,待主线程(doSelect方法)调用startLock.startThreads()后,waitForStart方法将返回false

[java]  view plain copy print ?
  1. // Triggers threads, waiting on this lock to start polling.  
  2. private synchronized void startThreads() {  
  3.     runsCounter++; // next run  
  4.     notifyAll(); // wake up threads.  
  5. }  
紧接着调用subSelector.poll(index)轮询各个文件描述符,同时主线程也在进行轮询,意思是所有线程(主线程和helper线程)都在轮询文件描述符。

如果在这期间,有文件描述符准备就绪,poll方法就会返回,不管是主线程返回还是helper线程返回,其他线程都会被唤醒

[java]  view plain copy print ?
  1. private synchronized void waitForHelperThreads() {  
  2.     if (threadsToFinish == threads.size()) {  
  3.         // no helper threads finished yet. Wakeup them up.  
  4.         wakeup();  
  5.     }  
  6.     while (threadsToFinish != 0) {  
  7.         try {  
  8.             finishLock.wait();  
  9.         } catch (InterruptedException e) {  
  10.             // Interrupted - set interrupted state.  
  11.             Thread.currentThread().interrupt();  
  12.         }  
  13.     }  
  14. }  
如果是主线程poll返回,会调用waitForHelperThreads方法唤醒helper线程,如果是其中一个helper线程返回,会调用threadFinished方法唤醒其他helper线程和主线程

[java]  view plain copy print ?
  1. private synchronized void threadFinished() {  
  2.            if (threadsToFinish == threads.size()) { // finished poll() first  
  3.                // if finished first, wakeup others  
  4.                wakeup();  
  5.            }  
  6.            threadsToFinish--;  
  7.            if (threadsToFinish == 0// all helper threads finished poll().  
  8.                notify();             // notify the main thread  
  9.        }  
因为整个轮询的过程中可能有其他键注册失败,或者调用了cancel方法,这里再次调用processDeregisterQueue()方法清理一下

现在把注意力放到updateSelectedKeys方法上,这个方法完成了选择键的更新,来看具体实现

[java]  view plain copy print ?
  1. private int updateSelectedKeys() {  
  2.     updateCount++;  
  3.     int numKeysUpdated = 0;  
  4.     numKeysUpdated += subSelector.processSelectedKeys(updateCount);  
  5.     for (SelectThread t: threads) {  
  6.         numKeysUpdated += t.subSelector.processSelectedKeys(updateCount);  
  7.     }  
  8.     return numKeysUpdated;  
  9. }  
对主线程和各个helper线程都调用了processSelectedKeys方法

[java]  view plain copy print ?
  1. private int processSelectedKeys(long updateCount) {  
  2.     int numKeysUpdated = 0;  
  3.     numKeysUpdated += processFDSet(updateCount, readFds,  
  4.                                    PollArrayWrapper.POLLIN,  
  5.                                    false);  
  6.     numKeysUpdated += processFDSet(updateCount, writeFds,  
  7.                                    PollArrayWrapper.POLLCONN |  
  8.                                    PollArrayWrapper.POLLOUT,  
  9.                                    false);  
  10.     numKeysUpdated += processFDSet(updateCount, exceptFds,  
  11.                                    PollArrayWrapper.POLLIN |  
  12.                                    PollArrayWrapper.POLLCONN |  
  13.                                    PollArrayWrapper.POLLOUT,  
  14.                                    true);  
  15.     return numKeysUpdated;  
  16. }  
processSelectedKeys方法分别对读选择键集、写选择键集,异常选择键集调用了processFDSet方法

[java]  view plain copy print ?
  1. private int processFDSet(long updateCount, int[] fds, int rOps,  
  2.                                  boolean isExceptFds){  
  3.           int numKeysUpdated = 0;  
  4.           for (int i = 1; i <= fds[0]; i++) {  
  5.               int desc = fds[i];  
  6.               if (desc == wakeupSourceFd) {  
  7.                   synchronized (interruptLock) {  
  8.                       interruptTriggered = true;  
  9.                   }  
  10.                   continue;  
  11.               }  
  12.               MapEntry me = fdMap.get(desc);  
  13.               // If me is null, the key was deregistered in the previous  
  14.               // processDeregisterQueue.  
  15.               if (me == null)  
  16.                   continue;  
  17.               SelectionKeyImpl sk = me.ski;  
  18.   
  19.               // The descriptor may be in the exceptfds set because there is  
  20.               // OOB data queued to the socket. If there is OOB data then it  
  21.               // is discarded and the key is not added to the selected set.  
  22.               if (isExceptFds &&  
  23.                   (sk.channel() instanceof SocketChannelImpl) &&  
  24.                   discardUrgentData(desc))  
  25.               {  
  26.                   continue;  
  27.               }  
  28.   
  29.               if (selectedKeys.contains(sk)) { // Key in selected set  
  30.                   if (me.clearedCount != updateCount) {  
  31.                       if (sk.channel.translateAndSetReadyOps(rOps, sk) &&  
  32.                           (me.updateCount != updateCount)) {  
  33.                           me.updateCount = updateCount;  
  34.                           numKeysUpdated++;  
  35.                       }  
  36.                   } else { // The readyOps have been set; now add  
  37.                       if (sk.channel.translateAndUpdateReadyOps(rOps, sk) &&  
  38.                           (me.updateCount != updateCount)) {  
  39.                           me.updateCount = updateCount;  
  40.                           numKeysUpdated++;  
  41.                       }  
  42.                   }  
  43.                   me.clearedCount = updateCount;  
  44.               } else { // Key is not in selected set yet  
  45.                   if (me.clearedCount != updateCount) {  
  46.                       sk.channel.translateAndSetReadyOps(rOps, sk);  
  47.                       if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) {  
  48.                           selectedKeys.add(sk);  
  49.                           me.updateCount = updateCount;  
  50.                           numKeysUpdated++;  
  51.                       }  
  52.                   } else { // The readyOps have been set; now add  
  53.                       sk.channel.translateAndUpdateReadyOps(rOps, sk);  
  54.                       if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) {  
  55.                           selectedKeys.add(sk);  
  56.                           me.updateCount = updateCount;  
  57.                           numKeysUpdated++;  
  58.                       }  
  59.                   }  
  60.                   me.clearedCount = updateCount;  
  61.               }  
  62.           }  
  63.           return numKeysUpdated;  
  64.       }  
  65.   }  
以上方法完成了更新选择键,步骤如下:

1、忽略wakeupSourceFd,这个文件描述符用于唤醒用的,与用户具体操作无关,所以忽略;

2、过滤fdMap中不存在的文件描述符,因为已被注销;

3、忽略oob data(搜了一下:out of band data指带外数据,有时也称为加速数据, 是指连接双方中的一方发生重要事情,想要迅速地通知对方 ),这也不是用户关心的;

4、如果通道的键还没有处于已选择的键的集合中,那么键的ready集合将被清空,然后表示操作系统发现的当前通道已经准备好的操作的比特掩码将被设置;

5、如果键在已选择的键的集合中。键的ready集合将被表示操作系统发现的当前已经准备好的操作的比特掩码更新。

来看下具体的更新ready集的方法translateAndUpdateReadyOps,不同的通道有不同的实现,以socketChannel为例

[java]  view plain copy print ?
  1. public boolean translateAndUpdateReadyOps(int ops, SelectionKeyImpl sk) {  
  2.     return translateReadyOps(ops, sk.nioReadyOps(), sk);  
  3. }  
[java]  view plain copy print ?
  1. public boolean translateReadyOps(int ops, int initialOps,  
  2.                                  SelectionKeyImpl sk) {  
  3.     int intOps = sk.nioInterestOps(); // Do this just once, it synchronizes  
  4.     int oldOps = sk.nioReadyOps();  
  5.     int newOps = initialOps;  
  6.   
  7.     if ((ops & PollArrayWrapper.POLLNVAL) != 0) {  
  8.         // This should only happen if this channel is pre-closed while a  
  9.         // selection operation is in progress  
  10.         // ## Throw an error if this channel has not been pre-closed  
  11.         return false;  
  12.     }  
  13.   
  14.     if ((ops & (PollArrayWrapper.POLLERR  
  15.                 | PollArrayWrapper.POLLHUP)) != 0) {  
  16.         newOps = intOps;  
  17.         sk.nioReadyOps(newOps);  
  18.         // No need to poll again in checkConnect,  
  19.         // the error will be detected there  
  20.         readyToConnect = true;  
  21.         return (newOps & ~oldOps) != 0;  
  22.     }  
  23.   
  24.     if (((ops & PollArrayWrapper.POLLIN) != 0) &&  
  25.         ((intOps & SelectionKey.OP_READ) != 0) &&  
  26.         (state == ST_CONNECTED))  
  27.         newOps |= SelectionKey.OP_READ;  
  28.   
  29.     if (((ops & PollArrayWrapper.POLLCONN) != 0) &&  
  30.         ((intOps & SelectionKey.OP_CONNECT) != 0) &&  
  31.         ((state == ST_UNCONNECTED) || (state == ST_PENDING))) {  
  32.         newOps |= SelectionKey.OP_CONNECT;  
  33.         readyToConnect = true;  
  34.     }  
  35.   
  36.     if (((ops & PollArrayWrapper.POLLOUT) != 0) &&  
  37.         ((intOps & SelectionKey.OP_WRITE) != 0) &&  
  38.         (state == ST_CONNECTED))  
  39.         newOps |= SelectionKey.OP_WRITE;  
  40.   
  41.     sk.nioReadyOps(newOps);  
  42.     return (newOps & ~oldOps) != 0;  
  43. }  
总之,最终是通过调用sk.nioReadyOps(newOps)来设置新的ready集的。

把目光转向selectkey类的几个方法

[java]  view plain copy print ?
  1. public final boolean isAcceptable() {  
  2.     return (readyOps() & OP_ACCEPT) != 0;  
  3. }  
  4. public final boolean isConnectable() {  
  5.     return (readyOps() & OP_CONNECT) != 0;  
  6. }  
  7. public final boolean isWritable() {  
  8.     return (readyOps() & OP_WRITE) != 0;  
  9. }  
  10. public final boolean isReadable() {  
  11.     return (readyOps() & OP_READ) != 0;  
  12. }  
可以看出一个通道是否可读、可写、可连接就是通过对ready集进行与操作来判断的。

总结一下doSelect:处理已取消的键集,通过本地方法poll轮询文件描述符,poll方法返回后更新已选择键的ready集。

唤醒

如果线程正阻塞在select方法上,调用wakeup方法会使阻塞的选择操作立即返回

[java]  view plain copy print ?
  1. public Selector wakeup() {  
  2.     synchronized (interruptLock) {  
  3.         if (!interruptTriggered) {  
  4.             setWakeupSocket();  
  5.             interruptTriggered = true;  
  6.         }  
  7.     }  
  8.     return this;  
  9. }  
[java]  view plain copy print ?
  1. //WindowsSelectorImpl.java  
  2. private void setWakeupSocket() {  
  3.     setWakeupSocket0(wakeupSinkFd);  
  4. }  
  5. private native void setWakeupSocket0(int wakeupSinkFd);  
  6.       
  7.   
  8. //WindowsSelectorImpl.c      
  9. JNIEXPORT void JNICALL  
  10. Java_sun_nio_ch_WindowsSelectorImpl_setWakeupSocket0(JNIEnv *env, jclass this,  
  11.                                                 jint scoutFd)  
  12. {  
  13.     /* Write one byte into the pipe */  
  14.     send(scoutFd, (char*)&POLLIN, 10);  
  15. }  
向pipe的sink端写入了一个字节,source文件描述符就会处于就绪状态,poll方法会返回,从而导致select方法返回。

这里有必要提一下打开通道pipe.open的实现细节,先看看windows的实现

[java]  view plain copy print ?
  1. public static Pipe open() throws IOException {  
  2.     return SelectorProvider.provider().openPipe();  
  3. }  
  4.   
  5. public Pipe openPipe() throws IOException {  
  6.   return new PipeImpl(this);  
  7. }  
  8.   
  9. PipeImpl(final SelectorProvider sp) throws IOException {  
  10.     try {  
  11.        AccessController.doPrivileged(new Initializer(sp));  
  12.     } catch (PrivilegedActionException x) {  
  13.        throw (IOException)x.getCause();  
  14.     }  
  15. }  
创建了一个PipeImpl对象, AccessController.doPrivileged调用后紧接着会执行initializer的run方法

[java]  view plain copy print ?
  1. public Void run() throws IOException {  
  2.     ServerSocketChannel ssc = null;  
  3.     SocketChannel sc1 = null;  
  4.     SocketChannel sc2 = null;  
  5.   
  6.     try {  
  7.         // loopback address  
  8.         InetAddress lb = InetAddress.getByName("127.0.0.1");  
  9.         assert (lb.isLoopbackAddress());  
  10.   
  11.         // bind ServerSocketChannel to a port on the loopback address  
  12.         ssc = ServerSocketChannel.open();  
  13.         ssc.socket().bind(new InetSocketAddress(lb, 0));  
  14.   
  15.         // Establish connection (assumes connections are eagerly  
  16.         // accepted)  
  17.         InetSocketAddress sa = new InetSocketAddress(lb, ssc.socket().getLocalPort());  
  18.         sc1 = SocketChannel.open(sa);  
  19.   
  20.         ByteBuffer bb = ByteBuffer.allocate(8);  
  21.         long secret = rnd.nextLong();  
  22.         bb.putLong(secret).flip();  
  23.         sc1.write(bb);  
  24.   
  25.         // Get a connection and verify it is legitimate  
  26.         for (;;) {  
  27.             sc2 = ssc.accept();  
  28.             bb.clear();  
  29.             sc2.read(bb);  
  30.             bb.rewind();  
  31.             if (bb.getLong() == secret)  
  32.                 break;  
  33.             sc2.close();  
  34.         }  
  35.   
  36.         // Create source and sink channels  
  37.         source = new SourceChannelImpl(sp, sc1);  
  38.         sink = new SinkChannelImpl(sp, sc2);  
  39.     } catch (IOException e) {  
  40.           
  41.     }  
  42. }  
该方法创建了两个通道sc1和sc2,这两个通道都绑定了本地ip,然后sc1向sc2写入了一个随机长整型的数,这两个通道分别做为管道的source与sink端。这相当于利用了回送地址(loopback address)自己向自己写数据,来达到通知的目的。
看看sun solaris的实现

[java]  view plain copy print ?
  1. PipeImpl(SelectorProvider sp) {  
  2.     int[] fdes = new int[2];  
  3.     IOUtil.initPipe(fdes, true);  
  4.     FileDescriptor sourcefd = new FileDescriptor();  
  5.     IOUtil.setfdVal(sourcefd, fdes[0]);  
  6.     source = new SourceChannelImpl(sp, sourcefd);  
  7.     FileDescriptor sinkfd = new FileDescriptor();  
  8.     IOUtil.setfdVal(sinkfd, fdes[1]);  
  9.     sink = new SinkChannelImpl(sp, sinkfd);  
  10. }  
  11.   
  12.   
  13. JNIEXPORT void JNICALL  
  14. Java_sun_nio_ch_IOUtil_initPipe(JNIEnv *env, jobject this,  
  15.                                     jintArray intArray, jboolean block)  
  16. {  
  17.     int fd[2];  
  18.     jint *ptr = 0;  
  19.   
  20.     if (pipe(fd) < 0) {  
  21.         JNU_ThrowIOExceptionWithLastError(env, "Pipe failed");  
  22.         return;  
  23.     }  
  24.     if (block == JNI_FALSE) {  
  25.         if ((configureBlocking(fd[0], JNI_FALSE) < 0)  
  26.             || (configureBlocking(fd[1], JNI_FALSE) < 0)) {  
  27.             JNU_ThrowIOExceptionWithLastError(env, "Configure blocking failed");  
  28.         }  
  29.     }  
  30.     ptr = (*env)->GetPrimitiveArrayCritical(env, intArray, 0);  
  31.     ptr[0] = fd[0];  
  32.     ptr[1] = fd[1];  
  33.     (*env)->ReleasePrimitiveArrayCritical(env, intArray, ptr, 0);  
  34. }  
可见solaris上采用系统调用pipe来完成管道的创建,相当于直接用了系统的管道,而windows上用的是loopback,同样是为了达到通知的目的,windows与与solaris采用了不同的方案。至于windows为什么不采用管道来实现,留个疑问??
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值