linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例
除了自己實(shí)現(xiàn)之外,還有個(gè)c語言寫的基于事件的開源網(wǎng)絡(luò)庫: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之間的區(qū)別總結(jié)[整理]
select,poll,epoll都是IO多路復(fù)用的機(jī)制。I/O多路復(fù)用就通過一種機(jī)制,可以監(jiān)視多個(gè)描述符,一旦某個(gè)描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進(jìn)行相應(yīng)的讀寫操作。但select,poll,epoll本質(zhì)上都是同步I/O,因?yàn)樗麄兌夹枰谧x寫事件就緒后自己負(fù)責(zé)進(jìn)行讀寫,也就是說這個(gè)讀寫過程是阻塞的,而異步I/O則無需自己負(fù)責(zé)進(jìn)行讀寫,異步I/O的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。關(guān)于這三種IO多路復(fù)用的用法,前面三篇總結(jié)寫的很清楚,并用服務(wù)器回射echo程序進(jìn)行了測試。連接如下所示:
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
今天對(duì)這三種IO多路復(fù)用進(jìn)行對(duì)比,參考網(wǎng)上和書上面的資料,整理如下:
1、select實(shí)現(xiàn)
select的調(diào)用過程如下所示:
(1)使用copy_from_user從用戶空間拷貝fd_set到內(nèi)核空間
(2)注冊(cè)回調(diào)函數(shù)__pollwait
(3)遍歷所有fd,調(diào)用其對(duì)應(yīng)的poll方法(對(duì)于socket,這個(gè)poll方法是sock_poll,sock_poll根據(jù)情況會(huì)調(diào)用到tcp_poll,udp_poll或者datagram_poll)
(4)以tcp_poll為例,其核心實(shí)現(xiàn)就是__pollwait,也就是上面注冊(cè)的回調(diào)函數(shù)。
(5)__pollwait的主要工作就是把current(當(dāng)前進(jìn)程)掛到設(shè)備的等待隊(duì)列中,不同的設(shè)備有不同的等待隊(duì)列,對(duì)于tcp_poll 來說,其等待隊(duì)列是sk->sk_sleep(注意把進(jìn)程掛到等待隊(duì)列中并不代表進(jìn)程已經(jīng)睡眠了)。在設(shè)備收到一條消息(網(wǎng)絡(luò)設(shè)備)或填寫完文件數(shù) 據(jù)(磁盤設(shè)備)后,會(huì)喚醒設(shè)備等待隊(duì)列上睡眠的進(jìn)程,這時(shí)current便被喚醒了。
(6)poll方法返回時(shí)會(huì)返回一個(gè)描述讀寫操作是否就緒的mask掩碼,根據(jù)這個(gè)mask掩碼給fd_set賦值。
(7)如果遍歷完所有的fd,還沒有返回一個(gè)可讀寫的mask掩碼,則會(huì)調(diào)用schedule_timeout是調(diào)用select的進(jìn)程(也就是 current)進(jìn)入睡眠。當(dāng)設(shè)備驅(qū)動(dòng)發(fā)生自身資源可讀寫后,會(huì)喚醒其等待隊(duì)列上睡眠的進(jìn)程。如果超過一定的超時(shí)時(shí)間(schedule_timeout 指定),還是沒人喚醒,則調(diào)用select的進(jìn)程會(huì)重新被喚醒獲得CPU,進(jìn)而重新遍歷fd,判斷有沒有就緒的fd。
(8)把fd_set從內(nèi)核空間拷貝到用戶空間。
總結(jié):
select的幾大缺點(diǎn):
(1)每次調(diào)用select,都需要把fd集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開銷在fd很多時(shí)會(huì)很大
(2)同時(shí)每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來的所有fd,這個(gè)開銷在fd很多時(shí)也很大
(3)select支持的文件描述符數(shù)量太小了,默認(rèn)是1024
2 poll實(shí)現(xiàn)
poll的實(shí)現(xiàn)和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結(jié)構(gòu)而不是select的fd_set結(jié)構(gòu),其他的都差不多。
關(guān)于select和poll的實(shí)現(xiàn)分析,可以參考下面幾篇博文:
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既然是對(duì)select和poll的改進(jìn),就應(yīng)該能避免上述的三個(gè)缺點(diǎn)。那epoll都是怎么解決的呢?在此之前,我們先看一下 epoll和select和poll的調(diào)用接口上的不同,select和poll都只提供了一個(gè)函數(shù)——select或者poll函數(shù)。而epoll提供 了三個(gè)函數(shù),epoll_create,epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個(gè)epoll句 柄;epoll_ctl是注冊(cè)要監(jiān)聽的事件類型;epoll_wait則是等待事件的產(chǎn)生。
對(duì)于第一個(gè)缺點(diǎn),epoll的解決方案在epoll_ctl函數(shù)中。每次注冊(cè)新的事件到epoll句柄中時(shí)(在epoll_ctl中指定 EPOLL_CTL_ADD),會(huì)把所有的fd拷貝進(jìn)內(nèi)核,而不是在epoll_wait的時(shí)候重復(fù)拷貝。epoll保證了每個(gè)fd在整個(gè)過程中只會(huì)拷貝 一次。
對(duì)于第二個(gè)缺點(diǎn),epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對(duì)應(yīng)的設(shè)備等待隊(duì)列中,而只在 epoll_ctl時(shí)把current掛一遍(這一遍必不可少)并為每個(gè)fd指定一個(gè)回調(diào)函數(shù),當(dāng)設(shè)備就緒,喚醒等待隊(duì)列上的等待者時(shí),就會(huì)調(diào)用這個(gè)回調(diào) 函數(shù),而這個(gè)回調(diào)函數(shù)會(huì)把就緒的fd加入一個(gè)就緒鏈表)。epoll_wait的工作實(shí)際上就是在這個(gè)就緒鏈表中查看有沒有就緒的fd(利用 schedule_timeout()實(shí)現(xiàn)睡一會(huì),判斷一會(huì)的效果,和select實(shí)現(xiàn)中的第7步是類似的)。
對(duì)于第三個(gè)缺點(diǎn),epoll沒有這個(gè)限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于2048,舉個(gè)例子, 在1GB內(nèi)存的機(jī)器上大約是10萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。
總結(jié):
(1)select,poll實(shí)現(xiàn)需要自己不斷輪詢所有fd集合,直到設(shè)備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實(shí)也需要調(diào)用 epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設(shè)備就緒時(shí),調(diào)用回調(diào)函數(shù),把就緒fd放入就緒鏈表中,并喚醒在 epoll_wait中進(jìn)入睡眠的進(jìn)程。雖然都要睡眠和交替,但是select和poll在“醒著”的時(shí)候要遍歷整個(gè)fd集合,而epoll在“醒著”的 時(shí)候只要判斷一下就緒鏈表是否為空就行了,這節(jié)省了大量的CPU時(shí)間。這就是回調(diào)機(jī)制帶來的性能提升。
(2)select,poll每次調(diào)用都要把fd集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次,并且要把current往設(shè)備等待隊(duì)列中掛一次,而epoll只要 一次拷貝,而且把current往等待隊(duì)列上掛也只掛一次(在epoll_wait的開始,注意這里的等待隊(duì)列并不是設(shè)備等待隊(duì)列,只是一個(gè)epoll內(nèi) 部定義的等待隊(duì)列)。這也能節(jié)省不少的開銷。
參考資料:
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多路復(fù)用之select總結(jié)
1、基本概念
IO多路復(fù)用是指內(nèi)核一旦發(fā)現(xiàn)進(jìn)程指定的一個(gè)或者多個(gè)IO條件準(zhǔn)備讀取,它就通知該進(jìn)程。IO多路復(fù)用適用如下場合:
(1)當(dāng)客戶處理多個(gè)描述字時(shí)(一般是交互式輸入和網(wǎng)絡(luò)套接口),必須使用I/O復(fù)用。
(2)當(dāng)一個(gè)客戶同時(shí)處理多個(gè)套接口時(shí),而這種情況是可能的,但很少出現(xiàn)。
(3)如果一個(gè)TCP服務(wù)器既要處理監(jiān)聽套接口,又要處理已連接套接口,一般也要用到I/O復(fù)用。
(4)如果一個(gè)服務(wù)器即要處理TCP,又要處理UDP,一般要使用I/O復(fù)用。
(5)如果一個(gè)服務(wù)器要處理多個(gè)服務(wù)或多個(gè)協(xié)議,一般要使用I/O復(fù)用。
與多進(jìn)程和多線程技術(shù)相比,I/O多路復(fù)用技術(shù)的最大優(yōu)勢是系統(tǒng)開銷小,系統(tǒng)不必創(chuàng)建進(jìn)程/線程,也不必維護(hù)這些進(jìn)程/線程,從而大大減小了系統(tǒng)的開銷。
2、select函數(shù)
該函數(shù)準(zhǔn)許進(jìn)程指示內(nèi)核等待多個(gè)事件中的任何一個(gè)發(fā)送,并只在有一個(gè)或多個(gè)事件發(fā)生或經(jīng)歷一段指定的時(shí)間后才喚醒。函數(shù)原型如下:
#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)返回值:就緒描述符的數(shù)目,超時(shí)返回0,出錯(cuò)返回-1
函數(shù)參數(shù)介紹如下:
(1)第一個(gè)參數(shù)maxfdp1指定待測試的描述字個(gè)數(shù),它的值是待測試的最大描述字加1(因此把該參數(shù)命名為maxfdp1),描述字0、1、2...maxfdp1-1均將被測試。
因?yàn)槲募枋龇菑?開始的。
(2)中間的三個(gè)參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測試讀、寫和異常條件的描述字。如果對(duì)某一個(gè)的條件不感興趣,就可以把它設(shè)為空指針。struct fd_set可以理解為一個(gè)集合,這個(gè)集合中存放的是文件描述符,可通過以下四個(gè)宏進(jìn)行設(shè)置:
????????? void FD_ZERO(fd_set *fdset);?????????? //清空集合
????????? void FD_SET(int fd, fd_set *fdset);?? //將一個(gè)給定的文件描述符加入集合之中
????????? void FD_CLR(int fd, fd_set *fdset);?? //將一個(gè)給定的文件描述符從集合中刪除
????????? int FD_ISSET(int fd, fd_set *fdset);?? // 檢查集合中指定的文件描述符是否可以讀寫?
(3)timeout告知內(nèi)核等待所指定描述字中的任何一個(gè)就緒可花多少時(shí)間。其timeval結(jié)構(gòu)用于指定這段時(shí)間的秒數(shù)和微秒數(shù)。
???????? struct timeval{
?????????????????? long tv_sec;?? //seconds
?????????????????? long tv_usec;? //microseconds
?????? };
這個(gè)參數(shù)有三種可能:
(1)永遠(yuǎn)等待下去:僅在有一個(gè)描述字準(zhǔn)備好I/O時(shí)才返回。為此,把該參數(shù)設(shè)置為空指針NULL。
(2)等待一段固定時(shí)間:在有一個(gè)描述字準(zhǔn)備好I/O時(shí)返回,但是不超過由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)。
(3)根本不等待:檢查描述字后立即返回,這稱為輪詢。為此,該參數(shù)必須指向一個(gè)timeval結(jié)構(gòu),而且其中的定時(shí)器值必須為0。
?原理圖:
3、測試程序
寫一個(gè)TCP回射程序,程序的功能是:客戶端向服務(wù)器發(fā)送信息,服務(wù)器接收并原樣發(fā)送給客戶端,客戶端顯示出接收到的信息。
服務(wù)端程序如下所示:
#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; /*客戶端個(gè)數(shù)*/int clifds[SIZE]; /*客戶端的個(gè)數(shù)*/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);//將新的連接描述符添加到數(shù)組中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)) {//接收客戶端發(fā)送的信息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) {/*每次調(diào)用select前都要重新設(shè)置文件描述符和時(shí)間,因?yàn)槭录l(fā)生后,文件描述符和時(shí)間都被內(nèi)核修改啦*//*添加監(jiān)聽套接字*/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)) {/*監(jiān)聽客戶端請(qǐng)求*/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、程序結(jié)果
啟動(dòng)服務(wù)程序,執(zhí)行三個(gè)個(gè)客戶程序進(jìn)行測試,結(jié)果如下圖所示:
參考:
http://konglingchun.is-programmer.com/posts/12146.html
http://blog.163.com/smileface100@126/blog/static/27720874200951024532966/
?
linux select 多路復(fù)用機(jī)制
下面給一個(gè)偽碼說明基本select模型的服務(wù)器模型:
array[slect_len]; nSock=0; array[nSock++]=listen_fd;(之前l(fā)isten 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 下標(biāo)1開始 (fd in array) { if(FD_ISSET(fd,&tyle="COLOR: #ff0000">set)) 執(zhí)行讀等相關(guān)操作 如果錯(cuò)誤或者關(guān)閉,則要?jiǎng)h除該fd,將array中相應(yīng)位置和最后一個(gè)元素互換就好,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; ?
} ?
//執(zhí)行結(jié)果ret = 1.
?
利用Select模型,設(shè)計(jì)的web服務(wù)器:
#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風(fēng)格)
此錯(cuò)誤由在非阻塞套接字上不能立即完成的操作返回,例如,當(dāng)套接字上沒有排隊(duì)數(shù)據(jù)可讀時(shí)調(diào)用了recv()函數(shù)(比如協(xié)議棧接收到了數(shù)據(jù)但拷貝狀態(tài)還未結(jié)束,忙)。此錯(cuò)誤不是嚴(yán)重錯(cuò)誤,相應(yīng)操作應(yīng)該稍后重試。對(duì)于在非阻塞?SOCK_STREAM套接字上調(diào)用connect()函數(shù)來說,報(bào)告EWOULDBLOCK是正常的,因?yàn)榻⒁粋€(gè)連接必須花費(fèi)一些時(shí)間。
在linux進(jìn)行非阻塞的socket接收數(shù)據(jù)時(shí)經(jīng)常出現(xiàn)Resource temporarily unavailable,errno代碼為11(EAGAIN),這是什么意思?
這表明你在非阻塞模式下調(diào)用了阻塞操作,在該操作沒有完成就返回這個(gè)錯(cuò)誤,這個(gè)錯(cuò)誤不會(huì)破壞socket的同步,不用管它,下次循環(huán)接著recv就可以。 對(duì)非阻塞socket而言,EAGAIN不是一種錯(cuò)誤。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。
non-blocking和select結(jié)合使用
select通過輪詢,監(jiān)視指定file descriptor(包括socket)的變化,知道:哪些ready for reading, 哪些ready for writing,哪些發(fā)生了錯(cuò)誤等。select和non-blocking結(jié)合使用可很好地實(shí)現(xiàn)socket的多client同步通信。
通過判斷返回的errno了解狀態(tài)。
accept():
??????? 在non-blocking模式下,如果返回值為-1,且errno == EAGAIN或errno == EWOULDBLOCK表示no connections沒有新連接請(qǐng)求;
recv()/recvfrom():
??????? 在non-blocking模式下,如果返回值為-1,且errno == EAGAIN表示沒有可接受的數(shù)據(jù)或很在接受尚未完成;
send()/sendto():
??????? 在non-blocking模式下,如果返回值為-1,且errno == EAGAIN或errno == EWOULDBLOCK表示沒有可發(fā)送數(shù)據(jù)或數(shù)據(jù)發(fā)送正在進(jìn)行沒有完成。
read/write:
??????? 在non-blocking模式下,如果返回-1,且errno == EAGAIN表示沒有可讀寫數(shù)據(jù)或可讀寫正在進(jìn)行尚未完成。
connect():
??????? 在non-bloking模式下,如果返回-1,且errno = EINPROGRESS表示正在連接。
?
?
?
?
?
?
IO多路復(fù)用之epoll總結(jié)
http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html
總結(jié)
以上是生活随笔為你收集整理的linux select 与 阻塞( blocking ) 及非阻塞 (non blocking)实现io多路复用的示例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习(3289):react hoo
- 下一篇: ubuntu下使用锐捷客户端连接校园网-