除了自己實現之外,還有個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 input int 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"); else printf("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 10 typedef 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 : b static 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:
- The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the INET (Internet Protocol) address family with the TCP transport (SOCK_STREAM) is used for this socket.
- The ioctl() API allows the local address to be reused when the server is restarted before the required wait time expires. In this example, it sets the socket to be nonblocking. All of the sockets for the incoming connections are also nonblocking because they inherit that state from the listening socket.
- After the socket descriptor is created, the bind() gets a unique name for the socket.
- The listen() allows the server to accept incoming client connections.
- The server uses the accept() API to accept an incoming connection request. The accept() API call blocks indefinitely, waiting for the incoming connection to arrive.
- The select() API allows the process to wait for an event to occur and to wake up the process when the event occurs. In this example, the select() API returns a number that represents the socket descriptors that are ready to be processed.
- 0
- Indicates that the process times out. In this example, the timeout is set for 3 minutes.
- -1
- Indicates that the process has failed.
- 1
- Indicates only one descriptor is ready to be processed. In this example, when a 1 is returned, the FD_ISSET and the subsequent socket calls complete only once.
- n
- Indicates that multiple descriptors are waiting to be processed. In this example, when an n is returned, the FD_ISSET and subsequent code loops and completes the requests in the order they are received by the server.
- The accept() and recv() APIs are completed when the EWOULDBLOCK is returned.
- The send() API echoes the data back to the client.
- The close() API closes any open socket descriptors.
#include <stdio.h> #include <stdlib.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <errno.h> #define SERVER_PORT 12345 #define TRUE 1 #define FALSE 0 main (int argc, char *argv[]) { int i, len, rc, on = 1; int listen_sd, max_sd, new_sd; int desc_ready, end_server = FALSE; int close_conn; char buffer[80]; struct sockaddr_in6 addr; struct timeval timeout; struct fd_set master_set, working_set; /*************************************************************/ /* Create an AF_INET6 stream socket to receive incoming */ /* connections on */ /*************************************************************/ listen_sd = socket(AF_INET6, SOCK_STREAM, 0); if (listen_sd < 0) { perror("socket() failed"); exit(-1); } /*************************************************************/ /* Allow socket descriptor to be reuseable */ /*************************************************************/ rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)); if (rc < 0) { perror("setsockopt() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Set socket to be nonblocking. All of the sockets for */ /* the incoming connections will also be nonblocking since */ /* they will inherit that state from the listening socket. */ /*************************************************************/ rc = ioctl(listen_sd, FIONBIO, (char *)&on); if (rc < 0) { perror("ioctl() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Bind the socket */ /*************************************************************/ memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; memcpy(&addr.sin6_addr, &in6addr_any, sizeof(in6addr_any)); addr.sin6_port = htons(SERVER_PORT); rc = bind(listen_sd, (struct sockaddr *)&addr, sizeof(addr)); if (rc < 0) { perror("bind() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Set the listen back log */ /*************************************************************/ rc = listen(listen_sd, 32); if (rc < 0) { perror("listen() failed"); close(listen_sd); exit(-1); } /*************************************************************/ /* Initialize the master fd_set */ /*************************************************************/ FD_ZERO(&master_set); max_sd = listen_sd; FD_SET(listen_sd, &master_set); /*************************************************************/ /* Initialize the timeval struct to 3 minutes. If no */ /* activity after 3 minutes this program will end. */ /*************************************************************/ timeout.tv_sec = 3 * 60; timeout.tv_usec = 0; /*************************************************************/ /* Loop waiting for incoming connects or for incoming data */ /* on any of the connected sockets. */ /*************************************************************/ do { /**********************************************************/ /* Copy the master fd_set over to the working fd_set. */ /**********************************************************/ memcpy(&working_set, &master_set, sizeof(master_set)); /**********************************************************/ /* Call select() and wait 3 minutes for it to complete. */ /**********************************************************/ printf("Waiting on select()...\n"); rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout); /**********************************************************/ /* Check to see if the select call failed. */ /**********************************************************/ if (rc < 0) { perror(" select() failed"); break; } /**********************************************************/ /* Check to see if the 3 minute time out expired. */ /**********************************************************/ if (rc == 0) { printf(" select() timed out. End program.\n"); break; } /**********************************************************/ /* One or more descriptors are readable. Need to */ /* determine which ones they are. */ /**********************************************************/ desc_ready = rc; for (i=0; i <= max_sd && desc_ready > 0; ++i) { /*******************************************************/ /* Check to see if this descriptor is ready */ /*******************************************************/ if (FD_ISSET(i, &working_set)) { /****************************************************/ /* A descriptor was found that was readable - one */ /* less has to be looked for. This is being done */ /* so that we can stop looking at the working set */ /* once we have found all of the descriptors that */ /* were ready. */ /****************************************************/ desc_ready -= 1; /****************************************************/ /* Check to see if this is the listening socket */ /****************************************************/ if (i == listen_sd) { printf(" Listening socket is readable\n"); /*************************************************/ /* Accept all incoming connections that are */ /* queued up on the listening socket before we */ /* loop back and call select again. */ /*************************************************/ do { /**********************************************/ /* Accept each incoming connection. If */ /* accept fails with EWOULDBLOCK, then we */ /* have accepted all of them. Any other */ /* failure on accept will cause us to end the */ /* server. */ /**********************************************/ new_sd = accept(listen_sd, NULL, NULL); if (new_sd < 0) { if (errno != EWOULDBLOCK) { perror(" accept() failed"); end_server = TRUE; } break; } /**********************************************/ /* Add the new incoming connection to the */ /* master read set */ /**********************************************/ printf(" New incoming connection - %d\n", new_sd); FD_SET(new_sd, &master_set); if (new_sd > max_sd) max_sd = new_sd; /**********************************************/ /* Loop back up and accept another incoming */ /* connection */ /**********************************************/ } while (new_sd != -1); } /****************************************************/ /* This is not the listening socket, therefore an */ /* existing connection must be readable */ /****************************************************/ else { printf(" Descriptor %d is readable\n", i); close_conn = FALSE; /*************************************************/ /* Receive all incoming data on this socket */ /* before we loop back and call select again. */ /*************************************************/ do { /**********************************************/ /* Receive data on this connection until the */ /* recv fails with EWOULDBLOCK. If any other */ /* failure occurs, we will close the */ /* connection. */ /**********************************************/ rc = recv(i, buffer, sizeof(buffer), 0); if (rc < 0) { if (errno != EWOULDBLOCK) { perror(" recv() failed"); close_conn = TRUE; } break; } /**********************************************/ /* Check to see if the connection has been */ /* closed by the client */ /**********************************************/ if (rc == 0) { printf(" Connection closed\n"); close_conn = TRUE; break; } /**********************************************/ /* Data was received */ /**********************************************/ len = rc; printf(" %d bytes received\n", len); /**********************************************/ /* Echo the data back to the client */ /**********************************************/ rc = send(i, buffer, len, 0); if (rc < 0) { perror(" send() failed"); close_conn = TRUE; break; } } while (TRUE); /*************************************************/ /* If the close_conn flag was turned on, we need */ /* to clean up this active connection. This */ /* clean up process includes removing the */ /* descriptor from the master set and */ /* determining the new maximum descriptor value */ /* based on the bits that are still turned on in */ /* the master set. */ /*************************************************/ if (close_conn) { close(i); FD_CLR(i, &master_set); if (i == max_sd) { while (FD_ISSET(max_sd, &master_set) == FALSE) max_sd -= 1; } } } /* End of existing connection is readable */ } /* End of if (FD_ISSET(i, &working_set)) */ } /* End of loop through selectable descriptors */ } while (end_server == FALSE); /*************************************************************/ /* Clean up all of the sockets that are open */ /*************************************************************/ for (i=0; i <= max_sd; ++i) { if (FD_ISSET(i, &master_set)) close(i); } }
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