<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      epoll源碼剖析

      1.前言

      好久好久沒有更新博客了,最近一直在實(shí)習(xí),刷算法找工作,忙里偷閑簡(jiǎn)單研究了一下epoll的源碼。也是由于面試的時(shí)候經(jīng)常被問到,我只會(huì)說那一套,什么epoll_create創(chuàng)建紅黑樹,以O(shè)(1)的方式去讀取數(shù)據(jù),它和poll與select的區(qū)別等等。本篇將從epoll的源碼層面重新學(xué)習(xí)epoll。

      2.應(yīng)用層的體現(xiàn)

      多路轉(zhuǎn)接(epoll)實(shí)現(xiàn)我在這篇文章中實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的epoll網(wǎng)絡(luò)server。感興趣的同學(xué)可以簡(jiǎn)單閱讀一下,我只挑其中關(guān)鍵的代碼來講一下應(yīng)用層是如何使用epoll的:

      #include"Sock.hpp"
      using namespace ns_Sock;
      #define NUM 128
      #include<sys/epoll.h>
      #include <cstdlib>
      void Usage(char* proc)
      {
          cout<<"Usage \n\t"<<proc<<" port"<<endl;
      }
      int main(int argc,char* argv[])
      {
          if(argc!=2)
          {
              Usage(argv[0]);
              exit(-1);
          }
          uint16_t port=(uint16_t)atoi(argv[1]);
          int listen_sock=Sock::Socket();
          Sock::Bind(listen_sock,port);
          Sock::Listen(listen_sock);
          //建立epoll模型,獲得epfd
          int epfd=epoll_create(128);
          //先添加listen_sock和它所關(guān)心的事件到內(nèi)核中
          struct epoll_event ev;
          ev.events=EPOLLIN;
          ev.data.fd=listen_sock;//雖然epoll_ctl有文件描述符,但是revs數(shù)組中的元素是epoll_event沒有fd,因此需要將fd添加都epoll_event的data字段中
          epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);
          //事件循環(huán)
          volatile bool quit=false;
          struct epoll_event revs[NUM];//由于epoll_wait的數(shù)組是輸出型參數(shù),因此需要接收
          while(!quit)
          {
              int timeout=1000;
              int n=epoll_wait(epfd,revs,NUM,-1);//epoll_wait會(huì)將epfd中就緒事件的epoll_event結(jié)構(gòu)體放在revs數(shù)組中,返回值表示數(shù)組大小
              switch(n)
              {
                  case 0:
                  cout<<"timeout....."<<endl;
                  break;
                  case -1:
                  cerr<<"epoll error"<<endl;
                  break;
                  default:
                  cout<<"有事件就緒了"<<endl;
                  //處理就緒事件
                  for(int i=0;i<n;i++)
                  {
                      int sock=revs[i].data.fd;//暫時(shí)方案
                      cout<<"文件描述符"<<sock<<"有數(shù)據(jù)就緒了"<<endl;
                      if(revs[i].events&EPOLLIN)//讀事件就緒
                      {
                          cout<<"文件描述符"<<sock<<"讀事件就緒了"<<endl;
                          if(sock==listen_sock)
                          {
                              int fd=Sock::Accept(listen_sock);
                              if(fd>=0)
                              {
                                  cout<<"獲取新鏈接成功了"<<endl;//此時(shí)還不能讀需要添加到epfd的空間中
                                  struct epoll_event _ev;
                                  _ev.events=EPOLLIN;
                                  _ev.data.fd=fd;
                                  epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&_ev);
                                  cout<<"已經(jīng)把"<<fd<<"添加到epfd空間中了"<<endl;
                              }
                          }
                          //正常的讀處理
                          else
                          {
                              cout<<"文件描述符"<<sock<<"正常數(shù)據(jù)準(zhǔn)備就緒"<<endl;
                              char buffer[1024];
                              ssize_t s=read(sock,buffer,sizeof(buffer)-1);
                              if(s>0)
                              {
                                  buffer[s]=0;
                                  cout<<"client["<<sock<<"]#"<<buffer<<endl;
                              }
                              else if(s==0)
                              {
                                  cout<<"client quit "<<sock<<"將被關(guān)閉"<<endl;
                                  close(sock);
                                  epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr);//將該套接字從epoll空間關(guān)注的位置刪除
                                  cout<<"Sock:"<<sock<<"delete from epoll success"<<endl;
                              }
                              else
                              {
                                  cout<<"recv error"<<endl;
                                  close(sock);
                                  epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr);
                                  cout<<"delete sock"<<sock<<endl;
                              }
                          }
                      }
                  }
              }
      
          }
      }
      

      這是我那篇博客的服務(wù)器端的代碼,使用telnet是可以直接訪問的,通過這段代碼我們可以發(fā)現(xiàn)調(diào)用epoll的過程以及一些細(xì)節(jié)。首先就是眾所周知的:

      • epoll_create創(chuàng)建一個(gè)epoll空間。
      • 接著調(diào)用epoll_ctl將一個(gè)文件描述符以及對(duì)該文件描述符需要關(guān)心的事件放進(jìn)epoll空間中。
      • 然后調(diào)用epoll_wait進(jìn)行等待就好了。事件就緒會(huì)使用epoll_wait這個(gè)函數(shù)來通知我們。

      但仔細(xì)看代碼還是會(huì)發(fā)現(xiàn)一些細(xì)節(jié),在epoll空間建立完成后,添加的第一個(gè)文件描述符就是listen_sock,并且關(guān)心它的讀事件。
      在epoll_wait成功的時(shí)候,會(huì)返回一個(gè)就緒事件的數(shù)組,通過遍歷這個(gè)數(shù)組去對(duì)數(shù)據(jù)進(jìn)行操作,但當(dāng)該數(shù)組元素表示的是listen_sock事件就緒的時(shí)候,需要在listen_sock中將新監(jiān)聽到的鏈接accept上來。這是對(duì)監(jiān)聽套接字獨(dú)有的一種處理方式,也說明epoll的回調(diào)函數(shù)不止一個(gè)觸發(fā)條件(數(shù)據(jù)就緒orl鏈接就緒)。

      其中這里還涉及到了一個(gè)重要的結(jié)構(gòu)體epoll_event,這個(gè)結(jié)構(gòu)體在內(nèi)核源碼中也經(jīng)常使用到,下面給出它在linux系統(tǒng)中的結(jié)構(gòu):

      typedef union epoll_data
      {
        void *ptr;
        int fd;
        uint32_t u32;
        uint64_t u64;
      } epoll_data_t;
      
      struct epoll_event
      {
        uint32_t events;	/* Epoll events */
        epoll_data_t data;	/* User data variable */
      } __EPOLL_PACKED;
      

      其中它的events表示的是事件,data又是一個(gè)結(jié)構(gòu)體,它其中的一個(gè)重要的東西就是fd。它表示該epoll_event是哪個(gè)套接字的。

      3.兩個(gè)重要結(jié)構(gòu)

      (1)eventpoll

      其中一個(gè)結(jié)構(gòu)名為eventpoll,當(dāng)調(diào)用epoll_create的時(shí)候內(nèi)核會(huì)自動(dòng)創(chuàng)建它,所以其實(shí)所謂的epoll空間僅僅是一個(gè)結(jié)構(gòu)體而已。

      struct eventpoll {
      	/*
      	struct ep_rb_tree {
      		struct epitem *rbh_root; 			
      	}
      	*/
      	ep_rb_tree rbr;      //rbr指向紅黑樹的根節(jié)點(diǎn)
      	int rbcnt; //紅黑樹中節(jié)點(diǎn)的數(shù)量(也就是添加了多少個(gè)TCP連接事件)
      	LIST_HEAD( ,epitem) rdlist;    //rdlist指向雙向鏈表的頭節(jié)點(diǎn);
      	/*	這個(gè)LIST_HEAD等價(jià)于 
      		struct {
      			struct epitem *lh_first;
      		}rdlist;
      	*/
      	int rdnum; //雙向鏈表中節(jié)點(diǎn)的數(shù)量(也就是有多少個(gè)TCP連接來事件了)
      	// ...略...
      };
      

      它包含了幾個(gè)內(nèi)容,有我們熟知的紅黑樹根節(jié)點(diǎn),還有所謂的就緒隊(duì)列(是一個(gè)雙鏈表),以及紅黑樹與就緒隊(duì)列的節(jié)點(diǎn)數(shù)量。

      (2)epitem

      這個(gè)是比較關(guān)鍵的一個(gè)結(jié)構(gòu)體,epoll_ctl將文件描述符以及所關(guān)心的事件插入到epoll空間中,插入的就是這么個(gè)玩意。

      struct epitem {
      	RB_ENTRY(epitem) rbn;
      	/*  RB_ENTRY(epitem) rbn等價(jià)于
      	struct {											
      		struct type *rbe_left;		//指向左子樹
      		struct type *rbe_right;		//指向右子樹
      		struct type *rbe_parent;	//指向父節(jié)點(diǎn)
      		int rbe_color;			    //該節(jié)點(diǎn)的顏色
      	} rbn
      	*/
       
      	LIST_ENTRY(epitem) rdlink;
      	/* LIST_ENTRY(epitem) rdlink等價(jià)于
      	struct {									
      		struct type *le_next;	//指向下個(gè)元素
      		struct type **le_prev;	//前一個(gè)元素的地址
      	}*/
       
      	int rdy; //判斷該節(jié)點(diǎn)是否同時(shí)存在與紅黑樹和雙向鏈表中
      	
      	int sockfd; //socket句柄
      	struct epoll_event event;  //存放用戶填充的事件
      };
      

      首先它包含了一個(gè)紅黑樹節(jié)點(diǎn)的結(jié)構(gòu),其次他又包含了雙向鏈表的結(jié)構(gòu),到這里我們就可以發(fā)現(xiàn),紅黑樹和雙向鏈表中插入的是同一個(gè)結(jié)構(gòu)體epitem。也很容易想到epoll_ctl的本質(zhì)其實(shí)就是使用傳入的參數(shù)來構(gòu)造一個(gè)epitem。
      因此它包含的參數(shù)顯而易見:sockfd表示該epitem對(duì)應(yīng)的套接字,epoll_event表示需要關(guān)心的該套接字的事件。

      4.四個(gè)函數(shù)

      (1)epoll_create源碼

      int epoll_create(int size) {
      	//size沒有什么卵用
      	if (size <= 0) return -1;
      	//tcp服務(wù),我也不懂,目前不是重點(diǎn)
      	nty_tcp_manager *tcp = nty_get_tcp_manager();
      	if (!tcp) return -1;
      	struct _nty_socket *epsocket = nty_socket_allocate(NTY_TCP_SOCK_EPOLL);
      	if (epsocket == NULL) {
      		nty_trace_epoll("malloc failed\n");
      		return -1;
      	}
      	//1.建立一個(gè)eventpoll
      	struct eventpoll *ep = (struct eventpoll*)calloc(1, sizeof(struct eventpoll));
      	if (!ep) {
      		nty_free_socket(epsocket->id, 0);
      		return -1;
      	}
      	//2.初始化紅黑樹指針為空,節(jié)點(diǎn)數(shù)為0
      	ep->rbcnt = 0;
      	RB_INIT(&ep->rbr);
      	//3.雙向鏈表頭指向空
      	LIST_INIT(&ep->rdlist);
      	//線程操作,暫時(shí)不用關(guān)心
      	if (pthread_mutex_init(&ep->mtx, NULL)) {
      		free(ep);
      		nty_free_socket(epsocket->id, 0);
      		return -2;
      	}
      	if (pthread_mutex_init(&ep->cdmtx, NULL)) {
      		pthread_mutex_destroy(&ep->mtx);
      		free(ep);
      		nty_free_socket(epsocket->id, 0);
      		return -2;
      	}
      	if (pthread_cond_init(&ep->cond, NULL)) {
      		pthread_mutex_destroy(&ep->cdmtx);
      		pthread_mutex_destroy(&ep->mtx);
      		free(ep);
      		nty_free_socket(epsocket->id, 0);
      		return -2;
      	}
      	if (pthread_spin_init(&ep->lock, PTHREAD_PROCESS_SHARED)) {
      		pthread_cond_destroy(&ep->cond);
      		pthread_mutex_destroy(&ep->cdmtx);
      		pthread_mutex_destroy(&ep->mtx);
      		free(ep);
      		nty_free_socket(epsocket->id, 0);
      		return -2;
      	}
      	//4.保存ep對(duì)象,通過epid可以在系統(tǒng)中找到eventpoll結(jié)構(gòu)
      	tcp->ep = (void*)ep;
      	epsocket->ep = (void*)ep;
      	return epsocket->id;
      }
      

      我們發(fā)現(xiàn)在epoll_create的時(shí)候會(huì)傳入一個(gè)參數(shù),但是這個(gè)參數(shù)似乎沒有意義,從這里就可以很簡(jiǎn)單的看出了,這個(gè)size除了判斷一下是不是大于0之外,之后什么都沒做。因此這個(gè)參數(shù)是沒有意義的。
      它的主要功能我在代碼中標(biāo)注了,大致如下:

      • 1.建立一個(gè)eventpoll對(duì)象,并為其分配空間。
      • 2.將該對(duì)象中紅黑樹根節(jié)點(diǎn)指向空,節(jié)點(diǎn)個(gè)數(shù)設(shè)為0。
      • 3.將該對(duì)象中雙向鏈表頭節(jié)點(diǎn)指向空。
      • 4.將eventpoll對(duì)象保存起來。以后可以通過epid找到它,并返回這個(gè)epid。

      (2)epoll_ctl源碼

      下面來看看epoll_ctl都干了些什么,在講解這個(gè)函數(shù)之前,我們還是先回頭看一下我們是怎么向它傳參的:

      epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);
      

      這是我們將listen_sock加入到epoll空間中的代碼,可以看到epfd用于尋找eventpoll結(jié)構(gòu)體,EPOLL_CTL_ADD表示我是要加入一個(gè)epoll節(jié)點(diǎn),listen_sock表示我加入的這個(gè)節(jié)點(diǎn)的文件描述符是listen_sock,&ev表示我關(guān)心的事件(使用地址傳參為了節(jié)省空間),這一切是不是很明確了呢?下面我們來閱讀這部分的源碼。

      int epoll_ctl(int epid, int op, int sockid, struct epoll_event *event) {
      	//tcp部分,暫時(shí)不管
      	nty_tcp_manager *tcp = nty_get_tcp_manager();
      	if (!tcp) return -1;
      	nty_trace_epoll(" epoll_ctl --> 1111111:%d, sockid:%d\n", epid, sockid);
      	
      	//1.通過傳入進(jìn)來的epid找到對(duì)應(yīng)的eventpoll結(jié)構(gòu)體
      	struct _nty_socket *epsocket = tcp->fdtable->sockfds[epid];
      	//struct _nty_socket *socket = tcp->fdtable->sockfds[sockid];
      	//nty_trace_epoll(" epoll_ctl --> 1111111:%d, sockid:%d\n", epsocket->id, sockid);
      	if (epsocket->socktype == NTY_TCP_SOCK_UNUSED) {
      		errno = -EBADF;
      		return -1;
      	}
      	if (epsocket->socktype != NTY_TCP_SOCK_EPOLL) {
      		errno = -EINVAL;
      		return -1;
      	}
      	nty_trace_epoll(" epoll_ctl --> eventpoll\n");
      	struct eventpoll *ep = (struct eventpoll*)epsocket->ep;
      	if (!ep || (!event && op != EPOLL_CTL_DEL)) {
      		errno = -EINVAL;
      		return -1;
      	}
      	//2.判斷如果是增加節(jié)點(diǎn)
      	if (op == EPOLL_CTL_ADD) {
      		pthread_mutex_lock(&ep->mtx);
      		//在棧區(qū)先創(chuàng)建一個(gè)對(duì)象,接收sockid
      		struct epitem tmp;
      		tmp.sockfd = sockid;
      		//查找這個(gè)節(jié)點(diǎn)是否在紅黑樹中,其實(shí)也就是根據(jù)epollfd查找的,這查找傳入tmp,可能表示的是紅黑樹節(jié)點(diǎn)類型,這是一個(gè)小細(xì)節(jié)
      		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);
      		if (epi) {
      			nty_trace_epoll("rbtree is exist\n");
      			pthread_mutex_unlock(&ep->mtx);
      			return -1;
      		}
      		//如果不存在才為結(jié)構(gòu)體分配完整空間
      		epi = (struct epitem*)calloc(1, sizeof(struct epitem));
      		if (!epi) {
      			pthread_mutex_unlock(&ep->mtx);
      			errno = -ENOMEM;
      			return -1;
      		}
      		//使用參數(shù)構(gòu)建這個(gè)epitem節(jié)點(diǎn)
      		epi->sockfd = sockid;
      		memcpy(&epi->event, event, sizeof(struct epoll_event));
      		epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi);
      		assert(epi == NULL);
      		ep->rbcnt ++;
      		pthread_mutex_unlock(&ep->mtx);
      	} else if (op == EPOLL_CTL_DEL) {
      		pthread_mutex_lock(&ep->mtx);
      		struct epitem tmp;
      		//先把fd分配給sockfd
      		tmp.sockfd = sockid;
      		//查找該節(jié)點(diǎn)是否已經(jīng)存在,這也說明是根據(jù)sockfd查找的
      		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);
      		if (!epi) {
      			nty_trace_epoll("rbtree no exist\n");
      			pthread_mutex_unlock(&ep->mtx);
      			return -1;
      		}
      	//存在則刪除即可
      		epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);
      		if (!epi) {
      			nty_trace_epoll("rbtree is no exist\n");
      			pthread_mutex_unlock(&ep->mtx);
      			return -1;
      		}
      		ep->rbcnt --;
      		free(epi);
      		pthread_mutex_unlock(&ep->mtx);
      	} else if (op == EPOLL_CTL_MOD) {
      	//修改該節(jié)點(diǎn)
      		struct epitem tmp;
      		tmp.sockfd = sockid;
      		struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);
      		//找到了就將該節(jié)點(diǎn)的事件進(jìn)行修改,這里還有一個(gè)細(xì)節(jié),修改之后會(huì)默認(rèn)加入EPOLLERR | EPOLLHUP這兩個(gè)事件監(jiān)聽,一個(gè)表示套接字發(fā)生錯(cuò)誤,一個(gè)表示套接字被掛斷。
      		if (epi) {
      			epi->event.events = event->events;
      			epi->event.events |= EPOLLERR | EPOLLHUP;
      		} else {
      			errno = -ENOENT;
      			return -1;
      		}
      	} else {
      		nty_trace_epoll("op is no exist\n");
      		assert(0);
      	}
      	return 0;
      }
      

      每根據(jù)一個(gè)不同的選擇就創(chuàng)建一個(gè)結(jié)構(gòu),并查找它是不是在紅黑樹中可能有點(diǎn)冗余,感覺直接在上面創(chuàng)建一個(gè)即可。
      這段代碼看起來不難,其實(shí)細(xì)節(jié)有很多的,首先先來說一下大體的步驟:

      • 根據(jù)傳入的epfd找到對(duì)應(yīng)的eventpoll結(jié)構(gòu)。
      • 根據(jù)不同的選擇類型進(jìn)行if-else判斷
        1.首先所有操作,都要先在棧區(qū)建立一個(gè)epitem的結(jié)構(gòu),然后將sock傳入其中。并使用紅黑樹查找函數(shù)傳入該結(jié)構(gòu)查找。
        2.根據(jù)有沒有來進(jìn)行構(gòu)造刪除,或者修改。
        以插入為例,沒有則在堆區(qū)構(gòu)建結(jié)構(gòu)體對(duì)象,然后向紅黑樹中插入,使用memcpy來加入事件。
        其中的兩個(gè)細(xì)節(jié)包括:

      都是先建立一個(gè)棧區(qū)對(duì)象,然后紅黑樹中進(jìn)行查找的,這里可以優(yōu)化一下。
      在修改的時(shí)候,會(huì)加入監(jiān)聽事件:文件描述符出錯(cuò),套接字被掛斷。

      (3)epoll_wait的源碼

      這個(gè)函數(shù)就是用于等待事件就緒,然后將他插入就緒隊(duì)列中的,其中這里的epoll_event是一個(gè)輸出型參數(shù),它通常表示一個(gè)數(shù)組的首地址。這里可以再回顧一下它是怎么進(jìn)行傳參的:

      int n=epoll_wait(epfd,revs,NUM,-1);
      

      其中epfd顯然還是去找eventpoll的,revs是一個(gè)數(shù)組首元素地址(我們建立一個(gè)數(shù)組,傳入數(shù)組名其實(shí)就可以了),NUM是一個(gè)整數(shù),表示多少個(gè)套接字就緒了就可以返回了,-1表示的是只要沒有文件描述符就緒,就永久阻塞。
      值得注意的是,這里我們沒有回調(diào)函數(shù)的實(shí)現(xiàn),也就是說暫時(shí)沒有套接字就緒了將它插入等待隊(duì)列中的操作,它的實(shí)現(xiàn)在后面講。

      int epoll_wait(int epid, struct epoll_event *events, int maxevents, int timeout) {
      	//tcp相關(guān),可惡我寫完這篇文章一定看看這是個(gè)啥東西
      	nty_tcp_manager *tcp = nty_get_tcp_manager();
      	if (!tcp) return -1;
      	//nty_socket_map *epsocket = &tcp->smap[epid];
      	//找到eventpoll
      	struct _nty_socket *epsocket = tcp->fdtable->sockfds[epid];
      	if (epsocket == NULL) return -1;
      	if (epsocket->socktype == NTY_TCP_SOCK_UNUSED) {
      		errno = -EBADF;
      		return -1;
      	}
      	if (epsocket->socktype != NTY_TCP_SOCK_EPOLL) {
      		errno = -EINVAL;
      		return -1;
      	}
      	struct eventpoll *ep = (struct eventpoll*)epsocket->ep;
      	if (!ep || !events || maxevents <= 0) {
      		errno = -EINVAL;
      		return -1;
      	}
      	//線程相關(guān),先不研究
      	if (pthread_mutex_lock(&ep->cdmtx)) {
      		if (errno == EDEADLK) {
      			nty_trace_epoll("epoll lock blocked\n");
      		}
      		assert(0);
      	}
      //如果rdnum為空,并且等待時(shí)間不為0的時(shí)候會(huì)等待一段時(shí)間
      	while (ep->rdnum == 0 && timeout != 0) {
      		ep->waiting = 1;
      		if (timeout > 0) {
      			struct timespec deadline;
      			clock_gettime(CLOCK_REALTIME, &deadline);
      			if (timeout >= 1000) {
      				int sec;
      				sec = timeout / 1000;
      				deadline.tv_sec += sec;
      				timeout -= sec * 1000;
      			}
      			deadline.tv_nsec += timeout * 1000000;
      			if (deadline.tv_nsec >= 1000000000) {
      				deadline.tv_sec++;
      				deadline.tv_nsec -= 1000000000;
      			}
      			int ret = pthread_cond_timedwait(&ep->cond, &ep->cdmtx, &deadline);
      			if (ret && ret != ETIMEDOUT) {
      				nty_trace_epoll("pthread_cond_timewait\n");
      				
      				pthread_mutex_unlock(&ep->cdmtx);
      				
      				return -1;
      			}
      			timeout = 0;
      		} else if (timeout < 0) {
      
      			int ret = pthread_cond_wait(&ep->cond, &ep->cdmtx);
      			if (ret) {
      				nty_trace_epoll("pthread_cond_wait\n");
      				pthread_mutex_unlock(&ep->cdmtx);
      
      				return -1;
      			}
      		}
      		ep->waiting = 0; 
      	}
      	pthread_mutex_unlock(&ep->cdmtx);
      
      	pthread_spin_lock(&ep->lock);
      
      	int cnt = 0;
      	//哪個(gè)少將哪個(gè)作為事件的數(shù)量
      	int num = (ep->rdnum > maxevents ? maxevents : ep->rdnum);
      	int i = 0;
      	while (num != 0 && !LIST_EMPTY(&ep->rdlist)) { //EPOLLET
      		//每次從雙向鏈表的頭節(jié)點(diǎn)取得一個(gè)一個(gè)的節(jié)點(diǎn)
      		struct epitem *epi = LIST_FIRST(&ep->rdlist);
      		//把這個(gè)節(jié)點(diǎn)從雙向鏈表中刪除
      		LIST_REMOVE(epi, rdlink);
      		//標(biāo)記這個(gè)節(jié)點(diǎn)不在雙向鏈表中,但是在紅黑樹中
      		epi->rdy = 0;//只有當(dāng)該節(jié)點(diǎn)再次被放入雙向鏈表中的時(shí)候,才會(huì)置為1
      		//把標(biāo)記的信息拷貝出來,拷貝到提供的events參數(shù)中
      		memcpy(&events[i++], &epi->event, sizeof(struct epoll_event));
      		//--,++的操作
      		num --;
      		cnt ++;
      		ep->rdnum --;
      	}
      	pthread_spin_unlock(&ep->lock);
      	return cnt;
      }
      

      (4)epoll_event_callback()

      1.客戶端connect()連入,服務(wù)器處于SYN_RCVD狀態(tài)時(shí)
      2.三路握手完成,服務(wù)器處于ESTABLISHED狀態(tài)時(shí)
      3.客戶端close()斷開連接,服務(wù)器處于FIN_WAIT_1和FIN_WAIT_2狀態(tài)時(shí)
      4.客戶端send/write()數(shù)據(jù),服務(wù)器可讀時(shí)
      5.服務(wù)器可以發(fā)送數(shù)據(jù)時(shí)

      它的作用是向雙向鏈表中添加一個(gè)紅黑樹的節(jié)點(diǎn)。它的參數(shù)有一個(gè)eventpoll類型,注意這里沒有傳epid,有一個(gè)sockid,有一個(gè)event。

      int epoll_event_callback(struct eventpoll *ep, int sockid, uint32_t event) {
      
      	struct epitem tmp;
      	tmp.sockfd = sockid;
      	//首先根據(jù)sockid找到紅黑樹中的節(jié)點(diǎn)
      	struct epitem *epi = RB_FIND(_epoll_rb_socket, &ep->rbr, &tmp);
      	if (!epi) {
      		nty_trace_epoll("rbtree not exist\n");
      		assert(0);
      	}
      	//根據(jù)epi->rdy判斷該節(jié)點(diǎn)是否在雙向鏈表里
      	if (epi->rdy) {
      	//如果在就將事件填入events中
      		epi->event.events |= event;
      		return 1;
      	} 
      	//如果不在,要做的就是將這個(gè)節(jié)點(diǎn)添加到雙向鏈表中的表頭位置
      	nty_trace_epoll("epoll_event_callback --> %d\n", epi->sockfd);
      	
      	pthread_spin_lock(&ep->lock);
      	epi->rdy = 1;
      	LIST_INSERT_HEAD(&ep->rdlist, epi, rdlink);
      	ep->rdnum ++;
      	pthread_spin_unlock(&ep->lock);
      
      	pthread_mutex_lock(&ep->cdmtx);
      
      	pthread_cond_signal(&ep->cond);
      	pthread_mutex_unlock(&ep->cdmtx);
      	return 0;
      }
      
      

      5.水平觸發(fā)和邊緣觸發(fā)

      1.狀態(tài)變化

      要講明白水平觸發(fā)和邊緣觸發(fā)就需要知道都有哪些狀態(tài)會(huì)觸發(fā),無非也就這四種:

      • 可讀:socket上有數(shù)據(jù)
      • 不可讀:socket上沒有數(shù)據(jù)了
      • 可寫:socket上有空間可寫
      • 不可寫:socket上無空間可寫

      對(duì)于水平觸發(fā)模式,一個(gè)事件只要有,就會(huì)一直被觸發(fā)。對(duì)于邊緣觸發(fā)模式,一個(gè)事件只要從無到有就會(huì)被觸發(fā)。

      2.LT模式

      讀:只要接收緩沖區(qū)上有未讀完的數(shù)據(jù),就會(huì)一直被觸發(fā)。
      寫:只要發(fā)送緩沖區(qū)上還有空間,就會(huì)一直被觸發(fā)。如果程序依賴于可寫事件觸發(fā)去發(fā)送數(shù)據(jù),要移除可寫事件,避免無意義的觸發(fā)。

      在LT模式下,讀事件觸發(fā)后,可以按需收取想要的字節(jié)數(shù),不用把本次接收到的數(shù)據(jù)收取干凈。

      3.ET模式

      讀:只有接收緩沖區(qū)上的數(shù)據(jù)從無到有,就會(huì)被觸發(fā)一次。
      寫:只有發(fā)送緩沖區(qū)上由不可寫到可寫,就會(huì)觸發(fā),(由滿到不滿)

      在ET模式下,當(dāng)讀事件觸發(fā)后,需要將數(shù)據(jù)一次性讀干凈。

      6.epoll中的鎖

      通過閱讀上面的代碼,我們發(fā)現(xiàn)訪問紅黑樹和訪問雙向鏈表的時(shí)候會(huì)加鎖。

      紅黑樹:互斥鎖,因?yàn)榧t黑樹是一個(gè)自平衡的二叉搜索樹,多線程訪問的時(shí)候可能改變樹的結(jié)構(gòu),因此加上互斥鎖。
      雙向鏈表:自旋鎖,是一個(gè)更輕量級(jí)的鎖,因?yàn)殡p向鏈表的結(jié)構(gòu)是不會(huì)改變的,通過自旋等待的方式獲取鎖,避免了切換上下文的開銷。

      7.epoll的致命缺點(diǎn)

      (1)多線程擴(kuò)展性

      場(chǎng)景一:同一個(gè) listen fd 在多個(gè) CPU 上調(diào)用 accept 系統(tǒng)調(diào)用

      水平觸發(fā):
      1.內(nèi)核收到了一個(gè)鏈接請(qǐng)求。同時(shí)喚醒了A和B兩個(gè)在epoll_wait上等待的線程。
      2.線程A epoll_wait成功,而線程B epoll_wait失敗。
      3.線程B其實(shí)不需要喚醒,造成驚群效應(yīng),消耗資源。
      
      邊緣觸發(fā):
      1.內(nèi)核收到了一個(gè)鏈接請(qǐng)求,由于是邊緣觸發(fā)所以只會(huì)喚醒一個(gè)線程,假設(shè)線程A被喚醒。
      2.A正在accept的時(shí)候,突然又來了一個(gè)鏈接,此時(shí)由于由于監(jiān)聽套接字處于就緒狀態(tài),沒有產(chǎn)生新事件,所以就不會(huì)發(fā)起通知。
      3.由于不會(huì)發(fā)起通知,所以必須由A再去處理該鏈接,這樣就造成了線程饑餓的問題。
      

      (2)epoll 所注冊(cè)的 fd(file descriptor) 和實(shí)際內(nèi)核中控制的結(jié)構(gòu) file description 擁有不同的生命周期

      在這里插入圖片描述
      epoll混淆了上圖中的進(jìn)程的文件描述符和系統(tǒng)中的文件描述符表。當(dāng)進(jìn)行EPOLLADD后,epoll其實(shí)監(jiān)聽的是系統(tǒng)級(jí)的文件描述符,所以當(dāng)close(fd)的時(shí)候,如果對(duì)應(yīng)的description只有它一個(gè)descriptor的時(shí)候,正常關(guān)閉。
      但如果它有多個(gè)descriptor與之對(duì)應(yīng)的話,就會(huì)發(fā)生即使將該文件描述符關(guān)閉了,但是還是可以接收到事件的情況。更糟糕的是,一旦你 close() 了這個(gè) fd,再也沒有機(jī)會(huì)把這個(gè)死掉的 fd 從 epoll 上摘除了。所以在刪除之前一定要進(jìn)行EPOLLDELETE。

      posted @ 2023-04-10 23:51  賣寂寞的小男孩  閱讀(12)  評(píng)論(0)    收藏  舉報(bào)  來源
      主站蜘蛛池模板: 鹿邑县| 亚洲高清WWW色好看美女| 女人张开腿让男人桶爽| 成人午夜免费无码视频在线观看 | 日日摸夜夜添狠狠添欧美| 亚洲第一二三区日韩国产| 欧美拍拍视频免费大全| 99国产精品欧美一区二区三区| 精品久久久久久国产| 亚洲精品三区四区成人少| 沁阳市| 亚洲国产精品日韩在线| 国产精品伦人视频免费看| 国产做无码视频在线观看| 在线aⅴ亚洲中文字幕 | 亚洲综合久久精品国产高清| 亚洲国产精品成人av网| 18禁无遮拦无码国产在线播放| 绥江县| 四虎永久播放地址免费| 五月综合激情婷婷六月| P尤物久久99国产综合精品| 亚洲国产综合性亚洲综合性| 国产一级小视频| 99精品日本二区留学生| 延安市| 少妇人妻偷人精品系列| 免费国产一级 片内射老| 国产精品国产三级国快看| 天天做天天爱夜夜爽导航| 亚洲风情亚aⅴ在线发布| 国产精品自拍三级在线观看| 日本边添边摸边做边爱喷水| 国产亚洲精品AA片在线播放天| 四虎女优在线视频免费看| 99久久久无码国产精品免费 | 国产福利社区一区二区| 日韩在线观看 一区二区| 国产午夜亚洲精品国产成人| 国产精品亚洲А∨怡红院| av无码精品一区二区三区|