概述
Selector是NIO中實(shí)現(xiàn)I/O多路復(fù)用的關(guān)鍵類幔崖。Selector實(shí)現(xiàn)了通過一個線程管理多個Channel食店,從而管理多個網(wǎng)絡(luò)連接的目的。
Channel代表這一個網(wǎng)絡(luò)連接通道赏寇,我們可以將Channel注冊到Selector中以實(shí)現(xiàn)Selector對其的管理吉嫩。一個Channel可以注冊到多個不同的Selector中。
當(dāng)Channel注冊到Selector后會返回一個SelectionKey對象嗅定,該SelectionKey對象則代表這這個Channel和它注冊的Selector間的關(guān)系自娩。并且SelectionKey中維護(hù)著兩個很重要的屬性:interestOps、readyOps
interestOps是我們希望Selector監(jiān)聽Channel的哪些事件。我們將我們感興趣的事件設(shè)置到該字段忙迁,這樣在selection操作時脐彩,當(dāng)發(fā)現(xiàn)該Channel有我們所感興趣的事件發(fā)生時,就會將我們感興趣的事件再設(shè)置到readyOps中姊扔,這樣我們就能得知是哪些事件發(fā)生了以做相應(yīng)處理惠奸。
Selector的中的重要屬性
Selector中維護(hù)3個特別重要的SelectionKey集合,分別是
- keys:所有注冊到Selector的Channel所表示的SelectionKey都會存在于該集合中恰梢。keys元素的添加會在Channel注冊到Selector時發(fā)生佛南。
- selectedKeys:該集合中的每個SelectionKey都是其對應(yīng)的Channel在上一次操作selection期間被檢查到至少有一種SelectionKey中所感興趣的操作已經(jīng)準(zhǔn)備好被處理。該集合是keys的一個子集嵌言。
- cancelledKeys:執(zhí)行了取消操作的SelectionKey會被放入到該集合中嗅回。該集合是keys的一個子集。
下面的源碼解析會說明上面3個集合的用處
Selector 源碼解析
下面我們通過一段對Selector的使用流程講解來進(jìn)一步深入其實(shí)現(xiàn)原理摧茴。
首先先來段Selector最簡單的使用片段
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
int port = 5566;
serverChannel.socket().bind(new InetSocketAddress(port));
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while(true){
int n = selector.select();
if(n > 0) {
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while (iter.hasNext()) {
SelectionKey selectionKey = iter.next();
......
iter.remove();
}
}
}
1绵载、Selector的構(gòu)建
SocketChannel、ServerSocketChannel和Selector的實(shí)例初始化都通過SelectorProvider類實(shí)現(xiàn)苛白。
ServerSocketChannel.open();
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
SocketChannel.open();
public static SocketChannel open() throws IOException {
return SelectorProvider.provider().openSocketChannel();
}
Selector.open();
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
我們來進(jìn)一步的了解下SelectorProvider.provider()
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
① 如果配置了“java.nio.channels.spi.SelectorProvider”屬性娃豹,則通過該屬性值load對應(yīng)的SelectorProvider對象,如果構(gòu)建失敗則拋異常丸氛。
② 如果provider類已經(jīng)安裝在了對系統(tǒng)類加載程序可見的jar包中培愁,并且該jar包的源碼目錄META-INF/services包含有一個java.nio.channels.spi.SelectorProvider提供類配置文件著摔,則取文件中第一個類名進(jìn)行l(wèi)oad以構(gòu)建對應(yīng)的SelectorProvider對象缓窜,如果構(gòu)建失敗則拋異常。
③ 如果上面兩種情況都不存在谍咆,則返回系統(tǒng)默認(rèn)的SelectorProvider禾锤,即,sun.nio.ch.DefaultSelectorProvider.create();
④ 隨后在調(diào)用該方法摹察,即SelectorProvider.provider()恩掷。則返回第一次調(diào)用的結(jié)果。
這里我們看linux下面的sun.nio.ch.DefaultSelectorProvider
public class DefaultSelectorProvider {
/**
* Prevent instantiation.
*/
private DefaultSelectorProvider() { }
/**
* Returns the default SelectorProvider.
*/
public static SelectorProvider create() {
return new sun.nio.ch.EPollSelectorProvider();
}
}
可以看見供嚎,linux系統(tǒng)下sun.nio.ch.DefaultSelectorProvider.create(); 會生成一個sun.nio.ch.EPollSelectorProvider類型的SelectorProvider黄娘,這里對應(yīng)于linux系統(tǒng)的epoll
接下來看下 selector.open():
/**
* Opens a selector.
*
* <p> The new selector is created by invoking the {@link
* java.nio.channels.spi.SelectorProvider#openSelector openSelector} method
* of the system-wide default {@link
* java.nio.channels.spi.SelectorProvider} object. </p>
*
* @return A new selector
*
* @throws IOException
* If an I/O error occurs
*/
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
在得到sun.nio.ch.EPollSelectorProvider后調(diào)用openSelector()方法構(gòu)建Selector,這里會構(gòu)建一個EPollSelectorImpl對象克滴。
EPollSelectorImpl
class EPollSelectorImpl
extends SelectorImpl
{
// File descriptors used for interrupt
protected int fd0;
protected int fd1;
// The poll object
EPollArrayWrapper pollWrapper;
// Maps from file descriptors to keys
private Map<Integer,SelectionKeyImpl> fdToKey;
EPollSelectorImpl(SelectorProvider sp) throws IOException {
super(sp);
long pipeFds = IOUtil.makePipe(false);
fd0 = (int) (pipeFds >>> 32);
fd1 = (int) pipeFds;
try {
pollWrapper = new EPollArrayWrapper();
pollWrapper.initInterrupt(fd0, fd1);
fdToKey = new HashMap<>();
} catch (Throwable t) {
try {
FileDispatcherImpl.closeIntFD(fd0);
} catch (IOException ioe0) {
t.addSuppressed(ioe0);
}
try {
FileDispatcherImpl.closeIntFD(fd1);
} catch (IOException ioe1) {
t.addSuppressed(ioe1);
}
throw t;
}
}
EPollSelectorImpl構(gòu)造函數(shù)完成:
① EPollArrayWrapper的構(gòu)建逼争,EpollArrayWapper將Linux的epoll相關(guān)系統(tǒng)調(diào)用封裝成了native方法供EpollSelectorImpl使用。
② 通過EPollArrayWrapper向epoll注冊中斷事件
void initInterrupt(int fd0, int fd1) {
outgoingInterruptFD = fd1;
incomingInterruptFD = fd0;
epollCtl(epfd, EPOLL_CTL_ADD, fd0, EPOLLIN);
}
③ fdToKey:構(gòu)建文件描述符-SelectionKeyImpl映射表劝赔,所有注冊到selector的channel對應(yīng)的SelectionKey和與之對應(yīng)的文件描述符都會放入到該映射表中誓焦。
EPollArrayWrapper
EPollArrayWrapper完成了對epoll文件描述符的構(gòu)建,以及對linux系統(tǒng)的epoll指令操縱的封裝着帽。維護(hù)每次selection操作的結(jié)果杂伟,即epoll_wait結(jié)果的epoll_event數(shù)組移层。
EPollArrayWrapper操縱了一個linux系統(tǒng)下epoll_event結(jié)構(gòu)的本地數(shù)組。
* typedef union epoll_data {
* void *ptr;
* int fd;
* __uint32_t u32;
* __uint64_t u64;
* } epoll_data_t;
*
* struct epoll_event {
* __uint32_t events;
* epoll_data_t data;
* };
epoll_event的數(shù)據(jù)成員(epoll_data_t data)包含有與通過epoll_ctl將文件描述符注冊到epoll時設(shè)置的數(shù)據(jù)相同的數(shù)據(jù)赫粥。這里data.fd為我們注冊的文件描述符观话。這樣我們在處理事件的時候持有有效的文件描述符了。
EPollArrayWrapper將Linux的epoll相關(guān)系統(tǒng)調(diào)用封裝成了native方法供EpollSelectorImpl使用越平。
private native int epollCreate();
private native void epollCtl(int epfd, int opcode, int fd, int events);
private native int epollWait(long pollAddress, int numfds, long timeout,
int epfd) throws IOException;
上述三個native方法就對應(yīng)Linux下epoll相關(guān)的三個系統(tǒng)調(diào)用 // The fd of the epoll driver
private final int epfd;
// The epoll_event array for results from epoll_wait
private final AllocatedNativeObject pollArray;
// Base address of the epoll_event array
private final long pollArrayAddress;
// 用于存儲已經(jīng)注冊的文件描述符和其注冊等待改變的事件的關(guān)聯(lián)關(guān)系匪燕。在epoll_wait操作就是要檢測這里文件描述法注冊的事件是否有發(fā)生。
private final byte[] eventsLow = new byte[MAX_UPDATE_ARRAY_SIZE];
private final Map<Integer,Byte> eventsHigh = new HashMap<>();
EPollArrayWrapper() throws IOException {
// creates the epoll file descriptor
epfd = epollCreate();
// the epoll_event array passed to epoll_wait
int allocationSize = NUM_EPOLLEVENTS * SIZE_EPOLLEVENT;
pollArray = new AllocatedNativeObject(allocationSize, true);
pollArrayAddress = pollArray.address();
}
EPoolArrayWrapper構(gòu)造函數(shù)喧笔,創(chuàng)建了epoll文件描述符帽驯。構(gòu)建了一個用于存放epoll_wait返回結(jié)果的epoll_event數(shù)組。
ServerSocketChannel的構(gòu)建
ServerSocketChannel.open();
返回ServerSocketChannelImpl對象书闸,構(gòu)建linux系統(tǒng)下ServerSocket的文件描述符尼变。
// Our file descriptor
private final FileDescriptor fd;
// fd value needed for dev/poll. This value will remain valid
// even after the value in the file descriptor object has been set to -1
private int fdVal;
ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
super(sp);
this.fd = Net.serverSocket(true);
this.fdVal = IOUtil.fdVal(fd);
this.state = ST_INUSE;
}
將ServerSocketChannel注冊到Selector
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
protected final SelectionKey register(AbstractSelectableChannel ch,
int ops,
Object attachment)
{
if (!(ch instanceof SelChImpl))
throw new IllegalSelectorException();
SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);
k.attach(attachment);
synchronized (publicKeys) {
implRegister(k);
}
k.interestOps(ops);
return k;
}
① 構(gòu)建代表channel和selector間關(guān)系的SelectionKey對象
② implRegister(k)將channel注冊到epoll中
③ k.interestOps(int) 完成下面兩個操作:
a) 會將注冊的感興趣的事件和其對應(yīng)的文件描述存儲到EPollArrayWrapper對象的eventsLow或eventsHigh中,這是給底層實(shí)現(xiàn)epoll_wait時使用的浆劲。
b) 同時該操作還會將設(shè)置SelectionKey的interestOps字段嫌术,這是給我們程序員獲取使用的。
EPollSelectorImpl. implRegister
protected void implRegister(SelectionKeyImpl ski) {
if (closed)
throw new ClosedSelectorException();
SelChImpl ch = ski.channel;
int fd = Integer.valueOf(ch.getFDVal());
fdToKey.put(fd, ski);
pollWrapper.add(fd);
keys.add(ski);
}
① 將channel對應(yīng)的fd(文件描述符)和對應(yīng)的SelectionKeyImpl放到fdToKey映射表中牌借。
② 將channel對應(yīng)的fd(文件描述符)添加到EPollArrayWrapper中度气,并強(qiáng)制初始化fd的事件為0 ( 強(qiáng)制初始更新事件為0,因?yàn)樵撌录赡艽嬖谟谥氨蝗∠^的注冊中膨报。)
③ 將selectionKey放入到keys集合中磷籍。
Selection操作
selection操作有3中類型:
① select():該方法會一直阻塞直到至少一個channel被選擇(即,該channel注冊的事件發(fā)生了)為止现柠,除非當(dāng)前線程發(fā)生中斷或者selector的wakeup方法被調(diào)用院领。
② select(long time):該方法和select()類似,該方法也會導(dǎo)致阻塞直到至少一個channel被選擇(即够吩,該channel注冊的事件發(fā)生了)為止比然,除非下面3種情況任意一種發(fā)生:a) 設(shè)置的超時時間到達(dá);b) 當(dāng)前線程發(fā)生中斷周循;c) selector的wakeup方法被調(diào)用
③ selectNow():該方法不會發(fā)生阻塞强法,如果沒有一個channel被選擇也會立即返回。
我們主要來看看select()的實(shí)現(xiàn) :int n = selector.select();
public int select() throws IOException {
return select(0);
}
最終會調(diào)用到EPollSelectorImpl的doSelect
protected int doSelect(long timeout) throws IOException {
if (closed)
throw new ClosedSelectorException();
processDeregisterQueue();
try {
begin();
pollWrapper.poll(timeout);
} finally {
end();
}
processDeregisterQueue();
int numKeysUpdated = updateSelectedKeys();
if (pollWrapper.interrupted()) {
// Clear the wakeup pipe
pollWrapper.putEventOps(pollWrapper.interruptedIndex(), 0);
synchronized (interruptLock) {
pollWrapper.clearInterrupted();
IOUtil.drain(fd0);
interruptTriggered = false;
}
}
return numKeysUpdated;
}
① 先處理注銷的selectionKey隊(duì)列
② 進(jìn)行底層的epoll_wait操作
③ 再次對注銷的selectionKey隊(duì)列進(jìn)行處理
④ 更新被選擇的selectionKey
先來看processDeregisterQueue():
void processDeregisterQueue() throws IOException {
Set var1 = this.cancelledKeys();
synchronized(var1) {
if (!var1.isEmpty()) {
Iterator var3 = var1.iterator();
while(var3.hasNext()) {
SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();
try {
this.implDereg(var4);
} catch (SocketException var12) {
IOException var6 = new IOException("Error deregistering key");
var6.initCause(var12);
throw var6;
} finally {
var3.remove();
}
}
}
}
}
從cancelledKeys集合中依次取出注銷的SelectionKey湾笛,執(zhí)行注銷操作饮怯,將處理后的SelectionKey從cancelledKeys集合中移除。執(zhí)行processDeregisterQueue()后cancelledKeys集合會為空迄本。
protected void implDereg(SelectionKeyImpl ski) throws IOException {
assert (ski.getIndex() >= 0);
SelChImpl ch = ski.channel;
int fd = ch.getFDVal();
fdToKey.remove(Integer.valueOf(fd));
pollWrapper.remove(fd);
ski.setIndex(-1);
keys.remove(ski);
selectedKeys.remove(ski);
deregister((AbstractSelectionKey)ski);
SelectableChannel selch = ski.channel();
if (!selch.isOpen() && !selch.isRegistered())
((SelChImpl)selch).kill();
}
注銷會完成下面的操作:
① 將已經(jīng)注銷的selectionKey從fdToKey( 文件描述與SelectionKeyImpl的映射表 )中移除
② 將selectionKey所代表的channel的文件描述符從EPollArrayWrapper中移除
③ 將selectionKey從keys集合中移除硕淑,這樣下次selector.select()就不會再將該selectionKey注冊到epoll中監(jiān)聽
④ 也會將selectionKey從對應(yīng)的channel中注銷
⑤ 最后如果對應(yīng)的channel已經(jīng)關(guān)閉并且沒有注冊其他的selector了,則將該channel關(guān)閉
完成??的操作后,注銷的SelectionKey就不會出現(xiàn)先在keys置媳、selectedKeys以及cancelKeys這3個集合中的任何一個于樟。
接著我們來看EPollArrayWrapper.poll(timeout):
int poll(long timeout) throws IOException {
updateRegistrations();
updated = epollWait(pollArrayAddress, NUM_EPOLLEVENTS, timeout, epfd);
for (int i=0; i<updated; i++) {
if (getDescriptor(i) == incomingInterruptFD) {
interruptedIndex = i;
interrupted = true;
break;
}
}
return updated;
}
updateRegistrations()方法會將已經(jīng)注冊到該selector的事件(eventsLow或eventsHigh)通過調(diào)用epollCtl(epfd, opcode, fd, events); 注冊到linux系統(tǒng)中。
這里epollWait就會調(diào)用linux底層的epoll_wait方法拇囊,并返回在epoll_wait期間有事件觸發(fā)的entry的個數(shù)
再看updateSelectedKeys():
private int updateSelectedKeys() {
int entries = pollWrapper.updated;
int numKeysUpdated = 0;
for (int i=0; i<entries; i++) {
int nextFD = pollWrapper.getDescriptor(i);
SelectionKeyImpl ski = fdToKey.get(Integer.valueOf(nextFD));
// ski is null in the case of an interrupt
if (ski != null) {
int rOps = pollWrapper.getEventOps(i);
if (selectedKeys.contains(ski)) {
if (ski.channel.translateAndSetReadyOps(rOps, ski)) {
numKeysUpdated++;
}
} else {
ski.channel.translateAndSetReadyOps(rOps, ski);
if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {
selectedKeys.add(ski);
numKeysUpdated++;
}
}
}
}
return numKeysUpdated;
}
該方法會從通過EPollArrayWrapper pollWrapper 以及 fdToKey( 構(gòu)建文件描述符-SelectorKeyImpl映射表 )來獲取有事件觸發(fā)的SelectionKeyImpl對象迂曲,然后將SelectionKeyImpl放到selectedKey集合( 有事件觸發(fā)的selectionKey集合,可以通過selector.selectedKeys()方法獲得 )中寥袭,即selectedKeys路捧。并重新設(shè)置SelectionKeyImpl中相關(guān)的readyOps值。
但是传黄,這里要注意兩點(diǎn):
① 如果SelectionKeyImpl已經(jīng)存在于selectedKeys集合中杰扫,并且發(fā)現(xiàn)觸發(fā)的事件已經(jīng)存在于readyOps中了,則不會使numKeysUpdated++膘掰;這樣會使得我們無法得知該事件的變化章姓。
??這點(diǎn)說明了為什么我們要在每次從selectedKey中獲取到Selectionkey后,將其從selectedKey集合移除识埋,就是為了當(dāng)有事件觸發(fā)使selectionKey能正確到放入selectedKey集合中凡伊,并正確的通知給調(diào)用者。
再者窒舟,如果不將已經(jīng)處理的SelectionKey從selectedKey集合中移除系忙,那么下次有新事件到來時,在遍歷selectedKey集合時又會遍歷到這個SelectionKey,這個時候就很可能出錯了。比如砸泛,如果沒有在處理完OP_ACCEPT事件后將對應(yīng)SelectionKey從selectedKey集合移除,那么下次遍歷selectedKey集合時见剩,處理到到該SelectionKey杀糯,相應(yīng)的ServerSocketChannel.accept()將返回一個空(null)的SocketChannel扫俺。
② 如果發(fā)現(xiàn)channel所發(fā)生I/O事件不是當(dāng)前SelectionKey所感興趣,則不會將SelectionKeyImpl放入selectedKeys集合中固翰,也不會使numKeysUpdated++
epoll原理
select狼纬,poll,epoll都是IO多路復(fù)用的機(jī)制骂际。I/O多路復(fù)用就是通過一種機(jī)制疗琉,一個進(jìn)程可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒)歉铝,能夠通知程序進(jìn)行相應(yīng)的讀寫操作盈简。但select,poll,epoll本質(zhì)上都是同步I/O柠贤,因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫香浩,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負(fù)責(zé)進(jìn)行讀寫臼勉,異步I/O的實(shí)現(xiàn)會負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間邻吭。
epoll是Linux下的一種IO多路復(fù)用技術(shù),可以非常高效的處理數(shù)以百萬計(jì)的socket句柄宴霸。
在 select/poll中囱晴,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對所有監(jiān)視的文件描述符進(jìn)行掃描瓢谢,而epoll事先通過epoll_ctl()來注冊一 個文件描述符畸写,一旦基于某個文件描述符就緒時,內(nèi)核會采用類似callback的回調(diào)機(jī)制氓扛,迅速激活這個文件描述符艺糜,當(dāng)進(jìn)程調(diào)用epoll_wait() 時便得到通知。(此處去掉了遍歷文件描述符幢尚,而是通過監(jiān)聽回調(diào)的的機(jī)制破停。這正是epoll的魅力所在。)
如果沒有大量的idle -connection或者dead-connection尉剩,epoll的效率并不會比select/poll高很多真慢,但是當(dāng)遇到大量的idle- connection,就會發(fā)現(xiàn)epoll的效率大大高于select/poll理茎。
注意:linux下Selector底層是通過epoll來實(shí)現(xiàn)的黑界,當(dāng)創(chuàng)建好epoll句柄后,它就會占用一個fd值皂林,在linux下如果查看/proc/進(jìn)程id/fd/朗鸠,是能夠看到這個fd的,所以在使用完epoll后础倍,必須調(diào)用close()關(guān)閉烛占,否則可能導(dǎo)致fd被耗盡。
先看看使用c封裝的3個epoll系統(tǒng)調(diào)用:
-
int epoll_create(int size)
epoll_create建立一個epoll對象沟启。參數(shù)size是內(nèi)核保證能夠正確處理的最大句柄數(shù)忆家,多于這個最大數(shù)時內(nèi)核可不保證效果。 -
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epoll_ctl可以操作epoll_create創(chuàng)建的epoll德迹,如將socket句柄加入到epoll中讓其監(jiān)控芽卿,或把epoll正在監(jiān)控的某個socket句柄移出epoll。 -
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout)
epoll_wait在調(diào)用時胳搞,在給定的timeout時間內(nèi)卸例,所監(jiān)控的句柄中有事件發(fā)生時称杨,就返回用戶態(tài)的進(jìn)程。
大概看看epoll內(nèi)部是怎么實(shí)現(xiàn)的:
- epoll初始化時筷转,會向內(nèi)核注冊一個文件系統(tǒng)列另,用于存儲被監(jiān)控的句柄文件,調(diào)用epoll_create時旦装,會在這個文件系統(tǒng)中創(chuàng)建一個file節(jié)點(diǎn)页衙。同時epoll會開辟自己的內(nèi)核高速緩存區(qū),以紅黑樹的結(jié)構(gòu)保存句柄阴绢,以支持快速的查找店乐、插入、刪除呻袭。還會再建立一個list鏈表眨八,用于存儲準(zhǔn)備就緒的事件。
- 當(dāng)執(zhí)行epoll_ctl時左电,除了把socket句柄放到epoll文件系統(tǒng)里file對象對應(yīng)的紅黑樹上之外廉侧,還會給內(nèi)核中斷處理程序注冊一個回調(diào)函數(shù),告訴內(nèi)核篓足,如果這個句柄的中斷到了段誊,就把它放到準(zhǔn)備就緒list鏈表里。所以栈拖,當(dāng)一個socket上有數(shù)據(jù)到了连舍,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后,就把socket插入到就緒鏈表里涩哟。
- 當(dāng)epoll_wait調(diào)用時索赏,僅僅觀察就緒鏈表里有沒有數(shù)據(jù),如果有數(shù)據(jù)就返回贴彼,否則就sleep潜腻,超時時立刻返回。
epoll的兩種工作模式:
- LT:level-trigger器仗,水平觸發(fā)模式融涣,只要某個socket處于readable/writable狀態(tài),無論什么時候進(jìn)行epoll_wait都會返回該socket青灼。
- ET:edge-trigger暴心,邊緣觸發(fā)模式,只有某個socket從unreadable變?yōu)閞eadable或從unwritable變?yōu)閣ritable時杂拨,epoll_wait才會返回該socket。
最后順便說下在Linux系統(tǒng)中JDK NIO使用的是 LT 悯衬,而Netty epoll使用的是 ET弹沽。
后記
因?yàn)楸救藢τ?jì)算機(jī)系統(tǒng)組成以及C語言等知識比較欠缺檀夹,因?yàn)槲闹邢嚓P(guān)知識點(diǎn)的表示也相當(dāng)“膚淺”,如有不對不妥的地方望讀者指出策橘。同時我也會繼續(xù)加強(qiáng)對該方面知識點(diǎn)的學(xué)習(xí)~
參考
http://www.reibang.com/p/0d497fe5484a
http://remcarpediem.com/2017/04/02/Netty源碼-三-I-O模型和Java-NIO底層原理/
圣思園netty課程