select實現server偽代碼

1 int main() 2 { 3 int lfd = socket(); 4 bind(); 5 listen(); 6 7 // 創建一文件描述符表 8 fd_st reads, temp; 9 // 初始化 10 fd_zero(&reads); 11 // 監聽的lfd加入到讀集合 12 fd_set(lfd, &reads); 13 int maxfd = lfd; 14 15 while(1) 16 { 17 // 委托檢測 18 temp = reads; 19 int ret = select(maxfd+1, &temp, NULL, NULL, NULL); 20 21 // 是不是監聽的 22 if(fd_isset(lfd, &temp)) 23 { 24 // 接受新連接 25 int cfd = accept(); 26 // cfd加入讀集合 27 fd_set(cfd, &reads); 28 // 更新maxfd 29 maxfd=maxfd(2)使用select函的優缺點:
優點:
跨平臺
缺點:
a. 每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大;
b. 同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大;
c. select支持的文件描述符數量太小了,默認是1024。
為什么是1024?
首先,看下內核中對fd_set的定義: typedef struct { unsigned long fds_bits[__FDSET_LONGS]; } __kernel_fd_set; typedef __kernel_fd_set fd_set; 其中有關的常量定義為: #undef __NFDBITS #define __NFDBITS (8 * sizeof(unsigned long)) #undef __FD_SETSIZE #define __FD_SETSIZE 1024 #undef __FDSET_LONGS #define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) 即__NFDBITS為8*4=32,__FD_SETSIZE為1024,那么,__FDSET_LONGS為1024/32=32,因此,fd_set實際上是32個無符號長整形,也就是1024位
(2)select函數及示例
#include /* According to earlier standards */ #include #include #include int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds: 監控的文件描述符集里最大文件描述符加1,因為此參數會告訴內核檢測前多少個文件描述符的狀態 readfds: 監控有讀數據到達文件描述符集合,傳入傳出參數 writefds: 監控寫數據到達文件描述符集合,傳入傳出參數 exceptfds: 監控異常發生達文件描述符集合,如帶外數據到達異常,傳入傳出參數 timeout: 定時阻塞監控時間,3種情況 1.NULL,永遠等下去 2.設置timeval,等待固定時間 3.設置timeval里時間均為0,檢查描述字后立即返回,輪詢 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0 int FD_ISSET(int fd, fd_set *set); //測試文件描述符集合里fd是否置1 void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1 void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
select示例:
select.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\n"); 16 exit(1); 17 } 18 struct sockaddr_in serv_addr; 19 socklen_t serv_len = sizeof(serv_addr); 20 int port = atoi(argv[1]); 21 22 // 創建套接字 23 int lfd = socket(AF_INET, SOCK_STREAM, 0); 24 // 初始化服務器 sockaddr_in 25 memset(&serv_addr, 0, serv_len); 26 serv_addr.sin_family = AF_INET; // 地址族 27 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 28 serv_addr.sin_port = htons(port); // 設置端口 29 // 綁定IP和端口 30 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 31 32 // 設置同時監聽的最大個數 33 listen(lfd, 36); 34 printf("Start accept ......\n"); 35 36 struct sockaddr_in client_addr; 37 socklen_t cli_len = sizeof(client_addr); 38 39 // 最大的文件描述符 40 int maxfd = lfd; 41 // 文件描述符讀集合 42 fd_set reads, temp; 43 // init 44 FD_ZERO(&reads); 45 FD_SET(lfd, &reads); 46 47 while(1) 48 { 49 // 委托內核做IO檢測 50 temp = reads; 51 int ret = select(maxfd+1, &temp, NULL, NULL, NULL); 52 if(ret == -1) 53 { 54 perror("select error"); 55 exit(1); 56 } 57 // 客戶端發起了新的連接 58 if(FD_ISSET(lfd, &temp)) 59 { 60 // 接受連接請求 - accept不阻塞 61 int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len); 62 if(cfd == -1) 63 { 64 perror("accept error"); 65 exit(1); 66 } 67 char ip[64]; 68 printf("new client IP: %s, Port: %d\n", 69 inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)), 70 ntohs(client_addr.sin_port)); 71 // 將cfd加入到待檢測的讀集合中 - 下一次就可以檢測到了 72 FD_SET(cfd, &reads); 73 // 更新最大的文件描述符 74 maxfd = maxfd < cfd ? cfd : maxfd; 75 } 76 // 已經連接的客戶端有數據到達 77 for(int i=lfd+1; i<=maxfd; ++i) 78 { 79 if(FD_ISSET(i, &temp)) 80 { 81 char buf[1024] = {0}; 82 int len = recv(i, buf, sizeof(buf), 0); 83 if(len == -1) 84 { 85 perror("recv error"); 86 exit(1); 87 } 88 else if(len == 0) 89 { 90 printf("客戶端已經斷開了連接\n"); 91 close(i); 92 // 從讀集合中刪除 93 FD_CLR(i, &reads); 94 } 95 else 96 { 97 printf("recv buf: %s\n", buf); 98 send(i, buf, strlen(buf)+1, 0); 99 } 100 } 101 } 102 } 103 104 close(lfd); 105 return 0; 106 }
select示例2:
select.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 創建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服務器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 設置端口 26 serv_len = sizeof(serv_addr); 27 // 綁定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 設置同時監聽的最大個數 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 int ret; 35 int maxfd = lfd; 36 // reads 實時更新,temps 內核檢測 37 fd_set reads, temps; 38 39 FD_ZERO(&reads); 40 FD_SET(lfd, &reads); 41 42 while(1) 43 { 44 temps = reads; 45 ret = select(maxfd+1, &temps, NULL, NULL, NULL); 46 if(ret == -1) 47 { 48 perror("select error"); 49 exit(1); 50 } 51 52 53 // 判斷是否有新連接 54 if(FD_ISSET(lfd, &temps)) 55 { 56 // 接受連接請求 57 clien_len = sizeof(clien_len); 58 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 59 60 // 文件描述符放入檢測集合 61 FD_SET(cfd, &reads); 62 // 更新最大文件描述符 63 maxfd = maxfd < cfd ? cfd : maxfd; 64 } 65 66 // 遍歷檢測的文件描述符是否有讀操作 67 for(int i=lfd+1; i<=maxfd; ++i) 68 { 69 if(FD_ISSET(i, &temps)) 70 { 71 // 讀數據 72 char buf[1024] = {0}; 73 int len = read(i, buf, sizeof(buf)); 74 if(len == -1) 75 { 76 perror("read error"); 77 exit(1); 78 } 79 else if(len == 0) 80 { 81 // 對方關閉了連接 82 FD_CLR(i, &reads); 83 close(i); 84 if(maxfd == i) 85 { 86 maxfd--; 87 } 88 } 89 else 90 { 91 printf("read buf = %s\n", buf); 92 for(int j=0; jselect_plus.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 創建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服務器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 設置端口 26 serv_len = sizeof(serv_addr); 27 // 綁定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 設置同時監聽的最大個數 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 int ret; 35 int maxfd = lfd; 36 // reads 實時更新,temps 內核檢測 37 fd_set reads, temps; 38 39 /*===============================================================*/ 40 // 記錄要檢測的文件描述符的數組 41 int allfd[FD_SETSIZE]; // 1024 42 // 記錄數組中最后一個元素的下標 43 int last_index = 0; 44 // 初始化數組 45 for(int i=0; i補充 pselect:
pselect原型如下。此模型應用較少,可參考select模型自行編寫C/S:
#include int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); struct timespec { long tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; 用sigmask替代當前進程的阻塞信號集,調用返回后還原原有阻塞信號集

3. poll
#include int poll(struct pollfd *fds, nfds_t nfds, int timeout); struct pollfd { int fd; /* 文件描述符 */ short events; /* 監控的事件 */ short revents; /* 監控事件中滿足條件返回的事件 */ }; POLLIN 普通或帶外優先數據可讀,即POLLRDNORM | POLLRDBAND POLLRDNORM 數據可讀 POLLRDBAND 優先級帶數據可讀 POLLPRI 高優先級可讀數據 POLLOUT 普通或帶外數據可寫 POLLWRNORM 數據可寫 POLLWRBAND 優先級帶數據可寫 POLLERR 發生錯誤 POLLHUP 發生掛起 POLLNVAL 描述字不是一個打開的文件 fds 數組地址 nfds 監控數組中有多少文件描述符需要被監控,數組的最大長度, 數組中最后一個使用的元素下標+1,內核會輪詢檢測fd數組的每個文件描述符 timeout 毫秒級等待 -1:阻塞等,#define INFTIM -1 Linux中沒有定義此宏 0:立即返回,不阻塞進程 >0:等待指定毫秒數,如當前系統時間精度不夠毫秒,向上取值 返回值: IO發送變化的文件描述符的個數
如果不再監控某個文件描述符時,可以把pollfd中,fd設置為-1,poll不再監控此pollfd,下次返回時,把revents設置為0。
示例(使用poll實現的server):
poll.c
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 #define SERV_PORT 8989 12 13 int main(int argc, const char* argv[]) 14 { 15 int lfd, cfd; 16 struct sockaddr_in serv_addr, clien_addr; 17 int serv_len, clien_len; 18 19 // 創建套接字 20 lfd = socket(AF_INET, SOCK_STREAM, 0); 21 // 初始化服務器 sockaddr_in 22 memset(&serv_addr, 0, sizeof(serv_addr)); 23 serv_addr.sin_family = AF_INET; // 地址族 24 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 25 serv_addr.sin_port = htons(SERV_PORT); // 設置端口 26 serv_len = sizeof(serv_addr); 27 // 綁定IP和端口 28 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 29 30 // 設置同時監聽的最大個數 31 listen(lfd, 36); 32 printf("Start accept ......\n"); 33 34 // poll結構體 35 struct pollfd allfd[1024]; 36 int max_index = 0; 37 // init 38 for(int i=0; i<1024; ++i) 39 { 40 allfd[i].fd = -1; 41 } 42 allfd[0].fd = lfd; 43 allfd[0].events = POLLIN; 44 45 while(1) 46 { 47 int i = 0; 48 int ret = poll(allfd, max_index+1, -1); 49 if(ret == -1) 50 { 51 perror("poll error"); 52 exit(1); 53 } 54 55 // 判斷是否有連接請求 56 if(allfd[0].revents & POLLIN) 57 { 58 clien_len = sizeof(clien_addr); 59 // 接受連接請求 60 int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len); 61 printf("============\n"); 62 63 // cfd添加到poll數組 64 for(i=0; i<1024; ++i) 65 { 66 if(allfd[i].fd == -1) 67 { 68 allfd[i].fd = cfd; 69 break; 70 } 71 } 72 // 更新最后一個元素的下標 73 max_index = max_index < i ? i : max_index; 74 } 75 76 // 遍歷數組 77 for(i=1; i<=max_index; ++i) 78 { 79 int fd = allfd[i].fd; 80 if(fd == -1) 81 { 82 continue; 83 } 84 if(allfd[i].revents & POLLIN) 85 { 86 // 接受數據 87 char buf[1024] = {0}; 88 int len = recv(fd, buf, sizeof(buf), 0); 89 if(len == -1) 90 { 91 perror("recv error"); 92 exit(1); 93 } 94 else if(len == 0) 95 { 96 allfd[i].fd = -1; 97 close(fd); 98 printf("客戶端已經主動斷開連接。。。\n"); 99 } 100 else 101 { 102 printf("recv buf = %s\n", buf); 103 for(int k=0; kpoll與select的比較:
兩者其實沒有大的變化,主要是poll沒有select對于1024的限制,由于內部實現是通過鏈表來實現的,因此理論上沒有限制。但是兩者最大的缺點還是內核會輪詢檢測fd數組的每個文件描述符。
補充 ppoll:
GNU定義了ppoll(非POSIX標準),可以支持設置信號屏蔽字,可參考poll模型自行實現C/S。
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *timeout_ts, const sigset_t *sigmask);
4.?epoll
epoll是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率,因為它會復用文件描述符集合來傳遞結果而不用迫使開發者
每次等待事件之前都必須重新準備要被偵聽的文件描述符集合(用戶態和內核態共享同一片文件描述符表內存),另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那
些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。
目前epell是linux大規模并發網絡程序中的熱門首選模型。
epoll除了提供select/poll那種IO事件的電平觸發(Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提
高應用程序效率。
可以使用cat命令查看一個進程可以打開的socket描述符上限。
cat /proc/sys/fs/file-max
如有需要,可以通過修改配置文件的方式修改該上限值。
sudo vi /etc/security/limits.conf 在文件尾部寫入以下配置,soft軟限制,hard硬限制。 * soft nofile 65536 * hard nofile 100000
1)創建一個epoll句柄,參數size用來告訴內核監聽的文件描述符的個數,跟內存大小有關。
#include int epoll_create(int size) size:監聽數目, epoll上能關注的最大描述符數
2)控制某個epoll監控的文件描述符上的事件:注冊、修改、刪除。
#include int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) epfd: 為epoll_creat的句柄 op: 表示動作,用3個宏來表示: EPOLL_CTL_ADD (注冊新的fd到epfd), EPOLL_CTL_MOD (修改已經注冊的fd的監聽事件), EPOLL_CTL_DEL (從epfd刪除一個fd); event: 告訴內核需要監聽的事件 struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t; EPOLLIN : 表示對應的文件描述符可以讀(包括對端SOCKET正常關閉) EPOLLOUT: 表示對應的文件描述符可以寫 EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來) EPOLLERR: 表示對應的文件描述符發生錯誤 EPOLLHUP: 表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)而言的 EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
3)等待所監控文件描述符上有事件的產生,類似于select()調用。
#include int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) events: 用來存內核得到事件的集合,用于回傳待處理事件的數組 maxevents: 告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size timeout: 是超時時間 -1: 阻塞 0: 立即返回,非阻塞 >0: 指定毫秒 返回值: 成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1
【Linux C編程】第十八章 高并發服務器(三)
Linux 數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。