Epoll例子的使用
第一部分:Epoll簡介
問題?: ?Select,Poll和Epoll的區別
答案?:?
Epoll和Select的區別
1.? 遍歷方式的區別 。select判斷是否有事件發生是遍歷的,而epoll是事件響應的,一旦句柄上有事件來了,就馬上選出來。
2.?數目的區別。select一般由一個內核參數(1024)限制了監聽的句柄數,但是epoll通常受限于打開文件的數目,通常會打得多。
3.?epoll自身,還有兩種觸發方式。水平觸發和邊緣觸發。邊沿觸發的效率更高(高了不少,但是編程的時候要小心處理每個時間,防止漏掉處理某些事件)。
Select
select()系統調用提供一個機制來實現同步多元I/O:
| #include?<sys/time.h> #include?<sys/types.h> #include?<unistd.h> int?select?(int?n,?fd_set?*readfds,?fd_set?*writefds,?fd_set?*exceptfds,?struct?timeval?*timeout); FD_CLR(int?fd,?fd_set?*set); FD_ISSET(int?fd,?fd_set?*set); FD_SET(int?fd,?fd_set?*set); FD_ZERO(fd_set?*set); |
調用select()將阻塞,直到指定的文件描述符準備好執行I/O,或者可選參數timeout指定的時間已經過去。
select()成功返回時,每組set都被修改以使它只包含準備好I/O的文件描述符。例如,假設有兩個文件描述符,值分別是7和9,被放在readfds中。當select()返回時,如果7仍然在set中,則這個文件描述符已經準備好被讀取而不會阻塞。如果9已經不在set中,則讀取它將可能會阻塞(我說可能是因為數據可能正好在select返回后就可用,這種情況下,下一次調用select()將返回文件描述符準備好讀取)。
第一個參數n,等于所有set中最大的那個文件描述符的值加1。當select()返回時,timeout參數的狀態在不同的系統中是未定義的,因此每次調用select()之前必須重新初始化timeout和文件描述符set。實際上,當前版本的Linux會自動修改timeout參數,設置它的值為剩余時間。因此,如果timeout被設置為5秒,然后在文件描述符準備好之前經過了3秒,則這一次調用select()返回時tv_sec將變為2。
因為文件描述符set是靜態創建的,它們對文件描述符的最大數目強加了一個限制,能夠放進set中的最大文件描述符的值由FD_SETSIZE指定。在Linux中,這個值是1024。本章后面我們還將看到這個限制的衍生物。
返回值和錯誤代碼
select()?成功時返回準備好I/O的文件描述符數目,包括所有三個set。如果提供了timeout,返回值可能是0;錯誤時返回-1,并且設置errno為下面幾個值之一:
EBADF:?給某個set提供了無效文件描述符。
EINTR::等待時捕獲到信號,可以重新發起調用。
EINVAL::參數n為負數,或者指定的timeout非法。
ENOMEM::不夠可用內存來完成請求。
Poll
和select()不一樣,poll()沒有使用低效的三個基于位的文件描述符set,而是采用了一個單獨的結構體pollfd數組,由fds指針指向這個組。pollfd結構體定義如下:
| #include?<sys/poll.h> int?poll?(struct?pollfd?*fds,?unsigned?int?nfds,?int?timeout); struct?pollfd?{ ????int?fd;?/*?file?descriptor?*/ ????short?events;?/*?requested?events?to?watch?*/ ????short?revents;?/*?returned?events?witnessed?*/ }; |
每一個pollfd結構體指定了一個被監視的文件描述符,可以傳遞多個結構體,指示poll()監視多個文件描述符。每個結構體的events域是監視該文件描述符的事件掩碼,由用戶來設置這個域。revents域是文件描述符的操作結果事件掩碼。內核在調用返回時設置這個域。events域中請求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN:有數據可讀。POLLRDNORM:有普通數據可讀。
POLLRDBAND:有優先數據可讀。
POLLPRI:有緊迫數據可讀。
POLLOUT:寫數據不會導致阻塞。
POLLWRNORM:寫普通數據不會導致阻塞。
POLLWRBAND:寫優先數據不會導致阻塞。
POLLMSG:SIGPOLL消息可用。
此外,revents域中還可能返回下列事件:
POLLER:指定的文件描述符發生錯誤。
POLLHUP:指定的文件描述符掛起事件。
POLLNVAL:指定的文件描述符非法。
這些事件在events域中無意義,因為它們在合適的時候總是會從revents中返回。使用poll()和select()不一樣,你不需要顯式地請求異常情況報告。
POLLIN | POLLPRI等價于select()的讀事件,POLLOUT | POLLWRBAND等價于select()的寫事件。POLLIN等價于POLLRDNORM | POLLRDBAND,而POLLOUT則等價于POLLWRNORM。
例如,要同時監視一個文件描述符是否可讀和可寫,我們可以設置events為POLLIN | POLLOUT。在poll返回時,我們可以檢查revents中的標志,對應于文件描述符請求的events結構體。如果POLLIN事件被設置,則文件描述符可以被讀取而不阻塞。如果POLLOUT被設置,則文件描述符可以寫入而不導致阻塞。這些標志并不是互斥的:它們可能被同時設置,表示這個文件描述符的讀取和寫入操作都會正常返回而不阻塞。
timeout參數指定等待的毫秒數,無論I/O是否準備好,poll都會返回。timeout指定為負數值表示無限超時;timeout為0指示poll調用立即返回并列出準備好I/O的文件描述符,但并不等待其它的事件。這種情況下,poll()就像它的名字那樣,一旦選舉出來,立即返回。
返回值和錯誤代碼
成功時,poll()返回結構體中revents域不為0的文件描述符個數;如果在超時前沒有任何事件發生,poll()返回0;失敗時,poll()返回-1,并設置errno為下列值之一:
EBADF:一個或多個結構體中指定的文件描述符無效。
EFAULT:fds指針指向的地址超出進程的地址空間。
EINTR:請求的事件之前產生一個信號,調用可以重新發起。
EINVAL:nfds參數超出PLIMIT_NOFILE值。
ENOMEM:可用內存不足,無法完成請求。
Epoll
Epoll的優點:
1.支持一個進程打開大數目的socket描述符(FD)
??? select?最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設置,默認值是2048。對于那些需要支持的上萬連接數目的IM服務器來說顯然太少了。這時候你一是可以選擇修改這個宏然后重新編譯內核,不過資料也同時指出這樣會帶來網絡效率的下降,二是可以選擇多進程的解決方案(傳統的?Apache方案),不過雖然Linux上面創建進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,所以也不是一種完美的方案。不過?epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。
2.IO效率不隨FD數目增加而線性下降
????傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由于網絡延時,任一時間只有部分的socket是"活躍"的,但是select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進行操作---這是因為在內核實現中epoll是根據每個fd上面的callback函數實現的。那么,只有"活躍"的socket才會主動的去調用?callback函數,其他idle狀態socket則不會,在這點上,epoll實現了一個"偽"AIO,因為這時候推動力在os內核。在一些?benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環境,epoll并不比select/poll有什么效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。
3.使用mmap加速內核與用戶空間的消息傳遞。
????這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核于用戶空間mmap同一塊內存實現的。而如果你想我一樣從2.5內核就關注epoll的話,一定不會忘記手工?mmap這一步的。
Epoll簡介:
在linux的網絡編程中,很長的時間都在使用select來做事件觸發。在linux新的內核中,有了一種替換它的機制,就是epoll。相比于select,epoll最大的好處在于它不會隨著監聽fd數目的增長而降低效率。因為在內核中的select實現中,它是采用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。
epoll的接口非常簡單,一共就三個函數:
1. int epoll_create(int size);
創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。第一個參數是epoll_create()[上面一個函數]的返回值,第二個參數表示動作,用三個宏來表示:
EPOLL_CTL_ADD:注冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是需要監聽的fd,第四個參數是告訴內核需要監聽什么事,struct epoll_event結構如下:
struct epoll_event {
? __uint32_t events;? /* Epoll events */
? epoll_data_t data;? /* User data variable */
};
events可以是以下幾個宏的集合:
EPOLLIN?:表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET:?將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生,類似于select()調用。參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。
令人高興的是,2.6內核的epoll比其2.5開發版本的/dev/epoll簡潔了許多,所以,大部分情況下,強大的東西往往是簡單的。唯一有點麻煩是epoll有2種工作方式:
LT和ET(水平觸發和邊緣觸發)
LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket.在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。 ET (edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再為那個文件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態了(比如,你在發送,接收或者接收請求,或者發送接收的數據少于一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。
第二部分:Epoll的三個例子
epoll用到的所有函數都是在頭文件sys/epoll.h中聲明的,下面簡要說明所用到的數據結構和函數:
所用到的數據結構
| 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_event 被用于注冊所感興趣的事件和回傳所發生待處理的事件,其中epoll_data 聯合體用來保存觸發事件的某個文件描述符相關的數據,例如一個client連接到服務器,服務器通過調用accept函數可以得到于這個client對應的socket文件描述符,可以把這文件描述符賦給epoll_data的fd字段以便后面的讀寫操作在這個文件描述符上進行。epoll_event 結構體的events字段是表示感興趣的事件和被觸發的事件可能的取值為:EPOLLIN :表示對應的文件描述符可以讀; EPOLLOUT:表示對應的文件描述符可以寫; EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來); EPOLLERR:表示對應的文件描述符發生錯誤; EPOLLHUP:表示對應的文件描述符被掛斷; EPOLLET:表示對應的文件描述符有事件發生; 所用到的函數: 1、epoll_create函數 函數聲明:int epoll_create(int size)? 該函數生成一個epoll專用的文件描述符,其中的參數是指定生成描述符的最大范圍(我覺得這個參數和select函數的第一個參數應該是類似的但是該怎么設置才好,我也不太清楚)。 2、epoll_ctl函數 函數聲明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 該函數用于控制某個文件描述符上的事件,可以注冊事件,修改事件,刪除事件。 參數:epfd:由 epoll_create 生成的epoll專用的文件描述符; op:要進行的操作例如注冊事件,可能的取值: EPOLL_CTL_ADD 注冊; EPOLL_CTL_MOD 修改; EPOLL_CTL_DEL 刪除 fd:關聯的文件描述符; event:指向epoll_event的指針; 如果調用成功返回0,不成功返回-1 3、epoll_wait函數 函數聲明:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout) 該函數用于輪詢I/O事件的發生; 參數: epfd:由epoll_create 生成的epoll專用的文件描述符; epoll_event:用于回傳代處理事件的數組; maxevents:每次能處理的事件數; timeout:等待I/O事件發生的超時值; 返回發生事件數。
例子1
| #include?<iostream> #include?<sys/socket.h> #include?<sys/epoll.h> #include?<netinet/in.h> #include?<arpa/inet.h> #include?<fcntl.h> #include?<unistd.h> #include?<stdio.h> #define?MAXLINE?10 #define?OPEN_MAX?100 #define?LISTENQ?20 #define?SERV_PORT?5555 #define?INFTIM?1000 void?setnonblocking(int?sock) { ????int?opts; ????opts?=?fcntl(sock,?F_GETFL); ????if(opts?<?0) ????{ ????????perror("fcntl(sock,GETFL)"); ????????exit(1); ????} ????opts?=?opts?|?O_NONBLOCK; ????if(fcntl(sock,?F_SETFL,?opts)?<?0) ????{ ????????perror("fcntl(sock,SETFL,opts)"); ????????exit(1); ????} } int?main() { ????int?i,?maxi,?listenfd,?connfd,?sockfd,?epfd,?nfds; ????ssize_t?n; ????char?line[MAXLINE]; ????socklen_t?clilen; ????//聲明epoll_event結構體的變量,ev用于注冊事件,數組用于回傳要處理的事件 ????struct?epoll_event?ev,?events[20]; ????//生成用于處理accept的epoll專用的文件描述符 ????epfd?=?epoll_create(256); ????struct?sockaddr_in?clientaddr; ????struct?sockaddr_in?serveraddr; ????listenfd?=?socket(AF_INET,?SOCK_STREAM,?0); ????//把socket設置為非阻塞方式 ????setnonblocking(listenfd); ????//設置與要處理的事件相關的文件描述符 ????ev.data.fd?=?listenfd; ????//設置要處理的事件類型 ????ev.events?=?EPOLLIN?|?EPOLLET; ????//注冊epoll事件 ????epoll_ctl(epfd,?EPOLL_CTL_ADD,?listenfd,?&ev); ????bzero(&serveraddr,?sizeof(serveraddr)); ????serveraddr.sin_family?=?AF_INET; ????char?*local_addr?=?"200.200.200.204"; ????inet_aton(local_addr,?&(serveraddr.sin_addr));?//htons(SERV_PORT); ????serveraddr.sin_port?=?htons(SERV_PORT); ????bind(listenfd,?(sockaddr?*)&serveraddr,?sizeof(serveraddr)); ????listen(listenfd,?LISTENQ); ????maxi?=?0; ????for?(?;?;?) ????{ ????????//等待epoll事件的發生 ????????nfds?=?epoll_wait(epfd,?events,?20,?500); ????????//處理所發生的所有事件 ????????for(i?=?0;?i?<?nfds;?++i) ????????{ ????????????if(events[i].data.fd?==?listenfd) ????????????{ ????????????????connfd?=?accept(listenfd,?(sockaddr?*)&clientaddr,?&clilen); ????????????????if(connfd?<?0) ????????????????{ ????????????????????perror("connfd<0"); ????????????????????exit(1); ????????????????} ????????????????setnonblocking(connfd); ????????????????char?*str?=?inet_ntoa(clientaddr.sin_addr); ????????????????std::cout?<<?"connect?from?"?<?_u115???tr?<<?std::endl; ????????????????//設置用于讀操作的文件描述符 ????????????????ev.data.fd?=?connfd; ????????????????//設置用于注測的讀操作事件 ????????????????ev.events?=?EPOLLIN?|?EPOLLET; ????????????????//注冊ev ????????????????epoll_ctl(epfd,?EPOLL_CTL_ADD,?connfd,?&ev); ????????????} ????????????else?if(events[i].events?&?EPOLLIN) ????????????{ ????????????????if?(?(sockfd?=?events[i].data.fd)?<?0)?continue; ????????????????if?(?(n?=?read(sockfd,?line,?MAXLINE))?<?0) ????????????????{ ????????????????????if?(errno?==?ECONNRESET) ????????????????????{ ????????????????????????close(sockfd); ????????????????????????events[i].data.fd?=?-1; ????????????????????} ????????????????????else ????????????????????????std::cout?<<?"readline?error"?<<?std::endl; ????????????????} ????????????????else?if?(n?==?0) ????????????????{ ????????????????????close(sockfd); ????????????????????events[i].data.fd?=?-1; ????????????????} ????????????????//設置用于寫操作的文件描述符 ????????????????ev.data.fd?=?sockfd; ????????????????//設置用于注測的寫操作事件 ????????????????ev.events?=?EPOLLOUT?|?EPOLLET; ????????????????//修改sockfd上要處理的事件為EPOLLOUT ????????????????epoll_ctl(epfd,?EPOLL_CTL_MOD,?sockfd,?&ev); ????????????} ????????????else?if(events[i].events?&?EPOLLOUT) ????????????{ ????????????????sockfd?=?events[i].data.fd; ????????????????write(sockfd,?line,?n); ????????????????//設置用于讀操作的文件描述符 ????????????????ev.data.fd?=?sockfd; ????????????????//設置用于注測的讀操作事件 ????????????????ev.events?=?EPOLLIN?|?EPOLLET; ????????????????//修改sockfd上要處理的事件為EPOLIN ????????????????epoll_ctl(epfd,?EPOLL_CTL_MOD,?sockfd,?&ev); ????????????} ????????} ????} } |
例子2
| /* *\?服務器端的源代碼 */ #include?<netinet/in.h> #include?<sys/types.h> #include?<sys/socket.h> #include?<fcntl.h> #include?<iostream> #include?<signal.h> #include?<sys/epoll.h> #define?MAXFDS?256 #define?EVENTS?100 #define?PORT?8888 int?epfd; bool?setNonBlock(int?fd) { ????int?flags?=?fcntl(fd,?F_GETFL,?0); ????flags?|=?O_NONBLOCK; ????if(-1?==?fcntl(fd,?F_SETFL,?flags)) ????????return?false; ????return?true; } int?main(int?argc,?char?*argv[],?char?*evp[]) { ????int?fd,?nfds,?confd; ????int?on?=?1; ????char?*buffer[512]; ????struct?sockaddr_in?saddr,?caddr; ????struct?epoll_event?ev,?events[EVENTS]; ????if(-1?==?socket(AF_INET,?SOCKSTREAM),?0) ????{ ????????std::cout?<<?"創建套接字出錯啦"?<<?std::endl; ????????return?-1; ????} ????struct?sigaction?sig; ????sigemptyset(&sig.sa_mask); ????sig_handler?=?SIG_IGN; ????sigaction(SIGPIPE,?&N?>?sig,?NULL); ????epfd?=?epoll_create(MAXFDS); ????setsockopt(fd,?SOL_SOCKET,?SO_REUSEADDR,?&on,?sizeof(on)); ????memset(&saddr,?0,?sizeof(saddr)); ????saddr.sin_family?=?AF_INET; ????saddr.sin_port?=?htons((short)(PORT)); ????saddr.sin_addr.s_addr?=?INADDR_ANY; ????if(-1?==?bind(fd,?(struct?sockaddr?*)&saddr,?sizeof(saddr))) ????{ ????????std::cout?<<?"套接字不能綁定到服務器上"?<<?std::endl; ????????return?-1; ????} ????if(-1?==?listen(fd,?32)) ????{ ????????std::cout?<<?"監聽套接字的時候出錯了"?<<?std::endl; ????????return?-1; ????} ????ev.data.fd?=?fd; ????ev.events?=?EPOLLIN; ????epoll_ctl(epfd,?EPOLL_CTL_ADD,?fd,?&ev); ????while(true) ????{ ????????nfds?=?epoll_wait(epfd,?&events,?MAXFDS,?0); ????????for(int?i?=?0;?i?<?nfds;?++?i) ????????{ ????????????if(fd?==?events[i].data.fd) ????????????{ ????????????????memset(&caddr,?sizeof(caddr)); ????????????????cfd?=?accept(fd,?(struct?sockaddr?*)&caddr,?&sizeof(caddr)); ????????????????if(-1?==?cfd) ????????????????{ ????????????????????std::cout?<<?"服務器接收套接字的時候出問題了"?<<?std::endl; ????????????????????break; ????????????????} ????????????????setNonBlock(cfd); ????????????????ev.data.fd?=?cfd; ????????????????ev.events?=?EPOLLIN; ????????????????epoll_ctl(epfd,?EPOLL_CTL_ADD,?cfd,?&ev); ????????????} ????????????else?if(events[i].data.fd?&?EPOLLIN) ????????????{ ????????????????bzero(&buffer,?sizeof(buffer)); ????????????????std::cout?<<?"服務器端要讀取客戶端發過來的消息"?<<?std::endl; ????????????????ret?=?recv(events[i].data.fd,?buffer,?sizeof(buffer),?0); ????????????????if(ret?<?0) ????????????????{ ????????????????????std::cout?<<?"服務器收到的消息出錯了"?<<?endl; ????????????????????return?-1; ????????????????} ????????????????std::cout?<<?"接收到的消息為:"?<<?(char?*)?buffer?<<?std::endl; ????????????????ev.data.fd?=?events[i].data.fd; ????????????????ev.events?=?EPOLLOUT; ????????????????epoll_ctl(epfd,?EPOLL_CTL_MOD,?events[i].data.fd,?&ev); ????????????} ????????????else?if(events[i].data.fd?&?EPOLLOUT) ????????????{ ????????????????bzero(&buffer,?sizeof(buffer)); ????????????????bcopy("The?Author@:?magicminglee@Hotmail.com",?buffer,?sizeof("The?Author@:?magicminglee@Hotmail.com")); ????????????????ret?=?send(events[i].data.fd,?buffer,?strlen(buffer)); ????????????????if(ret?<?0) ????????????????{ ????????????????????std::cout?<<?"服務器發送消息給客戶端的時候出錯啦"?<<?std::endl; ????????????????????return?-1; ????????????????} ????????????????ev.data.fd?=?events[i].data.fd; ????????????????epoll_ctl(epfd,?EPOLL_CTL_DEL,?ev.data.fd,?&ev); ????????????} ????????} ????} ????if(fd?>?0) ????{ ????????shutdown(fd,?SHUT_RDWR); ????????close(fd); ????} } |
| /* *\?客戶端源代碼 */ #include?<iostream> #include?<netinet/in.h> #include?<sys/types.h> #include?<sys/socket.h> #define?PORT?8888 int?main(int?argc,?char?*argv[],?char?*evp[]) { ????int?fd; ????int?on?=?1; ????char?*buffer[512]; ????struct?sockaddr_in?seraddr; ????memset(&seraddr,?0,?sizeof(seraddr)); ????if((fd?=?socket(AF_INET,?SOCK_STREAM,?0))?<?0) ????{ ????????std::cout?<<?"客戶端創建套接字出錯了"?<<?std::endl; ????????return?-1; ????} ????//如果用于多次測試,那么打開下面debug選項 #ifdef?_Debug_ming ????setsockopt(fd,?SOL_SOCKET,?SO_REUSEADDR,?&on,?sizeof(on)); #endif ????seraddr.sin_port?=?htons((short)(PORT)); ????seraddr.sin_family?=?AF_INET; ????seraddr.sin_addr.s_addr?=?inet_addr("127.0.0.1");//設置自己的ip吧 ????//你也可以采用無阻塞連接,不過需要對連接的錯誤結果進行分析處理 ????if(TEMP_FAILURE_RETRY(connect(fd,?(struct?sockaddr?*)&seraddr,?sizeof(seraddr))?<?0)) ????{ ????????std::cout?<<?"連接錯誤了"?<<?std::endl; ????????return?-1; ????} ????//下面就進行收發信息 ????bcopy("The?Author@:?magicminglee@Hotmail.com"); ????send(fd,?buffer,?strlen(buffer),?0); ????bzero(&buffer,?sizeof(buffer)); ????recv(fd,?buffer,?sizeof(buffer),?0); ????exit(0); } |
例子3
一個使用epoll的服務器
| #include?<iostream> #include?<sys/socket.h> #include?<sys/epoll.h> #include?<netinet/in.h> #include?<arpa/inet.h> #include?<fcntl.h> #include?<unistd.h> #include?<stdio.h> #include?<errno.h> #include?<stdlib.h> #include?<string.h> #include?<pthread.h> #define?MAXLINE?1024 #define?OPEN_MAX?100 #define?LISTENQ?20 #define?SERV_PORT?5555 #define?INFTIM?1000 //線程池任務隊列結構體 struct?task { ????int?fd;????????????//需要讀寫的文件描述符 ????struct?task?*next;?//下一個任務 }; //用于保存向客戶端發送一次消息所需的相關數據 struct?user_data { ????int?fd; ????unsigned?int?n_size; ????char?line[MAXLINE]; }; //線程的任務函數 void?*readtask(void?*args); void?*writetask(void?*args); //聲明epoll_event結構體的變量,ev用于注冊事件,數組用于回傳要處理的事件 struct?epoll_event?ev,?events[20]; int?epfd; pthread_mutex_t?mutex; pthread_cond_t?cond1; struct?task?*readhead?=?NULL,?*readtail?=?NULL,?*writehead?=?NULL; void?setnonblocking(int?sock) { ????int?opts; ????opts?=?fcntl(sock,?F_GETFL); ????if(opts?<?0) ????{ ????????perror("fcntl(sock,GETFL)"); ????????exit(1); ????} ????opts?=?opts?|?O_NONBLOCK; ????if(fcntl(sock,?F_SETFL,?opts)?<?0) ????{ ????????perror("fcntl(sock,SETFL,opts)"); ????????exit(1); ????} } int?main() { ????int?i,?maxi,?listenfd,?connfd,?sockfd,?nfds; ????pthread_t?tid1,?tid2; ????struct?task?*new_task?=?NULL; ????struct?user_data?*rdata?=?NULL; ????socklen_t?clilen; ????pthread_mutex_init(&mutex,?NULL); ????pthread_cond_init(&cond1,?NULL); ????//初始化用于讀線程池的線程,開啟兩個線程來完成任務,兩個線程會互斥地訪問任務鏈表 ????pthread_create(&tid1,?NULL,?readtask,?NULL); ????pthread_create(&tid2,?NULL,?readtask,?NULL); ????//生成用于處理accept的epoll專用的文件描述符 ????epfd?=?epoll_create(256); ????struct?sockaddr_in?clientaddr; ????struct?sockaddr_in?serveraddr; ????listenfd?=?socket(AF_INET,?SOCK_STREAM,?0); ????//把socket設置為非阻塞方式 ????setnonblocking(listenfd); ????//設置與要處理的事件相關的文件描述符 ????ev.data.fd?=?listenfd; ????//設置要處理的事件類型,當描述符可讀時出發,出發方式為ET模式 ????ev.events?=?EPOLLIN?|?EPOLLET; ????//注冊epoll事件 ????epoll_ctl(epfd,?EPOLL_CTL_ADD,?listenfd,?&ev); ????bzero(&serveraddr,?sizeof(serveraddr)); ????serveraddr.sin_family?=?AF_INET; ????const?char?*local_addr?=?"127.0.0.1"; ????inet_aton(local_addr,?&(serveraddr.sin_addr));?//htons(SERV_PORT); ????serveraddr.sin_port?=?htons(SERV_PORT); ????bind(listenfd,?(sockaddr?*)&serveraddr,?sizeof(serveraddr)); ????//開始監聽 ????listen(listenfd,?LISTENQ); ????maxi?=?0; ????for?(?;?;?) ????{ ????????//等待epoll事件的發生 ????????nfds?=?epoll_wait(epfd,?events,?20,?500); ????????//處理所發生的所有事件 ????????for(i?=?0;?i?<?nfds;?++i) ????????{ ????????????if(events[i].data.fd?==?listenfd) ????????????{ ????????????????connfd?=?accept(listenfd,?(sockaddr?*)&clientaddr,?&clilen); ????????????????if(connfd?<?0) ????????????????{ ????????????????????perror("connfd<0"); ????????????????????exit(1); ????????????????} ????????????????setnonblocking(connfd); ????????????????const?char?*str?=?inet_ntoa(clientaddr.sin_addr); ????????????????std::cout?<<?"connec_?from?>>?"?<<?str?<<?std::endl; ????????????????//設置用于讀操作的文件描述符 ????????????????ev.data.fd?=?connfd; ????????????????//設置用于注測的讀操作事件 ????????????????ev.events?=?EPOLLIN?|?EPOLLET; ????????????????//注冊ev ????????????????epoll_ctl(epfd,?EPOLL_CTL_ADD,?connfd,?&ev); ????????????} ????????????else?if(events[i].events?&?EPOLLIN) ????????????{ ????????????????printf("reading!\n"); ????????????????if?(?(sockfd?=?events[i].data.fd)?<?0)?continue; ????????????????new_task?=?new?task(); ????????????????new_task->fd?=?sockfd; ????????????????new_task->next?=?NULL; ????????????????//添加新的讀任務 ????????????????pthread_mutex_lock(&mutex); ????????????????if(readhead?==?NULL) ????????????????{ ????????????????????readhead?=?new_task; ????????????????????readtail?=?new_task; ????????????????} ????????????????else ????????????????{ ????????????????????readtail->next?=?new_task; ????????????????????readtail?=?new_task; ????????????????} ????????????????//喚醒所有等待cond1條件的線程 ????????????????pthread_cond_broadcast(&cond1); ????????????????pthread_mutex_unlock(&mutex); ????????????} ????????????else?if(events[i].events?&?EPOLLOUT) ????????????{ ????????????????rdata?=?(struct?user_data?*)events[i].data.ptr; ????????????????sockfd?=?rdata->fd; ????????????????write(sockfd,?rdata->line,?rdata->n_size); ????????????????delete?rdata; ????????????????//設置用于讀操作的文件描述符 ????????????????ev.data.fd?=?sockfd; ????????????????//設置用于注測的讀操作事件 ????????????????ev.events?=?EPOLLIN?|?EPOLLET; ????????????????//修改sockfd上要處理的事件為EPOLIN ????????????????epoll_ctl(epfd,?EPOLL_CTL_MOD,?sockfd,?&ev); ????????????} ????????} ????} } void?*readtask(void?*args) { ????int?fd?=?-1; ????unsigned?int?n; ????//用于把讀出來的數據傳遞出去 ????struct?user_data?*data?=?NULL; ????while(1) ????{ ????????//互斥訪問任務隊列 ????????pthread_mutex_lock(&mutex); ????????//等待到任務隊列不為空 ????????while(readhead?==?NULL) ????????????pthread_cond_wait(&cond1,?&mutex);?//線程阻塞,釋放互斥鎖,當等待的條件等到滿足時,它會再次獲得互斥鎖 ????????fd?=?readhead->fd; ????????//從任務隊列取出一個讀任務 ????????struct?task?*tmp?=?readhead; ????????readhead?=?readhead->next; ????????delete?tmp; ????????pthread_mutex_unlock(&mutex); ????????data?=?new?user_data(); ????????data->fd?=?fd; ????????if?(?(n?=?read(fd,?data->line,?MAXLINE))?<?0) ????????{ ????????????if?(errno?==?ECONNRESET) ????????????????close(fd); ????????????else ????????????????std::cout?<<?"readline?error"?<<?std::endl; ????????????if(data?!=?NULL)?delete?data; ????????} ????????else?if?(n?==?0) ????????{ ????????????//客戶端關閉了,其對應的連接套接字可能也被標記為EPOLLIN,然后服務器去讀這個套接字 ????????????//結果發現讀出來的內容為0,就知道客戶端關閉了。 ????????????close(fd); ????????????printf("Client?close?connect!\n"); ????????????if(data?!=?NULL)?delete?data; ????????} ????????else ????????{ ????????????std::cout?<<?"read?from?client:?"?<<?data->line?<<?std::endl; ????????????data->n_size?=?n; ????????????//設置需要傳遞出去的數據 ????????????ev.data.ptr?=?data; ????????????//設置用于注測的寫操作事件 ????????????ev.events?=?EPOLLOUT?|?EPOLLET; ????????????//修改sockfd上要處理的事件為EPOLLOUT ????????????epoll_ctl(epfd,?EPOLL_CTL_MOD,?fd,?&ev); ????????} ????} } |
========================================================= 給出一個簡單的客戶端吧,從《Linux編程技術詳解》書中拷貝而來。
| #include?<stdio.h> #include?<sys/types.h> #include?<sys/socket.h> #include?<sys/un.h> #include?<netdb.h> #include?<unistd.h> int?main(int?argc,?char?*argv[]) { ????int?connect_fd; ????int?ret; ????char?snd_buf[1024]; ????int?i; ????int?port; ????int?len; ????static?struct?sockaddr_in?srv_addr; ????if(argc?!=?3) ????{ ????????printf("Usage:?%s?server_ip_address?port\n",?argv[0]); ????????return?1; ????} ????port?=?atoi(argv[2]); ????connect_fd?=?socket(PF_INET,?SOCK_STREAM,?0); ????if(connect_fd?<?0) ????{ ????????perror("cannot?create?communication?socket"); ????????return?1; ????} ????memset(&srv_addr,?0,?sizeof(srv_addr)); ????srv_addr.sin_family?=?AF_INET; ????srv_addr.sin_addr.s_addr?=?inet_addr(argv[1]); ????srv_addr.sin_port?=?htons(port); ????ret?=?connect(connect_fd,?(struct?sockaddr?*)&srv_addr,?sizeof(srv_addr)); ????if(ret?==?-1) ????{ ????????perror("cannot?connect?to?the?server"); ????????close(connect_fd); ????????return?1; ????} ????memset(snd_buf,?0,?1024); ????while(1) ????{ ????????write(STDOUT_FILENO,?"input?message:",?14); ????????bzero(snd_buf,?1024); ????????len?=?read(STDIN_FILENO,?snd_buf,?1024); ????????if(snd_buf[0]?==?'@') ????????????break; ????????if(len?>?0) ????????????write(connect_fd,?snd_buf,?len); ????????len?=?read(connect_fd,?snd_buf,?len); ????????if(len?>?0) ????????????printf("Message?from?server:?%s\n",?snd_buf); ????} ????close(connect_fd); ????return?0; } |
=========================================== ecy@ecy-geek:~/C$ ./epoll_server? connec_ from >> 127.0.0.1 reading! read from client: ni hao ya ya ya ya ya
reading! read from client: hello world
reading! Client close connect!
ecy@ecy-geek:~/C$ pstree | grep epoll |-gnome-terminal-+-bash---epoll_server---2*[{epoll_server}]
ecy@ecy-geek:~/C$ ./p13.5 127.0.0.1 5555 input message:ni hao ya ya ya ya ya Message from server: ni hao ya ya ya ya ya
input message:hello world Message from server: hello world
input message:@
總結
以上是生活随笔為你收集整理的Epoll例子的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 绝地求生 java 雷达透视_绝地求生j
- 下一篇: java飞机大战boss素材_java小