物联网卡linux,Server Develop (六) Linux epoll总结
epoll是Kernel 2.6后新加入的事件機制,在高并發(fā)條件下,遠(yuǎn)優(yōu)于select。epoll最大的好處在于它不會隨著監(jiān)聽fd數(shù)目的增長而降低效率。因為在內(nèi)核中的select實現(xiàn)中,它是采用輪詢來處理的,輪詢的fd數(shù)目越多,自然耗時越多。并且,在linux/posix_types.h頭文件有這樣的聲明:
#define __FD_SETSIZE 1024 //select最多同時監(jiān)聽1024個fd
當(dāng)然,可以通過修改頭文件再重編譯內(nèi)核來擴大這個數(shù)目,但這似乎并不治本。
所以在Nginx中采用了epoll來實現(xiàn)其高并發(fā)特性。
工作方式LT(level triggered):水平觸發(fā),缺省方式,同時支持block和no-block socket,在這種做法中,內(nèi)核告訴我們一個文件描述符是否被就緒了,如果就緒了,你就可以對這個就緒的fd進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的,所以,這種模式編程出錯的可能性較小。傳統(tǒng)的selectpoll都是這種模型的代表。
ET(edge-triggered):邊沿觸發(fā),高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w狀態(tài)時,內(nèi)核通過epoll告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為就緒狀態(tài)了(比如:你在發(fā)送、接受或者接受請求,或者發(fā)送接受的數(shù)據(jù)少于一定量時導(dǎo)致了一個EWOULDBLOCK錯誤)。但是請注意,如果一直不對這個fs做IO操作(從而導(dǎo)致它再次變成未就緒狀態(tài)),內(nèi)核不會發(fā)送更多的通知。
區(qū)別:LT事件不會丟棄,而是只要讀buffer里面有數(shù)據(jù)可以讓用戶讀取,則不斷的通知你。而ET則只在事件發(fā)生之時通知。
主要的數(shù)據(jù)結(jié)構(gòu)
epoll_event的結(jié)構(gòu)如下:
typedef union epoll_data {void *ptr;intfd; __uint32_t u32; __uint64_t u64;} epoll_data_t;//保存觸發(fā)事件的某個文件描述符相關(guān)的數(shù)據(jù)structepoll_event { __uint32_t events;/*epoll event*/epoll_data_t data;/*User data variable*/};
events表示感興趣的事件和被觸發(fā)的事件,可能的取值為:EPOLLIN對應(yīng)的文件描述符可以讀
EPOLLOUT對應(yīng)的文件描述符可以寫
EPOLLPRI對應(yīng)的文件描述符有緊急的數(shù)可讀
EPOLLERR對應(yīng)的文件描述符發(fā)生錯誤
EPOLLHUP對應(yīng)的文件描述符被掛斷
EPOLLET將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的
EPOLLONESHOT只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
操作函數(shù)
epoll的接口非常簡單,用三個相關(guān)函數(shù)來創(chuàng)建epoll句柄、注冊epoll事件以及等待事件的發(fā)生。
創(chuàng)建epoll句柄:
int epoll_create(intsize);//size表示內(nèi)核需要監(jiān)聽的數(shù)目
//return : epoll文件描述符
需要注意的是,當(dāng)創(chuàng)建好epoll句柄后,它就是會占用一個fd值(文件標(biāo)識符),在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡。
epoll事件注冊函數(shù):
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)//epfd是epoll_create()的返回值//op表示動作/*op可被表示為: EPOLL_CTL_ADD:注冊新的fd到epfd中; EPOLL_CTL_MOD:修改已經(jīng)注冊的fd的監(jiān)聽事件; EPOLL_CTL_DEL:從epfd中刪除一個fd;*///fd是需要監(jiān)聽的fd//event是內(nèi)核需要監(jiān)聽的事件
等待事件發(fā)生函數(shù):
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, inttimeout)//epfd是函數(shù)返回值//events是內(nèi)核監(jiān)聽事件的集合//maxevents是epoll_wait可以處理的連接事件的最大限度值//timeout是超時時間
//返回值:請求數(shù)
epoll工作流程
首先,需要調(diào)用epoll_create創(chuàng)建epoll,此后我們就可以進(jìn)行socket/bind/listen,然后調(diào)用epoll_ctl進(jìn)行注冊。接下來,就可以通過一個while(1)循環(huán)調(diào)用epoll_wait來等待事件的發(fā)生,然后循環(huán)查看接收到的事件并進(jìn)行處理。如果事件是sever的socketfd我們就要進(jìn)行accept,并且把接收到client的socketfd加入到要監(jiān)聽的事件中。如果在監(jiān)聽過程中,需要修改操作方式(讀/寫),可以調(diào)用epoll_ctl來重新修改。如果監(jiān)聽到某一個客戶端關(guān)閉,那么我就需要再次調(diào)用epoll_ctl把它從epoll監(jiān)聽事件中刪除。
實例
#include #include#include#include#include#include#include#include#include#include#includevoid setnonblocking(intsockfd){intopts; opts=fcntl(sockfd, F_GETFL);if(opts < 0){ perror("fcntl1 error!"); exit(1); } opts= opts |O_NONBLOCK;if(fcntl(sockfd, F_SETFL, opts) < 0){ perror("fcntl2 error!"); exit(1); }}intmain(){intfd;inton;intrs;intlen;intconn;char buffer[100];intflag1, flag2;structsockaddr_in serv_addr, clt_addr;structtimeval timeout;inti;intnfds;intepfd;intnewfd;structepoll_event ev;struct epoll_event events[20]; fd= socket(AF_INET, SOCK_STREAM, 0); on= 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,&on, sizeof(on)); timeout.tv_sec= 5; timeout.tv_usec= 0; setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)); bzero(&serv_addr, sizeof(structsockaddr_in)); serv_addr.sin_family=AF_INET; serv_addr.sin_port= htons(9090); serv_addr.sin_addr.s_addr=INADDR_ANY; rs= bind(fd, (struct sockaddr*)(&serv_addr), sizeof(structsockaddr));if(rs < 0){ perror(""); close(fd);return -1; } setnonblocking(fd); epfd= epoll_create(100); ev.data.fd=fd; ev.events= EPOLLIN|EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, fd,&ev); rs= listen(fd, 5);if(rs < 0){ perror(""); close(fd);return -1; } len= sizeof(structsockaddr);for(;;){ nfds= epoll_wait(epfd, events, 20, 500);for(i = 0; i < nfds; ++i){if(events[i].data.fd ==fd){ conn= accept(fd, (struct sockaddr*)(&clt_addr), (unsigned int*)(&len)); setnonblocking(conn); ev.data.fd=conn; ev.events= EPOLLIN|EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, conn,&ev); }else if(events[i].events &EPOLLIN){if((newfd = events[i].data.fd) < 0)continue; bzero(buffer,sizeof(buffer)); flag1= recv(newfd, buffer, 100, 0); printf("recv: %s", buffer); printf("recv return: %d", flag1); ev.data.fd=newfd; ev.events= EPOLLOUT|EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, newfd,&ev); }else if(events[i].events &EPOLLOUT){if((newfd = events[i].data.fd) < 0)continue; bzero(buffer,sizeof(buffer)); strcpy(buffer,"ACK"); flag2= send(newfd, buffer, sizeof(buffer), 0); printf("recv return: %d", flag2); ev.data.fd=newfd; ev.events= EPOLLIN|EPOLLET; epoll_ctl(epfd, EPOLL_CTL_MOD, newfd,&ev); } } } close(fd);return 0;}
本文轉(zhuǎn)自cococo點點博客園博客,原文鏈接:http://www.cnblogs.com/coder2012/p/3143953.html,如需轉(zhuǎn)載請自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的物联网卡linux,Server Develop (六) Linux epoll总结的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 都升级!苹果突然发布iOS 16.3.1
- 下一篇: linux搭建springBoot环境,