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 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
支付宝扫一扫捐赠
微信公众号: 共鸣圈
欢迎讨论,邮件: 924948$qq.com 请把$改成@
QQ群:263132197
QQ: 924948