【Java】NIO中Selector的建立原始碼分析

鬆餅人發表於2019-05-16

在使用Selector時首先需要通過靜態方法open建立Selector物件

1 public static Selector open() throws IOException {
2         return SelectorProvider.provider().openSelector();
3 }

可以看到首先是呼叫SelectorProvider的靜態方法provider,得到一個Selector的提供者

 

 1 public static SelectorProvider provider() {
 2     synchronized (lock) {
 3         if (provider != null)
 4             return provider;
 5         return AccessController.doPrivileged(
 6             new PrivilegedAction<SelectorProvider>() {
 7                 public SelectorProvider run() {
 8                         if (loadProviderFromProperty())
 9                             return provider;
10                         if (loadProviderAsService())
11                             return provider;
12                         provider = sun.nio.ch.DefaultSelectorProvider.create();
13                         return provider;
14                     }
15                 });
16     }
17 }

這段程式碼的邏輯也比較簡單,首先判斷provider是否已經產生,若已經產生,則直接返回現有的;若沒有,則需要呼叫AccessController的靜態方法doPrivileged,該方法是一個native方法,就不說了;可以看到在實現的PrivilegedAction介面中的run方法,做了三次判斷:

第一次是根據是系統屬性,使用ClassLoader類載入:

 1 private static boolean loadProviderFromProperty() {
 2     String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
 3     if (cn == null)
 4         return false;
 5     try {
 6         Class<?> c = Class.forName(cn, true,
 7                                    ClassLoader.getSystemClassLoader());
 8         provider = (SelectorProvider)c.newInstance();
 9         return true;
10     } catch (ClassNotFoundException x) {
11         throw new ServiceConfigurationError(null, x);
12     } catch (IllegalAccessException x) {
13         throw new ServiceConfigurationError(null, x);
14     } catch (InstantiationException x) {
15         throw new ServiceConfigurationError(null, x);
16     } catch (SecurityException x) {
17         throw new ServiceConfigurationError(null, x);
18     }
19 }

先獲取鍵值為"java.nio.channels.spi.SelectorProvider"的屬性,若沒有,則直接返回false;若設定了,則需要使用載入器直接載入系統屬性設定的java.nio.channels.spi.SelectorProvider的實現類,再通過反射機制直接產生例項物件並賦值給靜態成員provider,最後返回true。

第二次使用ServiceLoader載入:

 1 private static boolean loadProviderAsService() {
 2     ServiceLoader<SelectorProvider> sl =
 3         ServiceLoader.load(SelectorProvider.class,
 4                            ClassLoader.getSystemClassLoader());
 5     Iterator<SelectorProvider> i = sl.iterator();
 6     for (;;) {
 7         try {
 8             if (!i.hasNext())
 9                 return false;
10             provider = i.next();
11             return true;
12         } catch (ServiceConfigurationError sce) {
13             if (sce.getCause() instanceof SecurityException) {
14                 // Ignore the security exception, try the next provider
15                 continue;
16             }
17             throw sce;
18         }
19     }
20 }

有關ServiceLoader的載入過程可以看我的上一篇部落格【Java】ServiceLoader原始碼分析,在這裡我就不累贅了。
該方法呼叫ServiceLoader的load載入在"META-INF/services/"路徑下指明的SelectorProvider.class的實現類(其實是懶載入,在迭代時才真正載入)得到ServiceLoader物件,通過該物件的帶迭代器,遍歷這個迭代器;可以看到若是迭代器不為空,則直接返回迭代器儲存的第一個元素,即第一個被載入的類的物件,並賦值給provider,返回true;否則返回false;

第三次是使用的預設的SelectorProvider(windows環境為例):

1 public class DefaultSelectorProvider {
2     private DefaultSelectorProvider() {
3     }
4 
5     public static SelectorProvider create() {
6         return new WindowsSelectorProvider();
7     }
8 }

可以看到直接返回了WindowsSelectorProvider賦值給provider ;

此時provider無論如何都已經有了,接下來就是呼叫provider的openSelector方法。

WindowsSelectorProvider的openSelector方法:

1 public class WindowsSelectorProvider extends SelectorProviderImpl {
2     public WindowsSelectorProvider() {
3     }
4 
5     public AbstractSelector openSelector() throws IOException {
6         return new WindowsSelectorImpl(this);
7     }
8 }

可以看到僅僅是產生了WindowsSelectorImpl:

1 WindowsSelectorImpl(SelectorProvider var1) throws IOException {
2     super(var1);
3     this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal();
4     SinkChannelImpl var2 = (SinkChannelImpl)this.wakeupPipe.sink();
5     var2.sc.socket().setTcpNoDelay(true);
6     this.wakeupSinkFd = var2.getFDVal();
7     this.pollWrapper.addWakeupSocket(this.wakeupSourceFd, 0);
8 }

WindowsSelectorImpl首先呼叫父類SelectorImpl的構造方法:

 1 protected Set<SelectionKey> selectedKeys = new HashSet();
 2 protected HashSet<SelectionKey> keys = new HashSet();
 3 private Set<SelectionKey> publicKeys;
 4 private Set<SelectionKey> publicSelectedKeys;
 5 
 6 protected SelectorImpl(SelectorProvider var1) {
 7     super(var1);
 8     if (Util.atBugLevel("1.4")) {
 9         this.publicKeys = this.keys;
10         this.publicSelectedKeys = this.selectedKeys;
11     } else {
12         this.publicKeys = Collections.unmodifiableSet(this.keys);
13         this.publicSelectedKeys = Util.ungrowableSet(this.selectedKeys);
14     }
15 
16 }

SelectorImpl同樣呼叫父類AbstractSelector的構造:

1 protected AbstractSelector(SelectorProvider provider) {
2         this.provider = provider;
3 }

此時的provider就是剛才產生的WindowsSelectorProvider物件;
在SelectorImpl中還會對其成員有一系列的賦值操作;
上述都完成後才繼續完成WindowsSelectorImpl的構造。

WindowsSelectorImpl在進行this.wakeupSourceFd = ((SelChImpl)this.wakeupPipe.source()).getFDVal()之前,其wakeupPipe成員如下:

1 private final Pipe wakeupPipe = Pipe.open();

wakeupPipe管道通過Pipe.open()賦值:

1 public static Pipe open() throws IOException {
2     return SelectorProvider.provider().openPipe();
3 }

可以看到實際上 SelectorProvider.provider()的provider的openPipe方法,而這個provider就是WindowsSelectorProvider,而WindowsSelectorProvider繼承自SelectorProviderImpl,openPipe方法是在SelectorProviderImpl裡實現的:

1 public Pipe openPipe() throws IOException {
2     return new PipeImpl(this);
3 }

該方法直接產生了PipeImpl物件,並將WindowsSelectorProvider物件傳入進去:

1 PipeImpl(SelectorProvider var1) throws IOException {
2     try {
3         AccessController.doPrivileged(new PipeImpl.Initializer(var1));
4     } catch (PrivilegedActionException var3) {
5         throw (IOException)var3.getCause();
6     }
7 }

可以看到這個構造方法實際上是以特權模式執行的PipeImpl的內部類Initializer的run方法(doPrivileged需要的引數是PrivilegedExceptionAction介面的實現類,該介面只有run方法):
Initializer 的初始化:

 1 private class Initializer implements PrivilegedExceptionAction<Void> {
 2     private final SelectorProvider sp;
 3     private IOException ioe;
 4     
 5     private Initializer(SelectorProvider var2) {
 6         this.ioe = null;
 7         this.sp = var2;
 8     }
 9     ......
10 }

該構造方法給sp賦值為傳入進來的WindowsSelectorProvider物件,令ioe=null;
其所實現的run方法如下:

 1 public Void run() throws IOException {
 2     PipeImpl.Initializer.LoopbackConnector var1 = new PipeImpl.Initializer.LoopbackConnector();
 3     var1.run();
 4     if (this.ioe instanceof ClosedByInterruptException) {
 5         this.ioe = null;
 6         Thread var2 = new Thread(var1) {
 7             public void interrupt() {
 8             }
 9         };
10         var2.start();
11 
12         while(true) {
13             try {
14                 var2.join();
15                 break;
16             } catch (InterruptedException var4) {
17                 ;
18             }
19         }
20 
21         Thread.currentThread().interrupt();
22     }
23 
24     if (this.ioe != null) {
25         throw new IOException("Unable to establish loopback connection", this.ioe);
26     } else {
27         return null;
28     }
29 }

首先產生LoopbackConnector 物件,是Initializer的內部類,而且實現了Runnable介面:

1 private class LoopbackConnector implements Runnable {
2     private LoopbackConnector() {
3     }
4 }

其實現的run方法如下:

 1 public void run() {
 2     ServerSocketChannel var1 = null;
 3     SocketChannel var2 = null;
 4     SocketChannel var3 = null;
 5 
 6     try {
 7         ByteBuffer var4 = ByteBuffer.allocate(16);
 8         ByteBuffer var5 = ByteBuffer.allocate(16);
 9         InetAddress var6 = InetAddress.getByName("127.0.0.1");
10 
11         assert var6.isLoopbackAddress();
12 
13         InetSocketAddress var7 = null;
14 
15         while(true) {
16             if (var1 == null || !var1.isOpen()) {
17                 var1 = ServerSocketChannel.open();
18                 var1.socket().bind(new InetSocketAddress(var6, 0));
19                 var7 = new InetSocketAddress(var6, var1.socket().getLocalPort());
20             }
21 
22             var2 = SocketChannel.open(var7);
23             PipeImpl.RANDOM_NUMBER_GENERATOR.nextBytes(var4.array());
24 
25             do {
26                 var2.write(var4);
27             } while(var4.hasRemaining());
28 
29             var4.rewind();
30             var3 = var1.accept();
31 
32             do {
33                 var3.read(var5);
34             } while(var5.hasRemaining());
35 
36             var5.rewind();
37             if (var5.equals(var4)) {
38                 PipeImpl.this.source = new SourceChannelImpl(Initializer.this.sp, var2);
39                 PipeImpl.this.sink = new SinkChannelImpl(Initializer.this.sp, var3);
40                 break;
41             }
42 
43             var3.close();
44             var2.close();
45         }
46     } catch (IOException var18) {
47         try {
48             if (var2 != null) {
49                 var2.close();
50             }
51 
52             if (var3 != null) {
53                 var3.close();
54             }
55         } catch (IOException var17) {
56             ;
57         }
58 
59         Initializer.this.ioe = var18;
60     } finally {
61         try {
62             if (var1 != null) {
63                 var1.close();
64             }
65         } catch (IOException var16) {
66             ;
67         }
68 
69     }
70 
71 }

在這個run方法中首先定義了三個Channel一個ServerSocketChannel和兩個SocketChannel,然後申請了兩個十六位元組的ByteBuffer緩衝區,定義了一個回送地址var6;在while迴圈中先檢查ServerSocketChannel是否開啟了,若沒有則需要呼叫open方法開啟並賦值給var1,繫結地址為var6即回送地址,埠為0,令var7這個InetSocketAddress物件的地址是var6,埠是ServerSocketChannel的埠;ServerSocketChannel初始化完畢,初始化一個SocketChannel即var2,通過剛才的var7這個InetSocketAddress物件和ServerSocketChannel建立連線;

在PipeImpl裡有一個靜態成員:

1 private static final Random RANDOM_NUMBER_GENERATOR = new SecureRandom();

RANDOM_NUMBER_GENERATOR 聽名字就知道它是用來生成隨機數;
通過RANDOM_NUMBER_GENERATOR將從生成的隨機數存放在其中一個緩衝區ByteBuffer(var4)中,然後通過剛才連線好的SocketChannel即var2的write方法寫入緩衝區中的所有可用資料傳送給ServerSocketChannel;令var4緩衝區標誌置0;接著ServerSocketChannel呼叫accept方法偵聽剛才的連線產生一個SocketChannel物件var3,從var3中讀取資料存放在緩衝區var5中,令var5緩衝區標誌置0;然後比較var4和var5中的內容是否一致,若是一致則給PipeImpl的成員source和sink分別初始化儲存起來,若不一致就繼續迴圈,不斷地重複上述過程,直至Pipe通道成功建立;至此結束LoopbackConnector的run方法。
其在連線建立的過程中若是出現了異常會通過Initializer的ioe成員儲存異常。

再回到Initializer的run方法,在完成LoopbackConnector的run方法後,再根據ioe判讀是否在剛才的連線建立中出現了ClosedByInterruptException異常,若是出現還需要通過執行緒啟動LoopbackConnector的run方法直至其結束;若不是ClosedByInterruptException異常則直接丟擲IOException。

至此PipeImpl的構造結束,再回到WindowsSelectorImpl的構造,通過上述的操作產生的PipeImpl物件就賦值給了wakeupPipe成員;wakeupPipe的source就是剛才產生的SourceChannelImpl物件,wakeupPipe的sink就是剛才產生的SinkChannelImpl物件,再使用wakeupSourceFd儲存source的fdVal值和wakeupSinkFd儲存sink的fdVal值;並且開啟Nagle演算法,最後使用pollWrpper成員儲存source的fdVal值。

Selector到此建立完畢。

相關文章