Unix网络编程—— I/O复用之select
Unix的五种I/O模型
阻塞式I/O 非阻塞式I/O I/O复用(select poll) 信号驱动式I/O(SIGIO) 异步I/O(POSIX的aio系列函数)阻塞与非阻塞I/O
最流行的I/O模型是阻塞式I/O,一般默认情况下所有套接字都是阻塞的,但是进程可以把一个套接字设置成非阻塞式I/O,以通知内核——当所请求的I/O操作必须把当前进程投入睡眠时才能完成时,不要把当前进程投入睡眠,而是返回一个错误。
I/O复用
当我们使用select或者poll将进程阻塞在这两个系统调用之上而不是阻塞在真正的I/O调用上时,成为I/O复用。I/O复用的好处是,可以让一个进程同时对多个套接字描述符的状态进行监控。而传统的阻塞式一个进程监控一个套接字的状态。
select函数
select函数可以使一个进程同时等待多个事件中的任何一个发生,并且只在有一个或多个事件发生或经历一段制定的时间后才唤醒它。 注意:select函数监听的set集合在每次返回后非激活状态的套接字位在set集合中被清空,因此每次重新select时需要更新fd_set
#include <sys/select.h> #include <sys/time.h> int select(int maxfd1, fd_set *readset, // 读描述符监控 fd_set *writeset, // 写描述符监控 fd_set *exceptset, // 异常条件描述符监控 const struct timeval *timeout // 超时等待设置 ); //返回:若有就绪套接字则返回其数目,超时返回0,失败返回-1对于exceptset目前支持2中条件:
某个套接字的带外数据到达 某个已置为分组模式的伪终端存在可从其主机读取的控制状态信息(不作讨论)fd_set及其操作
通过FD_SET、FD_CLR、FD_ISSET、FD_ZERO等宏,可以使fd_set结构的每一位与要监控的描述符绑定、清除绑定、判定绑定、清空结构体。在Ubuntu16.4上使用sizeof测试,得到其长度为128字节,也就是1024位,为其同时支持的最多的描述符个数。
void FD_SERO(fd_set *fdset); // 清空fdset中的所有位 void FD_SET(int fd,fd_set *fdset); // 将描述符fd设置为监听状态 void FD_CLR(int fd,fd_set *fdset); //将fd从fdset中清除 void fd_ISSET(int fd,fd_set *fdset);//判断fd是否为集合fdset中的监听套接字描述符就绪条件
读套接字就绪条件: 写套接字就绪条件: 异常条件就绪:
struct timeval结构体
struct timeval{ long tv_sec; // seconds long tv_usec; // micro seconds }用于指定等待超时的秒数和微秒数。这个参数分为三种情况:
永远等待,直到有一个描述符准备就绪才返回。此时该参数设置为NULL 等待固定时间:在有一个描述符准备就绪时提前返回,否则超时返回0 不等待:将定时器的值都设置为0shutdown函数
终止网络连接的方式通常是调用close函数。不过close函数有2个限制,可以通过使用shutdown来避免。
close把描述符的引用计数减一,仅在该数变为0的时候才关闭套接字。使用shutdown可以不管引用计数,直接激发TCP的正常连接终止序列。 close终止读和谐两个方向的数据传送。既然TCP连接是全涮工搞的,有时候我们需要告知对端我们已经完成了数据的发送,及时对端仍然有数据要发送给我们,此时如果调用close将不能收到对端的数据,而使用shutdown则可以选择关闭读、写或者读写全部关闭。 int shutdown(int sockfd,int howto); //返回:成功返回0,失败返回-1howto的可选项:
SHUT_RD:关闭连接的读——套接字中不再读取数据,而且套接字接受缓冲区中的数据也会被丢弃。进程不能再对这个套接字调用任何读取函数。 SHUT_WR:关闭连接的写——对于TCP套接字,这成为半关闭。当前留在套接字发送缓冲区中的数据将被发送掉,然后TCP的正常连接终止序列。无论这个套接字描述符的引用计数是否为0,shutdown都会激发TCP终止序列,以后进程不能再对这个套接字调用任何写函数。 SHUT_RDWR:关闭读、写——相当于分别调取了shutdown两次并传递参数SHUT_RD和SHUT_WR使用select和shutdown的网络服务器/客户端例子
// serv_select.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/select.h> #include <sys/time.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <unistd.h> #define FD_SET_SIZE 1024 #define LISTEN_PORT 8003 #define MAX_BUF_LEN 1024 int main(){ int i,imaxfd,imax,listenfd,connectfd,nready,nread,iaddrlen; struct sockaddr_in listen_addr,client_addr; char buf[MAX_BUF_LEN]; int all_clientfd[FD_SET_SIZE]; for(i = 0;i < FD_SET_SIZE;++i) all_clientfd[i] = -1; listenfd = socket(AF_INET,SOCK_STREAM,0); listen_addr.sin_family = AF_INET; listen_addr.sin_port = htons(LISTEN_PORT); listen_addr.sin_addr.s_addr = htonl(INADDR_ANY); int ret = bind(listenfd,(struct sockaddr *)&listen_addr,sizeof(listen_addr)); ret = listen(listenfd,LISTENQ); fd_set all_set,read_set; FD_ZERO(&all_set); FD_SET(listenfd,&all_set); imax = -1; imaxfd = listenfd; while(1){ read_set = all_set; nready = select(imaxfd + 1,&read_set,NULL,NULL,NULL); if(nready < 0){ perror("select"); exit(1); } if(FD_ISSET(listenfd,&read_set)){ iaddrlen = sizeof(listen_addr); connectfd = accept(listenfd,(struct sockaddr*)&listen_addr,&iaddrlen); for(i = 0;i < FD_SET_SIZE;++i){ if(all_clientfd[i] == -1){ all_clientfd[i] = connectfd; break; } } if(i == FD_SET_SIZE){ PRintf("out of max listen fd count!\n"); continue; } if(i > imax){ imax = i; } FD_SET(connectfd,&all_set); if(connectfd > imaxfd) imaxfd = connectfd; if(--nready <= 0) continue; } for(i = 0;i < imax;++i){ if(all_client[i] == -1) continue; if(FD_ISSET(all_client[i],&read_set)){ if(nread = read(all_client[i],buf,MAX_BUF_LEN) <= 0){ close(all_client[i]); FD_CLR(all_client[i],&all_set); all_client[i] = -1; } else { write(all_client[i],buf,nread); } if(--nready <= 0) break; } } } } // client_select.c