UNP学习笔记之四-select和poll
对于I/O操作,有以下几种模型:
1、阻塞I/O(Blocking I/O):udp协议中的recvfrom在接受数据时进行等待就是使用此模型。
2.非阻塞I/O:recvfrom不阻塞,在数据未准备好时返回错误。
3.I/O复用:select和poll,轮询描述符是否准备好,如果准备好了就调用recvfrom获取数据。select可以同时监听多个描述符,但是受限于系统分配的描述符大小。
4.信号驱动I/O:使用sigaction注册一个信号回调函数,在数据准备好的时候回调该函数。
5.异步I/O:POSIX中进行了定义,异步I/O是在所有的数据读取操作进行完成后异步通过信号通知给程序。
5种不同的I/O模型的纵向对比:
接下来说我们的主角,select和poll,先说select:
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);
Returns: positive count of ready descriptors, 0 on timeout, –1 on error
第一个参数表示检测的描述符中最大的个数,一般的取值是最大的描述符的值再加上1,max fd plus 1.中间3个参数分别表示需要监听的读,写,错误的描述符集合。为空则表示不感兴趣。如果3个都为空,则可以用来作为sleep函数,精度可以控制到毫秒,因为最后一个参数struct timeval可以表示到毫秒。
select的返回值>0表示准备好的描述符的个数,但是具体是哪几个描述符准备好,则需要使用FD_ISSET去判断。
以下4种情况socket准备好读取数据:
1、socket接受缓冲区的数据大小大于或者等于socket接受缓冲区定义的最小接受长度,可以使用SO_RCVLOWAT选项设置最小接受长度,tcp和udp默认的长度均为1.
2、连接的一方已经关闭连接。read操作不会阻塞且立即返回0.
3、socket是一个监听socket时,对应的完成连接数>0,这个socket对应的accept函数不会被阻塞。
4、socket产生错误时,read操作不会阻塞且返回-1.
以下4种情况socket准备好写数据:
1、socket发送缓冲区大小大于或者等于socket发送缓冲区定义的最小发送长度,并且具备以下条件:(1)socket已经连接。(2)socket不需要连接(udp)。如果我们设置socket为非阻塞,write操作不会阻塞且立即返回一个正值。使用SO_SNDLOWAT可以设置最小发送长度。默认大小为2048(udp和tcp)
2、连接的一方已经关闭。write操作会产生SIGPIPE.
3、使用非阻塞的connect函数已经完成或者connect函数失败。
4、socket产生错误。
close有两个限制可由函数shutdown来避免:
close将描述字的访问计数减1,仅在此计数为0时才关闭套接口
shutdown可激发TCP的正常连接终止序列, 而不管访问计数。
close终止了数据传送的两个方向:读和写。
shutdown终止的数据传送的两个方向:读和写, 或其中任一方向:读或写
定义:
int shutdown( int sockfd, int howto) ;
howto选项:
SHUT_RD 关闭连接的读一半
SHUT_WR 关闭连接的写这一半
SHUT_RDWR 关闭连接读读和写
我们来看看一个进程使用select来处理并发的情况:
#include "unp.h" int main(int argc, char **argv) { int i, maxi, maxfd, listenfd, connfd, sockfd; int nready, client[FD_SETSIZE]; ssize_t n; fd_set rset, allset; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); Bind(listenfd, (SA *) &servaddr, sizeof(servaddr)); Listen(listenfd, LISTENQ); maxfd = listenfd; //首先把监听fd加入到select中 maxi = -1; //表示最大的可用接入fd的下标 for (i = 0; i < FD_SETSIZE; i++) client[i] = -1; //接入fd的数组集合,-1表示未使用 FD_ZERO(&allset); FD_SET(listenfd, &allset); //加入监听fd到select中 for ( ; ; ) { rset = allset; nready = Select(maxfd+1, &rset, NULL, NULL, NULL);//注册select,只注册了可读集合,并且非阻塞 if (FD_ISSET(listenfd, &rset)) { //如果监听fd准备好,则新分配一个接入fd clilen = sizeof(cliaddr); connfd = Accept(listenfd, (SA *) &cliaddr, &clilen); #ifdef NOTDEF printf("new client: %s, port %d\n", Inet_ntop(AF_INET, &cliaddr.sin_addr, 4, NULL), ntohs(cliaddr.sin_port)); #endif for (i = 0; i < FD_SETSIZE; i++) if (client[i] < 0) { client[i] = connfd; //加入到接入fd数组中 break; } if (i == FD_SETSIZE)//如果接入fd数组不存在-1的index,则表示已经接入过多的连接 err_quit("too many clients"); FD_SET(connfd, &allset); //把最新的接入fd加入select中 if (connfd > maxfd) maxfd = connfd; //更新max fd if (i > maxi) maxi = i; //更新最大的可用index if (--nready <= 0) continue; //如果nready只是等于1,则表明只有一个链接。 } for (i = 0; i <= maxi; i++) { //查询所有可用的接入fd,处理数据 if ( (sockfd = client[i]) < 0) continue; if (FD_ISSET(sockfd, &rset)) { if ( (n = Read(sockfd, buf, MAXLINE)) == 0) { Close(sockfd); FD_CLR(sockfd, &allset); client[i] = -1; } else Writen(sockfd, buf, n); if (--nready <= 0) break; //如果nready只是等于1,则表明只有一个链接。 } } } }
不过这样的程序会存在Dos攻击,链接阻塞在read,防止dos攻击的方法是使用非阻塞io,或者使用另外的进程和线程处理逻辑,设置超时等。
接下来我们看看poll,定义如下:
#include <poll.h>
int poll (struct pollfd *fdarray, unsigned long nfds, int timeout);
poll函数的第一个参数是指向名为pollfd的结构体指针。pollfd结构体定义如下:
struct pollfd { int fd; /* descriptor to check */ short events; /* events of interest on fd */ short revents; /* events that occurred on fd */ };
pollfd里面,events表示等待的事件,revents表示实际发生的事件。根据结果可以做不同的处理。下表是poll的事件定义:
poll的第二个参数表示fdarray的个数,第三个参数是超时时间,单位是毫秒。
poll的例子与select基本类似,这里就不多说,具体参见unp。
最后说poll比select优秀的地方:
(1)不再有fd个数的上限限制,可以将参数ufds想象成栈低指针,nfds是栈中元素个数,该栈可以无限制增长
(2)引入pollfd结构,将fd信息、需要监控的事件、返回的事件分开保存,则poll返回后不会丢失fd信息和需要监控的事件信息,也就省略了select模型中前面的循环操作,返回后的循环仍然不可避免。另每次poll阻塞操作都会自动把上次的revents清空。
以下是在学习过程中找到的一些好的文章和讨论:
http://bbs3.chinaunix.net/thread-1283223-1-1.html
http://linux.chinaunix.net/techdoc/develop/2008/09/27/1034861.shtml
http://blog.csdn.net/pmunix/archive/2008/01/13/2041512.aspx
http://www.cs.uwaterloo.ca/~brecht/theses/Ostrowski-MMath.pdf