關(guān)于NIO編程與epoll、IOCP大家應(yīng)該耳熟能詳了磷醋,先簡單回顧一下常見的結(jié)論:
- NIO是非阻塞IO
- epoll和IOCP分別是linux上和windows上對NIO操作系統(tǒng)級別的實現(xiàn)聊闯。
- NIO單機支持的連接數(shù)比BIO要高很多史简,解決了C10K問題居暖。
在獲取通信數(shù)據(jù)時,NIO使用輪詢的方式代替了阻塞的方式藤肢。但這樣做是否效率更高呢太闺?如果提高了,那原因是什么呢嘁圈?在看了很多的資料后我對以上問題依然不是特別清晰省骂。不過最近通過研究epoll與java selector的實現(xiàn),有了一些新的理解最住。
java中selector的select方法依然是阻塞的钞澳,其內(nèi)部調(diào)用的是不同操作系統(tǒng)中的實現(xiàn),windows下是IOCP的poll0(...)方法涨缚,這是一個native方法轧粟,如下。
private native int poll0(long pollAddress, int numfds,
int[] readFds, int[] writeFds, int[] exceptFds, long timeout);
// These arrays will hold result of native select().
// The first element of each array is the number of selected sockets.
// Other elements are file descriptors of selected sockets.
private final int[] readFds = new int [MAX_SELECTABLE_FDS + 1];//保存發(fā)生read的FD
private final int[] writeFds = new int [MAX_SELECTABLE_FDS + 1]; //保存發(fā)生write的FD
private final int[] exceptFds = new int [MAX_SELECTABLE_FDS + 1]; //保存發(fā)生except的FD
這個poll0()會監(jiān)聽pollWrapper中的FD有沒有數(shù)據(jù)進出脓魏,這會造成IO阻塞兰吟,直到有數(shù)據(jù)讀寫事件發(fā)生。比如茂翔,由于pollWrapper中保存的也有ServerSocketChannel的FD混蔼,所以只要ClientSocket發(fā)一份數(shù)據(jù)到ServerSocket,那么poll0()就會返回;又由于pollWrapper中保存的也有pipe的write端的FD珊燎,所以只要pipe的write端向FD發(fā)一份數(shù)據(jù)惭嚣,也會造成poll0()返回;如果這兩種情況都沒有發(fā)生悔政,那么poll0()就一直阻塞晚吞,也就是selector.select()會一直阻塞;如果有任何一種情況發(fā)生卓箫,那么selector.select()就會返回载矿,所有在OperationServer的run()里要用while (true) {},這樣就可以保證在selector接收到數(shù)據(jù)并處理完后繼續(xù)監(jiān)聽poll();
所以可以看出烹卒,NIO依然是阻塞式的IO闷盔,那么它和BIO的區(qū)別究竟在哪呢。
其實它的區(qū)別在于阻塞的位置不同旅急,BIO是阻塞在read方法(recvfrom)逢勾,而NIO阻塞在select方法。那么這樣做有什么好處呢藐吮。如果單純的改變阻塞的位置溺拱,自然是沒有什么變化的逃贝,但epoll等的實現(xiàn)的巧妙之處就在于,它利用回調(diào)機制迫摔,讓監(jiān)聽能夠只需要知曉哪些socket上的數(shù)據(jù)已經(jīng)準備好了沐扳,只需要處理這些線程上面的數(shù)據(jù)就行了。采用BIO句占,假設(shè)有1000個連接沪摄,需要開1000個線程,然后有1000個read的位置在阻塞纱烘,采用NIO編程杨拐,只需要1個線程,它利用select的輪詢方法配合epoll的事件機制及紅黑樹數(shù)據(jù)結(jié)構(gòu)擂啥,降低了其內(nèi)部輪詢的開銷哄陶,同時極大的減小了線程上下文切換的開銷。
那么在一秒鐘內(nèi)哺壶,epoll的epoll.wait方法究竟會輪詢多少次呢屋吨。這個問題我目前沒有得到直接的答案。我調(diào)用java的NIO庫的selector的selectNow()方法变骡,然后不做任何業(yè)務(wù)處理离赫,只增加了一個index++的操作,計算得到的結(jié)果大致是1秒鐘index增加40w塌碌,這說明了epoll.wait是一個很快的的操作(有資料也表明了O(1)時間復(fù)雜度)渊胸,看來NIO確實很適合處理大量并發(fā)連接的情況。
參考文章