摘要:基本概念多路復用是指內(nèi)核一旦發(fā)現(xiàn)進程指定的一個或者多個條件準備讀取它就通知該進程多路復用適用如下場合當客戶處理多個描述字時一般是交互式輸入和網(wǎng)絡套接口必須使用復用當一個客戶同時處理多個套接口時而這種情況是可能的但很少出現(xiàn)如果一個服務器既要處
基本概念
IO多路復用是指內(nèi)核一旦發(fā)現(xiàn)進程指定的一個或者多個IO條件準備讀取, 它就通知該進程. IO多路復用適用如下場合:
(1)當客戶處理多個描述字時(一般是交互式輸入和網(wǎng)絡套接口), 必須使用I/O復用.
(2)當一個客戶同時處理多個套接口時, 而這種情況是可能的, 但很少出現(xiàn).
(3)如果一個TCP服務器既要處理監(jiān)聽套接口, 又要處理已連接套接口, 一般也要用到I/O復用.
(4)如果一個服務器即要處理TCP, 又要處理UDP, 一般要使用I/O復用.
(5)如果一個服務器要處理多個服務或多個協(xié)議, 一般要使用I/O復用.
與多進程和多線程技術(shù)相比, I/O多路復用技術(shù)的最大優(yōu)勢是系統(tǒng)開銷小, 系統(tǒng)不必創(chuàng)建進程/線程, 也不必維護這些進程/線程, 從而大大減小了系統(tǒng)的開銷.
Selector(選擇器)在 Java 中, Selector 這個類是 select/epoll/poll 的外包類, 在不同的平臺上, 底層的實現(xiàn)可能有所不同, 但其基本原理是一樣的, 其原理圖如下所示:
所有的 Channel 都歸 Selector 管理, 這些 channel 中只要有至少一個有IO動作, 就可以通過 Selector.select 方法檢測到, 并且使用 selectedKeys 得到這些有 IO 的 channel, 然后對它們調(diào)用相應的IO操作.
我這里有一個服務端的例子:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class EpollServer { public static void main(String[] args) { try { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8000)); //不設置阻塞隊列 ssc.configureBlocking(false); Selector selector = Selector.open(); // 注冊 channel,并且指定感興趣的事件是 Accept ssc.register(selector, SelectionKey.OP_ACCEPT); ByteBuffer readBuff = ByteBuffer.allocate(1024); ByteBuffer writeBuff = ByteBuffer.allocate(128); writeBuff.put("received".getBytes()); writeBuff.flip(); while (true) { int nReady = selector.select(); Setkeys = selector.selectedKeys(); Iterator it = keys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); it.remove(); if (key.isAcceptable()) { // 創(chuàng)建新的連接,并且把連接注冊到selector上,而且, // 聲明這個channel只對讀操作感興趣。 SocketChannel socketChannel = ssc.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); readBuff.clear(); socketChannel.read(readBuff); readBuff.flip(); System.out.println("received : " + new String(readBuff.array())); key.interestOps(SelectionKey.OP_WRITE); } else if (key.isWritable()) { writeBuff.rewind(); SocketChannel socketChannel = (SocketChannel) key.channel(); socketChannel.write(writeBuff); key.interestOps(SelectionKey.OP_READ); } } } } catch (IOException e) { e.printStackTrace(); } } }
這個例子的關鍵點:
創(chuàng)建一個 ServerSocketChannel, 和一個 Selector, 并且把這個 server channel 注冊到 selector 上, 注冊的時間指定, 這個 channel 所感覺興趣的事件是 SelectionKey.OP_ACCEPT, 這個事件代表的是有客戶端發(fā)起TCP連接請求.
使用 select 方法阻塞住線程, 當 select 返回的時候, 線程被喚醒. 再通過 selectedKeys 方法得到所有可用 channel 的集合.
遍歷這個集合, 如果其中 channel 上有連接到達, 就接受新的連接, 然后把這個新的連接也注冊到 selector 中去.
如果有 channel 是讀, 那就把數(shù)據(jù)讀出來, 并且把它感興趣的事件改成寫. 如果是寫, 就把數(shù)據(jù)寫出去, 并且把感興趣的事件改成讀.
Selector.open 在不同的系統(tǒng)里實現(xiàn)方式不同
sunOS 使用 DevPollSelectorProvider, Linux就會使用 EPollSelectorProvider, 而默認則使用 PollSelectorProvider
也就是說 selector.select() 用來阻塞線程, 直到一個或多個 channle 進行 io 操作. 比如 SelectionKey.OP_ACCEPT.
然后使用 selector.selectedKeys() 方法獲取出, 這些通道.
那么 selector.select() 是怎么直到已經(jīng)有 io 操作了呢?
原因是因為 poll
poll# includeint poll ( struct pollfd * fds, unsigned int nfds, int timeout);
pollfd結(jié)構(gòu)體定義如下:
struct pollfd { int fd; /* 文件描述符 */ short events; /* 等待的事件 */ short revents; /* 實際發(fā)生了的事件 */ };
每一個 pollfd 結(jié)構(gòu)體指定了一個被監(jiān)視的文件描述符, 可以傳遞多個結(jié)構(gòu)體, 指示 poll() 監(jiān)視多個文件描述符.
每個結(jié)構(gòu)體的 events 域是監(jiān)視該文件描述符的事件掩碼, 由用戶來設置這個域. revents 域是文件描述符的操作結(jié)果事件掩碼, 內(nèi)核在調(diào)用返回時設置這個域.
events 域中請求的任何事件都可能在 revents 域中返回. 事件如下:
值 | 描述 |
---|---|
POLLIN | 有數(shù)據(jù)可讀 |
POLLRDNORM | 有普通數(shù)據(jù)可讀 |
POLLRDBAND | 有優(yōu)先數(shù)據(jù)可讀 |
POLLPRI | 有緊迫數(shù)據(jù)可讀 |
POLLOUT | 寫數(shù)據(jù)不會導致阻塞 |
POLLWRNORM | 寫普通數(shù)據(jù)不會導致阻塞 |
POLLWRBAND | 寫優(yōu)先數(shù)據(jù)不會導致阻塞 |
POLLMSGSIGPOLL | 消息可用 |
POLLER | 指定的文件描述符發(fā)生錯誤 |
POLLHUP | 指定的文件描述符掛起事件 |
POLLNVAL | 指定的文件描述符非法 |
說白了 poll() 可以監(jiān)視多個文件描述符.
如果返回值是 3, 我們需要逐個去遍歷出返回值是 3 的 socket, 然后在做對應操作.
epollpoll 方法有一個非常大的缺陷. poll 函數(shù)的返回值是一個整數(shù), 得到了這個返回值以后, 我們還是要逐個去檢查, 比如說, 有一萬個 socket 同時 poll, 返回值是3, 我們還是只能去遍歷這一萬個 socket, 看看它們是否有IO動作.
這就很低效了, 于是, 就有了 epoll 的改進, epoll可以直接通過“輸出參數(shù)”(可以理解為C語言中的指針類型的參數(shù)), 一個 epoll_event 數(shù)組, 直接獲得這三個 socket, 這就比較快了.
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/75540.html
摘要:它是在的基礎上改進的一種方案,通過對文件描述符上的事件狀態(tài)進行判斷。檢索新的事件執(zhí)行與相關的回調(diào)幾乎所有情況下,除了關閉的回調(diào)函數(shù),它們由計時器和排定的之外,其余情況將在此處阻塞。執(zhí)行事件的,例如或者。 前言 學習Node就繞不開異步IO, 異步IO又與事件循環(huán)息息相關, 而關于這一塊一直沒有仔細去了解整理過, 剛好最近在做項目的時候, 有了一些思考就記錄了下來, 希望能盡量將這一塊的...
摘要:最近在學習網(wǎng)絡編程和相關的知識,了解到是模式的網(wǎng)絡框架,但是提供了不同的來支持不同模式的網(wǎng)絡通信處理,包括同步異步阻塞和非阻塞。因為的版本使用的的模式,而則希望使用模式,而且版本沒有將的部分配置項暴露出來,比如說和。 ??最近在學習Java網(wǎng)絡編程和Netty相關的知識,了解到Netty是NIO模式的網(wǎng)絡框架,但是提供了不同的Channel來支持不同模式的網(wǎng)絡通信處理,包括同步、異步、...
摘要:的方法,的默認實現(xiàn)會判斷是否是類型注意自動拆箱,自動裝箱問題。適應自旋鎖鎖競爭是下的,會經(jīng)過用戶態(tài)到內(nèi)核態(tài)的切換,是比較花時間的。在中引入了自適應的自旋鎖,說明自旋的時間不固定,要不要自旋變得越來越聰明。 前言 只有光頭才能變強 之前在刷博客的時候,發(fā)現(xiàn)一些寫得比較好的博客都會默默收藏起來。最近在查閱補漏,有的知識點比較重要的,但是在之前的博客中還沒有寫到,于是趁著閑整理一下。 文本的...
摘要:,,都是多路復用的機制,,本質(zhì)上都是同步,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步則無需自己負責進行讀寫,異步的實現(xiàn)會負責把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。跟都能提供多路復用的解決方案。 select、poll、epoll:select,poll,epoll都是IO多路復用的機制 select,poll,epoll本質(zhì)上都是同步I/O,因為他們都需...
閱讀 2975·2021-11-16 11:51
閱讀 2608·2021-09-22 15:02
閱讀 3723·2021-08-04 10:21
閱讀 3605·2019-08-30 15:43
閱讀 1947·2019-08-30 11:04
閱讀 3599·2019-08-29 17:14
閱讀 490·2019-08-29 12:16
閱讀 2933·2019-08-28 18:31