linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例
除了自己實現之外,還有個c語言寫的基于事件的開源網絡庫:libevent
http://www.cnblogs.com/Anker/p/3265058.html
?
最簡單的select示例:
#include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h>#define STDIN 0 // file descriptor for standard inputint main(void) {struct timeval tv;fd_set readfds;tv.tv_sec = 2;tv.tv_usec = 500000;FD_ZERO(&readfds);FD_SET(STDIN, &readfds);// don't care about writefds and exceptfds:select(STDIN+1, &readfds, NULL, NULL, &tv);if (FD_ISSET(STDIN, &readfds))printf("A key was pressed!\n");elseprintf("Timed out.\n");return 0; }?
?
select、poll、epoll之間的區別總結[整理]
select,poll,epoll都是IO多路復用的機制。I/O多路復用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。關于這三種IO多路復用的用法,前面三篇總結寫的很清楚,并用服務器回射echo程序進行了測試。連接如下所示:
select:http://www.cnblogs.com/Anker/archive/2013/08/14/3258674.html
poll:http://www.cnblogs.com/Anker/archive/2013/08/15/3261006.html
epoll:http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
今天對這三種IO多路復用進行對比,參考網上和書上面的資料,整理如下:
1、select實現
select的調用過程如下所示:
(1)使用copy_from_user從用戶空間拷貝fd_set到內核空間
(2)注冊回調函數__pollwait
(3)遍歷所有fd,調用其對應的poll方法(對于socket,這個poll方法是sock_poll,sock_poll根據情況會調用到tcp_poll,udp_poll或者datagram_poll)
(4)以tcp_poll為例,其核心實現就是__pollwait,也就是上面注冊的回調函數。
(5)__pollwait的主要工作就是把current(當前進程)掛到設備的等待隊列中,不同的設備有不同的等待隊列,對于tcp_poll 來說,其等待隊列是sk->sk_sleep(注意把進程掛到等待隊列中并不代表進程已經睡眠了)。在設備收到一條消息(網絡設備)或填寫完文件數 據(磁盤設備)后,會喚醒設備等待隊列上睡眠的進程,這時current便被喚醒了。
(6)poll方法返回時會返回一個描述讀寫操作是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。
(7)如果遍歷完所有的fd,還沒有返回一個可讀寫的mask掩碼,則會調用schedule_timeout是調用select的進程(也就是 current)進入睡眠。當設備驅動發生自身資源可讀寫后,會喚醒其等待隊列上睡眠的進程。如果超過一定的超時時間(schedule_timeout 指定),還是沒人喚醒,則調用select的進程會重新被喚醒獲得CPU,進而重新遍歷fd,判斷有沒有就緒的fd。
(8)把fd_set從內核空間拷貝到用戶空間。
總結:
select的幾大缺點:
(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
(3)select支持的文件描述符數量太小了,默認是1024
2 poll實現
poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,其他的都差不多。
關于select和poll的實現分析,可以參考下面幾篇博文:
http://blog.csdn.net/lizhiguo0532/article/details/6568964#comments
http://blog.csdn.net/lizhiguo0532/article/details/6568968
http://blog.csdn.net/lizhiguo0532/article/details/6568969
http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-
http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml
3、epoll
epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎么解決的呢?在此之前,我們先看一下 epoll和select和poll的調用接口上的不同,select和poll都只提供了一個函數——select或者poll函數。而epoll提供 了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是創建一個epoll句 柄;epoll_ctl是注冊要監聽的事件類型;epoll_wait則是等待事件的產生。
對于第一個缺點,epoll的解決方案在epoll_ctl函數中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定 EPOLL_CTL_ADD),會把所有的fd拷貝進內核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝 一次。
對于第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在 epoll_ctl時把current掛一遍(這一遍必不可少)并為每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調 函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用 schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。
對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大于2048,舉個例子, 在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關系很大。
總結:
(1)select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用 epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,并喚醒在 epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的 時候只要判斷一下就緒鏈表是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。
(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,并且要把current往設備等待隊列中掛一次,而epoll只要 一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊列并不是設備等待隊列,只是一個epoll內 部定義的等待隊列)。這也能節省不少的開銷。
參考資料:
http://www.cnblogs.com/apprentice89/archive/2013/05/09/3070051.html
http://www.linuxidc.com/Linux/2012-05/59873p3.htm
http://xingyunbaijunwei.blog.163.com/blog/static/76538067201241685556302/
http://blog.csdn.net/kkxgx/article/details/7717125
https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/epoll-example.c
?
IO多路復用之select總結
1、基本概念
IO多路復用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程。IO多路復用適用如下場合:
(1)當客戶處理多個描述字時(一般是交互式輸入和網絡套接口),必須使用I/O復用。
(2)當一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現。
(3)如果一個TCP服務器既要處理監聽套接口,又要處理已連接套接口,一般也要用到I/O復用。
(4)如果一個服務器即要處理TCP,又要處理UDP,一般要使用I/O復用。
(5)如果一個服務器要處理多個服務或多個協議,一般要使用I/O復用。
與多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。
2、select函數
該函數準許進程指示內核等待多個事件中的任何一個發送,并只在有一個或多個事件發生或經歷一段指定的時間后才喚醒。函數原型如下:
#include <sys/select.h> #include <sys/time.h>int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)返回值:就緒描述符的數目,超時返回0,出錯返回-1
函數參數介紹如下:
(1)第一個參數maxfdp1指定待測試的描述字個數,它的值是待測試的最大描述字加1(因此把該參數命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。
因為文件描述符是從0開始的。
(2)中間的三個參數readset、writeset和exceptset指定我們要讓內核測試讀、寫和異常條件的描述字。如果對某一個的條件不感興趣,就可以把它設為空指針。struct fd_set可以理解為一個集合,這個集合中存放的是文件描述符,可通過以下四個宏進行設置:
????????? void FD_ZERO(fd_set *fdset);?????????? //清空集合
????????? void FD_SET(int fd, fd_set *fdset);?? //將一個給定的文件描述符加入集合之中
????????? void FD_CLR(int fd, fd_set *fdset);?? //將一個給定的文件描述符從集合中刪除
????????? int FD_ISSET(int fd, fd_set *fdset);?? // 檢查集合中指定的文件描述符是否可以讀寫?
(3)timeout告知內核等待所指定描述字中的任何一個就緒可花多少時間。其timeval結構用于指定這段時間的秒數和微秒數。
???????? struct timeval{
?????????????????? long tv_sec;?? //seconds
?????????????????? long tv_usec;? //microseconds
?????? };
這個參數有三種可能:
(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。為此,把該參數設置為空指針NULL。
(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該參數所指向的timeval結構中指定的秒數和微秒數。
(3)根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數必須指向一個timeval結構,而且其中的定時器值必須為0。
?原理圖:
3、測試程序
寫一個TCP回射程序,程序的功能是:客戶端向服務器發送信息,服務器接收并原樣發送給客戶端,客戶端顯示出接收到的信息。
服務端程序如下所示:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/select.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <assert.h>#define IPADDR "127.0.0.1" #define PORT 8787 #define MAXLINE 1024 #define LISTENQ 5 #define SIZE 10typedef struct server_context_st {int cli_cnt; /*客戶端個數*/int clifds[SIZE]; /*客戶端的個數*/fd_set allfds; /*句柄集合*/int maxfd; /*句柄最大值*/ } server_context_st;static server_context_st *s_srv_ctx = NULL;/*===========================================================================* ==========================================================================*/ static int create_server_proc(const char* ip,int port) {int fd;struct sockaddr_in servaddr;fd = socket(AF_INET, SOCK_STREAM,0);if (fd == -1) {fprintf(stderr, "create socket fail,erron:%d,reason:%s\n",errno, strerror(errno));return -1;}int yes = 1;if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { return -1;}int reuse = 1;if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {return -1;}bzero(&servaddr,sizeof(servaddr));servaddr.sin_family = AF_INET;inet_pton(AF_INET,ip,&servaddr.sin_addr);servaddr.sin_port = htons(port);if (bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) {perror("bind error: ");return -1;}listen(fd,LISTENQ);return fd; }static int accept_client_proc(int srvfd) {struct sockaddr_in cliaddr;socklen_t cliaddrlen;cliaddrlen = sizeof(cliaddr);int clifd = -1;printf("accpet clint proc is called.\n");ACCEPT:clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen);if (clifd == -1) {if (errno == EINTR) {goto ACCEPT;} else {fprintf(stderr, "accept fail,error:%s\n", strerror(errno));return -1;}}fprintf(stdout, "accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);//將新的連接描述符添加到數組中int i = 0;for (i = 0; i < SIZE; i++) {if (s_srv_ctx->clifds[i] < 0) {s_srv_ctx->clifds[i] = clifd;s_srv_ctx->cli_cnt++;break;}}if (i == SIZE) {fprintf(stderr,"too many clients.\n");return -1;}}static int handle_client_msg(int fd, char *buf) {assert(buf);printf("recv buf is :%s\n", buf);write(fd, buf, strlen(buf) +1);return 0; }static void recv_client_msg(fd_set *readfds) {int i = 0, n = 0;int clifd;char buf[MAXLINE] = {0};for (i = 0;i <= s_srv_ctx->cli_cnt;i++) {clifd = s_srv_ctx->clifds[i];if (clifd < 0) {continue;}if (FD_ISSET(clifd, readfds)) {//接收客戶端發送的信息n = read(clifd, buf, MAXLINE);if (n <= 0) {FD_CLR(clifd, &s_srv_ctx->allfds);close(clifd);s_srv_ctx->clifds[i] = -1;continue;}handle_client_msg(clifd, buf);}} } static void handle_client_proc(int srvfd) {int clifd = -1;int retval = 0;fd_set *readfds = &s_srv_ctx->allfds;struct timeval tv;int i = 0;while (1) {/*每次調用select前都要重新設置文件描述符和時間,因為事件發生后,文件描述符和時間都被內核修改啦*//*添加監聽套接字*/FD_ZERO(readfds);FD_SET(srvfd, readfds);s_srv_ctx->maxfd = srvfd;tv.tv_sec = 30;tv.tv_usec = 0;/*添加客戶端套接字*/for (i = 0; i < s_srv_ctx->cli_cnt; i++) {clifd = s_srv_ctx->clifds[i];FD_SET(clifd, readfds);s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);}retval = select(s_srv_ctx->maxfd + 1, readfds, NULL, NULL, &tv);if (retval == -1) {fprintf(stderr, "select error:%s.\n", strerror(errno));return;}if (retval == 0) {fprintf(stdout, "select is timeout.\n");continue;}if (FD_ISSET(srvfd, readfds)) {/*監聽客戶端請求*/accept_client_proc(srvfd);} else {/*接受處理客戶端消息*/recv_client_msg(readfds);}} }static void server_uninit() {if (s_srv_ctx) {free(s_srv_ctx);s_srv_ctx = NULL;} }static int server_init() {s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));if (s_srv_ctx == NULL) {return -1;}memset(s_srv_ctx, 0, sizeof(server_context_st));int i = 0;for (;i < SIZE; i++) {s_srv_ctx->clifds[i] = -1;}return 0; }int main(int argc,char *argv[]) {int srvfd;if (server_init() < 0) {return -1;}srvfd = create_server_proc(IPADDR, PORT);if (srvfd < 0) {fprintf(stderr, "socket create or bind fail.\n");goto err;}handle_client_proc(srvfd);return 0;err:server_uninit();return -1; }客戶端程序如下:
#include <netinet/in.h> #include <sys/socket.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/select.h> #include <time.h> #include <unistd.h> #include <sys/types.h> #include <errno.h>#define MAXLINE 1024 #define IPADDRESS "127.0.0.1" #define SERV_PORT 8787#define max(a,b) (a > b) ? a : bstatic void handle_recv_msg(int sockfd, char *buf) {printf("client recv msg is:%s\n", buf);sleep(5);write(sockfd, buf, strlen(buf) +1); }static void handle_connection(int sockfd) {char sendline[MAXLINE],recvline[MAXLINE];int maxfdp,stdineof;fd_set readfds;int n;struct timeval tv;int retval = 0;while (1) {FD_ZERO(&readfds);FD_SET(sockfd,&readfds);maxfdp = sockfd;tv.tv_sec = 5;tv.tv_usec = 0;retval = select(maxfdp+1,&readfds,NULL,NULL,&tv);if (retval == -1) {return ;}if (retval == 0) {printf("client timeout.\n");continue;}if (FD_ISSET(sockfd, &readfds)) {n = read(sockfd,recvline,MAXLINE);if (n <= 0) {fprintf(stderr,"client: server is closed.\n");close(sockfd);FD_CLR(sockfd,&readfds);return;}handle_recv_msg(sockfd, recvline);}} }int main(int argc,char *argv[]) {int sockfd;struct sockaddr_in servaddr;sockfd = socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERV_PORT);inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);int retval = 0;retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));if (retval < 0) {fprintf(stderr, "connect fail,error:%s\n", strerror(errno));return -1;}printf("client send to server .\n");write(sockfd, "hello server", 32);handle_connection(sockfd);return 0; }4、程序結果
啟動服務程序,執行三個個客戶程序進行測試,結果如下圖所示:
參考:
http://konglingchun.is-programmer.com/posts/12146.html
http://blog.163.com/smileface100@126/blog/static/27720874200951024532966/
?
linux select 多路復用機制
下面給一個偽碼說明基本select模型的服務器模型:
array[slect_len]; nSock=0; array[nSock++]=listen_fd;(之前listen port已綁定并listen) maxfd=listen_fd; while(1){ FD_ZERO(&set); foreach (fd in array) { fd大于maxfd,則maxfd=fd FD_SET(fd,&set) } res=select(maxfd+1,&set,0,0,0); if(FD_ISSET(listen_fd,&set)) { newfd=accept(listen_fd); array[nsock++]=newfd; if(--res<=0) continue; } foreach 下標1開始 (fd in array) { if(FD_ISSET(fd,&tyle="COLOR: #ff0000">set)) 執行讀等相關操作 如果錯誤或者關閉,則要刪除該fd,將array中相應位置和最后一個元素互換就好,nsock減一 if(--res<=0) continue; } }檢測鍵盤有無輸入,完整的程序如下:
?
#include<sys/time.h> ?#include<sys/types.h> ?
#include<unistd.h> ?
#include<string.h> ?
#include<stdlib.h> ?
#include<stdio.h> ?
#define LEN 10
int main() ?
{ ?
??? char buf[LEN]=""; ?
??? fd_set rdfds; ?
??? struct timeval tv; ?
??? int ret; ?
??? FD_ZERO(&rdfds); ?
??? FD_SET(0,&rdfds);?? //文件描述符0表示stdin鍵盤輸入 ?
??? tv.tv_sec = 3; ?
??? tv.tv_usec = 500; ?
??? ret = select(1,&rdfds,NULL,NULL,&tv); ?
??? if(ret<0) ?
??????? printf("\n selcet"); ?
??? else if(ret == 0) ?
??????? printf("\n timeout"); ?
??? else ?
??????? printf("\n ret = %d",ret); ?
??? if(FD_ISSET(1,&rdfds))? //如果有輸入,從stdin中獲取輸入字符 ?
??? { ?
??????? printf("\n reading"); ?
??????? fread(buf, LEN-1, 1, stdin); ?
??? } ?
??? write(1,buf,strlen(buf)); ?
??? printf("\n %d \n",strlen(buf)); ?
??? return 0; ?
} ?
//執行結果ret = 1.
?
利用Select模型,設計的web服務器:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define MYPORT 88960 // the port users will be connecting to #define BACKLOG 10 // how many pending connections queue will hold #define BUF_SIZE 200 int fd_A[BACKLOG]; // accepted connection fd int conn_amount; // current connection amount void showclient() { int i; printf("client amount: %d\n", conn_amount); for (i = 0; i < BACKLOG; i++) { printf("[%d]:%d ", i, fd_A[i]); } printf("\n\n"); } int main(void) { int sock_fd, new_fd; // listen on sock_fd, new connection on new_fd struct sockaddr_in server_addr; // server address information struct sockaddr_in client_addr; // connector's address information socklen_t sin_size; int yes = 1; char buf[BUF_SIZE]; int ret; int i; if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket"); exit(1); } if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) { perror("setsockopt"); exit(1); } server_addr.sin_family = AF_INET; // host byte order server_addr.sin_port = htons(MYPORT); // short, network byte order server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero)); if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) { perror("bind"); exit(1); } if (listen(sock_fd, BACKLOG) == -1) { perror("listen"); exit(1); } printf("listen port %d\n", MYPORT); fd_set fdsr; int maxsock; struct timeval tv; conn_amount = 0; sin_size = sizeof(client_addr); maxsock = sock_fd; while (1) { // initialize file descriptor set FD_ZERO(&fdsr); FD_SET(sock_fd, &fdsr); // timeout setting tv.tv_sec = 30; tv.tv_usec = 0; // add active connection to fd set for (i = 0; i < BACKLOG; i++) { if (fd_A[i] != 0) { FD_SET(fd_A[i], &fdsr); } } ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv); if (ret < 0) { perror("select"); break; } else if (ret == 0) { printf("timeout\n"); continue; } // check every fd in the set for (i = 0; i < conn_amount; i++) { if (FD_ISSET(fd_A[i], &fdsr)) { ret = recv(fd_A[i], buf, sizeof(buf), 0); char str[] = "Good,very nice!\n"; send(fd_A[i],str,sizeof(str) + 1, 0); if (ret <= 0) { // client close printf("client[%d] close\n", i); close(fd_A[i]); FD_CLR(fd_A[i], &fdsr); fd_A[i] = 0; } else { // receive data if (ret < BUF_SIZE) memset(&buf[ret], '\0', 1); printf("client[%d] send:%s\n", i, buf); } } } // check whether a new connection comes if (FD_ISSET(sock_fd, &fdsr)) { new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size); if (new_fd <= 0) { perror("accept"); continue; } // add to fd queue if (conn_amount < BACKLOG) { fd_A[conn_amount++] = new_fd; printf("new connection client[%d] %s:%d\n", conn_amount, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); if (new_fd > maxsock) maxsock = new_fd; } else { printf("max connections arrive, exit\n"); send(new_fd, "bye", 4, 0); close(new_fd); break; } } showclient(); } // close other connections for (i = 0; i < BACKLOG; i++) { if (fd_A[i] != 0) { close(fd_A[i]); } } exit(0); }?
上面的都是 select 和 blocking io的使用示例。
?
下面是select 和 non blocking io的使用示例:
Nonblocking I/O and select()
https://www-01.ibm.com/support/knowledgecenter/ssw_ibm_i_71/rzab6/xnonblock.htm
This sample program illustrates a server application that uses nonblocking and the select() API.
?
Socket flow of events: Server that uses nonblocking I/O and select()
The following calls are used in the example:
?
EAGAIN=EWOULDBLOCK(BSD風格)
此錯誤由在非阻塞套接字上不能立即完成的操作返回,例如,當套接字上沒有排隊數據可讀時調用了recv()函數(比如協議棧接收到了數據但拷貝狀態還未結束,忙)。此錯誤不是嚴重錯誤,相應操作應該稍后重試。對于在非阻塞?SOCK_STREAM套接字上調用connect()函數來說,報告EWOULDBLOCK是正常的,因為建立一個連接必須花費一些時間。
在linux進行非阻塞的socket接收數據時經常出現Resource temporarily unavailable,errno代碼為11(EAGAIN),這是什么意思?
這表明你在非阻塞模式下調用了阻塞操作,在該操作沒有完成就返回這個錯誤,這個錯誤不會破壞socket的同步,不用管它,下次循環接著recv就可以。 對非阻塞socket而言,EAGAIN不是一種錯誤。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。
non-blocking和select結合使用
select通過輪詢,監視指定file descriptor(包括socket)的變化,知道:哪些ready for reading, 哪些ready for writing,哪些發生了錯誤等。select和non-blocking結合使用可很好地實現socket的多client同步通信。
通過判斷返回的errno了解狀態。
accept():
??????? 在non-blocking模式下,如果返回值為-1,且errno == EAGAIN或errno == EWOULDBLOCK表示no connections沒有新連接請求;
recv()/recvfrom():
??????? 在non-blocking模式下,如果返回值為-1,且errno == EAGAIN表示沒有可接受的數據或很在接受尚未完成;
send()/sendto():
??????? 在non-blocking模式下,如果返回值為-1,且errno == EAGAIN或errno == EWOULDBLOCK表示沒有可發送數據或數據發送正在進行沒有完成。
read/write:
??????? 在non-blocking模式下,如果返回-1,且errno == EAGAIN表示沒有可讀寫數據或可讀寫正在進行尚未完成。
connect():
??????? 在non-bloking模式下,如果返回-1,且errno = EINPROGRESS表示正在連接。
?
?
?
?
?
?
IO多路復用之epoll總結
http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
總結
以上是生活随笔為你收集整理的linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(3289):react hoo
- 下一篇: 前端学习(3128):react-hel