select的效率一定比epoll低嗎?

一般以爲epoll的效率比select高,當然有人知道這個前提是網絡環境較差且存在大連接(超過10k)的情況下。

網絡上<<epoll內核實現>>一文中列出了下列因素:
     select/poll的缺點在於:
     1.每次調用時要重複地從用戶態讀入參數。

2.每次調用時要重複地掃描文件描述符。
     3.每次在調用開始時,要把當前進程放入各個文件描述符的等待隊列。在調用結束後,又把進程從各個等待隊列中刪除。
     在實際應用中,select/poll監視的文件描述符可能會非常多,如果每次只是返回一小部分,那麼,這種情況下select/poll
顯得不夠高效。



上面給出的原因是文件描述符非常多時,每次都要對文件描述符進行一些操作並對參數讀入時內核和用戶態的切換操作導致select效率比較低。但是換做另外一種場景,如果在網絡最好的韓國的WAN或者天朝的某個公司內部的局域網LAN中(互聯網網速咱就不跟人家棒子比了),網絡環境非常好,即干擾很少,某個服務器只接受固定的計算機發來的數據(例如10臺),這種情況下連接非常少, 使得epoll高效的上述因素顯然不能成立了,此時可以認爲epoll和select效率近似。
在上面的場景中加一個因素,10臺計算機每次對服務器發送的數據都非常多(超過1M),可以參考下面的代碼,則select檢查到連接數目後,for循環檢查確認10臺機器中那幾臺機器來了數據,應該非常快,當收數據時,用recv或者recvfrom進行數據接收,不用在內核態和用戶態各自的buf之間頻繁拷貝,這種情況下效率應該能夠令人滿意。但是epoll呢?

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>


int main(int argc,  char** argv) {
int local_sock_fd = socket(AF_INET,SOCK_DGRAM,0);
if (local_sock_fd < 0) {
exit(0);
}


struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(6000);
int ret = bind(local_sock_fd, (struct sockaddr*)(&local_addr), sizeof(local_addr));
if (ret == -1) {
exit(1);
}
fd_set local_fd_set;
fd_set local_read_fd_set;
FD_ZERO(&local_fd_set);
FD_ZERO(&local_read_fd_set);
FD_SET(local_sock_fd, &local_fd_set);


struct timeval time_out;
time_out.tv_sec = 1;
time_out.tv_usec = 0;
char data_buf[65536] = {0};
int max_fd = local_sock_fd + 1;
while(1) {
local_read_fd_set = local_fd_set;
int fd_num = select(max_fd, &local_read_fd_set, NULL, NULL, &time_out);
if (fd_num < 0) {
perror("select");
exit(2);
}


int idx = 0;
for( idx = 0; idx < fd_num; idx++) {
struct sockaddr_in peer_addr;
int peer_addr_len = sizeof(peer_addr);
if (FD_ISSET(local_sock_fd, &local_read_fd_set)) {
int data_buf_len = recvfrom(local_sock_fd, data_buf, sizeof(data_buf) - 1, 0, (struct sockaddr*)(&peer_addr), &peer_addr_len);
if (data_buf_len < 0) {
break;
} else if (data_buf_len == 0) {
continue;
}
data_buf[data_buf_len] = '\0';
printf("recv data: %s\n", data_buf);
}
}
}//while


return 0;
}

epoll部分的代碼可以參考下面這部分,epoll必須先獲取一個新的連接socket描述符,然後把它加入event pool file system,併爲之分配相應的inode,這期間的epoll_ctl操作很費時的,因爲其本質是阻塞的,等到內核數據接收完一部分數據後(一般一次2k),它再通知相應的socket,用戶態再去拷貝數據,整個過程應爲有數次內核態與用戶態的切換,其效率相對上面的select就有點低下了。


#include <sys/types.h>
#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 <stdlib.h>
#include <errno.h>
#include <string.h>


void set_sock_nonblock(int sock) {
int flag = fcntl(sock, F_GETFL);
flag |= O_NONBLOCK;
flag |= O_NDELAY;
fcntl(sock, F_SETFL, flag);
}


int main(int argc, char* argv[]) {
int local_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
set_sock_nonblock(local_sock_fd);
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_addr.s_addr = INADDR_ANY;
local_addr.sin_port = htons(6000);
int ret = bind(local_sock_fd, (struct sockaddr*)(&local_addr), sizeof(local_addr));
if (ret == -1) {
exit(1);
}
listen(local_sock_fd, 5);


int ep_fd = epoll_create(10);
struct epoll_event ep_event;
ep_event.events = EPOLLIN;
ep_event.data.fd = local_sock_fd;
epoll_ctl(ep_fd, EPOLL_CTL_ADD, local_sock_fd, &ep_event);
struct epoll_event events[10];
int time_out = 1;
int peer_sock_fd = 0;
char data_buf[1024] = {0};
int data_buf_len = 0;
while (1) {
int fd_num = epoll_wait(ep_fd, events, sizeof(events) / sizeof(struct epoll_event), time_out);
int idx = 0;
for (idx = 0; idx < fd_num; ++idx) {
if (local_sock_fd == events[idx].data.fd) {
struct sockaddr_in peer_addr;
int peer_addr_len = sizeof(struct sockaddr_in);
peer_sock_fd = accept(local_sock_fd, (struct sockaddr *)(&peer_addr), &peer_addr_len);
if (peer_sock_fd < 0){
perror("accept");
exit(1);
}
ep_event.data.fd = peer_sock_fd;
ep_event.events = EPOLLIN|EPOLLET;
epoll_ctl(ep_fd, EPOLL_CTL_ADD, peer_sock_fd, &ep_event);

if(events[idx].events & EPOLLIN) {
peer_sock_fd = events[idx].data.fd;
if (peer_sock_fd < 0) {
continue;
}
int ret = read(peer_sock_fd, data_buf, sizeof(data_buf) - 1);
if (ret < 0) {
if (errno == ECONNRESET) {
close(peer_sock_fd);
events[idx].data.fd = -1;
}
} else if (ret == 0) {
close(peer_sock_fd);
events[idx].data.fd = -1;
}
data_buf[ret] = '\0';
printf("read data: %s\n", data_buf);
}
}
}


close(ep_fd);

close(local_sock_fd);

return 0;
}
所以對二者效率的區分,也要區分使用場景。如果要搭建網絡服務框架,建議使用epoll,畢竟網絡情況很複雜,瓶頸不僅僅是網絡數據的接收和發送,數據的處理也是很複雜的。但是在一些簡單的場景下,特別是內網LAN通信,select就可以滿足要求,效率不會比epoll低,而且編程簡單。如果不想使用龐大的網絡框架式,它顯然是很好的選擇。
以上是個人的一些理解,歡迎大家指正。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章