除了本身實現以外,還有個c語言寫的基於事件的開源網絡庫:libeventhtml
http://www.cnblogs.com/Anker/p/3265058.htmllinux
最簡單的select示例:web
#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都是IO多路複用的機制。I/O多路複用就經過一種機制,能夠監視多個描述符,一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做。但select,poll,epoll本質上都是同步I/O,由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需本身負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。關於這三種IO多路複用的用法,前面三篇總結寫的很清楚,並用服務器回射echo程序進行了測試。鏈接以下所示:api
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多路複用進行對比,參考網上和書上面的資料,整理以下:多線程
一、select實現app
select的調用過程以下所示:less
(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
三、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多路複用是指內核一旦發現進程指定的一個或者多個IO條件準備讀取,它就通知該進程。IO多路複用適用以下場合:
(1)當客戶處理多個描述字時(通常是交互式輸入和網絡套接口),必須使用I/O複用。
(2)當一個客戶同時處理多個套接口時,而這種狀況是可能的,但不多出現。
(3)若是一個TCP服務器既要處理監聽套接口,又要處理已鏈接套接口,通常也要用到I/O複用。
(4)若是一個服務器即要處理TCP,又要處理UDP,通常要使用I/O複用。
(5)若是一個服務器要處理多個服務或多個協議,通常要使用I/O複用。
與多進程和多線程技術相比,I/O多路複用技術的最大優點是系統開銷小,系統沒必要建立進程/線程,也沒必要維護這些進程/線程,從而大大減少了系統的開銷。
二、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、一、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。
原理圖:
三、測試程序
寫一個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; }
四、程序結果
啓動服務程序,執行三個個客戶程序進行測試,結果以下圖所示:
參考:
http://konglingchun.is-programmer.com/posts/12146.html
http://blog.163.com/smileface100@126/blog/static/27720874200951024532966/
下面給一個僞碼說明基本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的使用示例:
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.
The following calls are used in the example:
#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。
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表示正在鏈接。
http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html