Netty之線程喚醒wakeup [續(xù)]
在之前的Netty之線程喚醒wakeup文章中, 介紹了如何喚醒Netty中的監(jiān)聽線程. 接下來我們通過部分源碼,結(jié)合一些命令和實(shí)驗(yàn),看一下它的實(shí)現(xiàn).
// WakeUp.javaimport java.net.InetSocketAddress;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.net.ServerSocket;public class WakeUp {public static void main(String[] args) throws Exception {// 底層創(chuàng)建管道Pipe和Epollfinal Selector selector = Selector.open();// 創(chuàng)建用于監(jiān)聽的socketServerSocketChannel serverSocketChannel = ServerSocketChannel.open();ServerSocket socket = serverSocketChannel.socket();socket.bind(new InetSocketAddress("127.0.0.1", 8080), 64);serverSocketChannel.configureBlocking(false);serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);new Thread() {@Overridepublic void run() {try {System.out.print("Thread[" + Thread.currentThread().getName() + "]invoke select\r\n");// 線程阻塞在這里int readyChannels = selector.select();} catch (Exception x) {x.printStackTrace();}System.out.print("Success...\r\n");}}.start();}}
我們先看第一句話
Selector selector = Selector.open();這句話是要創(chuàng)建一個Selector,在Linux系統(tǒng)下,是在EpollSelectorImpl.java文件中.


我們可以理解成
Selector selector = new EPollSelectorImpl(sp);
通過makePipe(false)方法創(chuàng)建了一個管道,管道的一端通過int fd0表示, 管道另一端通過int fd1表示 .
接下來就是創(chuàng)建epoll相關(guān)的信息.

接下來就是把管道的一端添加到epoll的紅黑樹上,交由epoll監(jiān)管.

把fd0添加到epoll中管理,這樣當(dāng)我們向fd1寫數(shù)據(jù)的時候,epoll發(fā)現(xiàn)fd0有數(shù)據(jù)可以讀取了(數(shù)據(jù)是從fd1流向fd0),于是就把與epoll對應(yīng)的那個線程給喚醒了(后面有圖片,可以形象一些).
當(dāng)我們執(zhí)行上面的Java程序時,通過查看進(jìn)程的文件描述符,就可以很明顯的看到這個管道

8號文件描述符用于監(jiān)聽客戶端的連接.

5和8號文件描述符添加到epoll中,交由epoll管理它們.

總結(jié)一下,創(chuàng)建一個epoll套接字用于管理其他文件描述符. 創(chuàng)建一個管道,其中管道的一端(5號套接字)交給epoll管理, 8號服務(wù)端套接字也交給epoll管理.效果如下圖

7號epoll套接字管理著5號和8號, 即便此時客戶端還沒有連接到8號監(jiān)聽套接字,此時IO線程阻塞住了,我們依然可以通過6號管道一端寫數(shù)據(jù),然后epoll監(jiān)聽到5號管道有數(shù)據(jù)到來,于是乎就可以把IO線程給喚醒 . 至于向6號管道寫什么數(shù)據(jù),不是那么重要,比如下面的視頻,我們向6號管道寫了一個1,甚至我們什么多不寫都可以,依然可以喚醒阻塞的IO線程.

做了一個簡短演示視頻
視頻中,通過echo命令給管道的一端寫入數(shù)據(jù),那么epoll'發(fā)現(xiàn)'管道的另一端有數(shù)據(jù)到來,于是從阻塞狀態(tài)'醒來'.
以上是在Linux平臺下,喚醒select線程是通過管道的方式,而在Windows平臺,卻不是通過管道的方式 .
我們把上面的Java代碼在Windows平臺編譯并運(yùn)行它

通過TCPView工具查看

我們發(fā)現(xiàn),在進(jìn)程內(nèi)有一對TCP連接,那么這對TCP連接就是類似上面說的管道的作用,用來喚醒阻塞在select方法的線程. 我們可以通過關(guān)閉某個TCP連接,看一下阻塞在select方法的線程是否會被喚醒.


做了一個簡短演示視頻
視頻中,通過關(guān)閉某個TCP連接,向?qū)Χ说腡CP發(fā)送數(shù)據(jù),那么epoll'發(fā)現(xiàn)'TCP的另一端被關(guān)閉,于是從阻塞狀態(tài)'醒來'.

本篇主要講解在Linux平臺和Windows平臺, 被阻塞在select方法的IO線程是通過什么手段被喚醒的.在Linux平臺是通過管道的方式, 而在Windows平臺是通過TCP連接的方式.
