linux io端口复用,Linux系统IO复用接口(select、poll、epoll)
epoll僅僅是一個異步事件的通知機制,其本身并不作任何的IO讀寫操作,它只負責告訴你是不是可以讀或可以寫了,而具體的讀寫操作,還要應用程序自己來完成。epoll僅提供這種機制是非常好的,它保持了事件通知與IO操作之間彼此的獨立性,使得epoll的使用更加靈活。
接口介紹
int epoll_create(int size)
該函數生成一個epoll專用的文件描述符。它其實是在內核申請一空間,用來存放你想關注的fd上是否發生的事件。size就是你在這個epoll fd上能關注的最大fd數,這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
該函數用于控制某個epoll文件描述符上的事件,可以注冊事件,修改事件,刪除事件。
epfd:由 epoll_create生成的epoll專用的文件描述符;
op:要進行的操作例如注冊事件,可能的取值:
1)EPOLL_CTL_ADD 注冊新的fd到epfd中;
2)EPOLL_CTL_MOD修改已經注冊的fd的監聽事件;
3)EPOLL_CTL_DEL 從epfd中刪除一個fd;
fd:需要監聽的fd
event:指向epoll_event的指針,告訴內核需要監聽的事件,常用的事件類型:
1)EPOLLIN :表示對應的文件描述符可以讀;
2)EPOLLOUT:表示對應的文件描述符可以寫;
3)EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
4)EPOLLERR:表示對應的文件描述符發生錯誤;
5)EPOLLHUP:表示對應的文件描述符被掛斷;
6)EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
7)EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。
如果調用成功返回0,不成功返回-1
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
該函數用于輪詢I/O事件的發生,如果發生則將發生的fd和事件類型放入到events數組中。 并且將注冊在epfd上的fd的事件類型給清空,所以如果下一個循環你還要關注這個fd的話,則需要用epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev)來重新設置fd的事件類型。這時不用EPOLL_CTL_ADD,因為fd并未清空,只是事件類型清空。
epfd:由epoll_create生成的epoll專用的文件描述符;
epoll_event:用于回傳待處理事件的數組;
maxevents:每次能處理的事件數;
timeout:等待I/O事件發生的超時值,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件返回。
epoll程序基本框架:
for( ; ; )
{
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i
{
if(events[i].data.fd==listenfd) //有新的連接
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個連接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監聽隊列中
}
else if( events[i].events&EPOLLIN ) //接收到數據,讀socket
{
n = read(sockfd, line, MAXLINE)) < 0 //讀
ev.data.ptr = md; //md為自定義類型,添加數據
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓
}
else if(events[i].events&EPOLLOUT) //有數據待發送,寫socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取數據
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //發送數據
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標識符,等待下一個循環時接收數據
}
else
{
//其他的處理
}
}
}
接口比較
Linux提供了select、poll、epoll接口來實現IO復用,三者的原型如下所示,本文從參數、實現、性能等方面對三者進行對比。
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
select的第一個參數nfds為fdset集合中最大描述符值加1,fdset是一個位數組,其大小限制為__FD_SETSIZE(1024),select的第二三四個參數表示需要關注讀、寫、錯誤事件的文件描述符位數組,這些參數既是輸入參數也是輸出參數,可能會被內核修改用于標示哪些描述符上發生了關注的事件。所以每次調用select前都需要重新初始化fdset。select對應于內核中的sys_select調用,sys_select首先將第二三四個參數指向的fd_set拷貝到內核,然后對每個被SET的描述符調用進行poll,并記錄在臨時結果中(fdset),如果有事件發生,select會將臨時結果寫到用戶空間并返回;當輪詢一遍后沒有任何事件發生時,如果指定了超時時間,則select會睡眠到超時,睡眠結束后再進行一次輪詢,并將臨時結果寫到用戶空間,然后返回。select返回后,需要逐一檢查關注的描述符是否被SET(事件是否發生)。
poll與select不同,通過一個pollfd數組向內核傳遞需要關注的事件,故沒有描述符個數的限制,pollfd中的events字段和revents分別用于標示關注的事件和發生的事件,故pollfd數組只需要被初始化一次。poll的實現機制與select類似,其對應內核中的sys_poll,只不過poll向內核傳遞pollfd數組,然后對pollfd中的每個描述符進行poll,相比處理fdset來說,poll效率更高。
epoll通過epoll_create創建一個用于epoll輪詢的描述符,通過epoll_ctl添加/修改/刪除事件,通過epoll_wait檢查事件,epoll不是通過輪詢,而是通過在等待的描述符上注冊回調函數,當事件發生時,回調函數負責把發生的事件存儲在就緒事件鏈表中,最后寫到用戶空間。epoll返回后,該參數指向的緩沖區中即為發生的事件,即epoll返回時已經明確的知道哪個fd發生了事件,不用再一個個比對。這樣就提高了效率。同時select的FD_SETSIZE是有限止的,而epoll是沒有限止的只與系統資源有關。epoll不會隨著監聽fd數目的增長而降低效率,因為select采用輪詢方式,輪詢的fd數目越多,自然耗時越多,而epoll是觸發式的,所以效率高。
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的linux io端口复用,Linux系统IO复用接口(select、poll、epoll)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手动添加linux用户,Linux入门教
- 下一篇: linux下搭建ntp服务,Linux