《6 IO复用与套接字选项(10,11)new》由会员分享,可在线阅读,更多相关《6 IO复用与套接字选项(10,11)new(40页珍藏版)》请在金锄头文库上搜索。
1、目录lI/O模型lI/O复用概述lSelect函数l 套接字选项和fcntll 非阻塞I/Ol 信号驱动I/O模型I/O模型llinux下可用的i/o模型l阻塞i/ol非阻塞i/oli/o复用(select和poll)l信号驱动i/o(SIGIO)l异步i/o(posix.1的aio_系列函数)l 举例:输入操作的不同i/o模型l一个输入操作一般有两个阶段l等待数据准备好l从内核到进程拷贝数据阻塞I/O模型l最流行的i/o模型。缺省时,所有套接字都是阻塞的。非阻塞I/O模型l当把一个套接字设置成非阻塞方式时,即通知内核:当请求的i/o操作不能马上完成时,不要阻塞进程,而应返回一个错误。l上述方
2、式循环调用recvfrom,称为轮询,极为浪费cpu资源I/O复用模型li/o复用模型调用select或poll,进程阻塞于这两个系统调用上,而不是阻塞于真正的i/o系统调用上。l注意:与阻塞i/o模型相比,由于使用了系统调用select,似乎比阻塞i/o还差。但select的好处在于可以等待多个描述字准备好。信号驱动I/O模型l让内核在描述字准备好时用信号SIGIO通知进程。这种模型的好处是当等待数据报到达时,可以不阻塞。前提是允许套接口进行信号驱动io 。l可以由信号处理程序来读取数据,或通知主循环来读取数据.l在信号处理程序中调用recvfrom函数异步I/O模型l异步i/o让内核启动操
3、作,并在整个操作完成后(包括将数据从内核拷贝到应用进程的缓冲)通知我们。五个I/O模型的比较fcntl函数lfcntl()是标准系统调用,原意是用于对文件描述字进行各种控制操作,但后来可利用它对任何描述字进行控制操作,包括对套接字描述字。#include int fcntl(int fd, int cmd, /* int arg */);l 每个描述字都有一组由命令F_GETFL取得和由命令F_SETFL设置的文件标志。fcntl函数(续)l函数fcntl提供了以下关于网络编程的特性:l非阻塞i/o。我们可以通过F_SETFL命令设置O_NONBLOCK来设置套接字为非阻塞型;fcntl函数(
4、续)l 用fcntl来使能非阻塞i/o的典型代码:int flags;if (flags = fcntl(fd, F_GETFL, 0) 0)err_sys(“”);flags |= O_NONBLOCK;/保留原始状态if (flags = fcntl(fd, F_SETFL, flags) 0)err_sys(“”);l 关闭非阻塞标志方法:flags &= O_NONBLOCK; /恢复为原始状态if (fcntl(fd, F_SETFL, flags) 0)err_sys(“”);非阻塞I/Ol缺省情况下,套接字是阻塞方式的。可能阻塞的套接字调用分成以下四类:l输入操作:read, r
5、eadv, recv, recvfrom和recvmsg函数。如果在一个阻塞的tcp套接字上调用这些函数,而且在套接字接收缓冲区中没有数据(或者没有满足条件的数据),进程将在数据到来前睡眠。到来的数据可以是一个字节,也可以是一个tcp分节。阻塞udp套接字将在一个udp数据报到来之前一直处于睡眠状态(在没有设置接收超时的前提下)。(但,在一个非阻塞套接字上,如果输入操作不能被满足,在一个非阻塞套接字上,如果输入操作不能被满足,它们会立即返回一个它们会立即返回一个EWOULDBLOCK错误错误)。非阻塞I/O(续)l输出操作:write, writev, send, sendto和sendmsg
6、函数。当tcp阻塞套接字从应用进程缓冲区向套接字发送缓冲区拷贝数据,如果发送缓冲区没有空间,进程会一直睡眠到腾出空间(没有设置超时)。对于一个非阻塞套接字而言,上述情况会立即返回一个EWOULDBLOCK错误。由于udp套接字没有发送缓冲区,内核只是拷贝应用进程数据并将其向协议栈下层传递。因此在一个阻塞udp套接字上的输出操作并不会阻塞。l接收外来的连接:accept函数。如果在一个阻塞套接字上调用accept函数,而且没有新的连接,进程会进入睡眠状态(没有设置超时)。如果在一个非阻塞套接字上调用accept,而且没有新的连接,将返回EWOULDBLOCK错误。非阻塞I/O(续)l初始化外出的
7、连接:用于tcp的connect函数(udp尽管也可以connect,但不是建立一个真正的连接,而仅仅是让内核保存对方的IP和端口号),tcp的连接建立总会使调用进程阻塞起码到服务器的一次往返时间RTT。如果在一个非阻塞的tcp套接字上调用connect,而且连接不能马上建立,连接的建立过程将开始,但返回一个EINPROGRESS错误。I/O复用概述l问题:并发服务器为多个客户服务:l避免服务器的服务被阻塞,采用并发服务l并发服务器:创建多个描述字,多进程或线程提供服务,这种方法会增加开销。l为每个连接套接字提供一个单独的服务实体(进城或线程),有没有一种没有过多开销的方式呢?l一个服务实体,
8、但能同时为多个连接提供服务?I/O复用的应用场合l多个相同描述字或者多个不同的描述字等待I/O操作:l当客户处理多个描述字(一般是交互式输入和网络套接字),使用i/o复用;l 如果一个tcp服务器程序既要处理监听套接字,又要处理连接套接字,一般也可以用i/o复用;(当然可以使用并发技术)l 如果一个服务器程序既要处理tcp,又要处理udp,一般也要使用i/o复用;l 如果一个服务器程序要处理多个服务或者多个协议,一般要使用i/o复用;I/O复用l原理分析图I/O复用的基本思想l先构造一张或多张包含所有需要等待的描述符的表(区分不同的连接操作,或者文件操作)l然后调用一个阻塞函数,它要到这些描述
9、符中的一个或多个已准备好进行I/O时才返回l准备好:l网络数据已经到了缓冲区l文件数据已经读到缓冲区l在返回时,它告诉进程哪一个描述符已准备好可以进行I/O。select函数#include #include int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);返回:准备好描述字的数目,0超时,-1出错。参数timeout指定内核等待的时间,其结构如下:struct timeval long tv_sec; /* seconds */
10、long tv_usec;/* microseconds */typedef struct _fd_mask fds_bits_FD_SETSIZE / _NFDBITS; / 整型 fd_set; select函数(续)l原理分析图select函数(续)l该函数有三种执行结果:l永远等待下去:仅在有一个或以上描述字准备好i/o才返回,为此,我们将timeout设置为空指针。l等待固定时间:在有一个描述字准备好时返回,但不超过由timeout参数指定的秒数和微秒数。l根本不等待,检查描述字后立即返回,这称为轮询。这种情况下,timeout必须指向结构timeval,且定时器的值必须为0。l 在
11、前两种情况的等待中,如果进程捕获了一个信号并从信号处理程序返回,那么等待一般被中断。select函数(续)l结构timeval指定了秒数和微秒数。但内核支持的分辨率却要粗糙得多,因此,定时并不精确。另外,如果select的三个测试指针为空,将提供一个比函数sleep更为精确的定时器(sleep睡眠以秒为最小单位)。l select的三个描述字集合分别指示不同测试类型的描述字集合(读、写、异常描述字)l其中异常描述字支持:套接字上带外数据的到达和控制状态信息的存在。select函数(续)l对描述字集合(32位整数)的操作主要有以下四个宏:Void FD_ZERO(fd_set *fdset);
12、/* 清除 fdset各位 */Void FD_SET(int fd, fd_set *fdset); /* 把fdset中fd对应位置1 */Void FD_CLR(int fd, fd_set *fdset); /*把fdset中fd对应位置0 */Void FD_ISSET(int fd, fd_set *fdset); /* 判断fdset中fd位 */l 使用select函数需重点注意的几个问题:l需对最大描述字加1,如:需等待的描述字为1,4,5,其maxfd就应该是6。l对集合初始化是很重要的,如果集合作为一个自动变量分配而未初始化,那将导致不可预测的后果。每次调用select前
13、都必须对等待描述字集合完成初始化和设置工作。select函数(续)l套接字可读的条件主要有:l套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低潮限度的当前值;l如果对方tcp发送数据,套接字就变为可读且read返回大于0的值;l如果对方tcp发送一个FIN(对方进程终止),套接字就变为可读且read返回0;l如果tcp发送一个RST(对方主机崩溃并重新启动),套接字就变为可读且read返回-1。select函数(续)l套接字可写的条件主要有:l套接字发送缓冲区的可用空间大于等于套接字发送缓冲区的低潮限度;l套接字的写这一半关闭,对套接字的写将产生SIGPIPE信号;l有一个套接字错误待
14、处理 Select举例l原理Select阻塞读文件读套接字Select举例客户程序Int main(int argc, char *argv)intsockfd;struct sockaddr_inservaddr;if (argc != 2)err_quit(usage: tcpcli );sockfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr);servaddr.sin_family = AF_INET;servaddr.sin_port = htons(SERV_PORT);Inet_pton(A
15、F_INET, argv1, &servaddr.sin_addr);Connect(sockfd, (SA *) &servaddr, sizeof(servaddr);str_cli(stdin, sockfd);/* do it all */exit(0);Select举例客户程序(续)Void str_cli(FILE *fp, int sockfd)intmaxfdp1, stdineof;fd_setrset;charsendlineMAXLINE, recvlineMAXLINE;stdineof = 0;FD_ZERO(&rset);for ( ; ; ) if (stdine
16、of = 0)/判断标准输入是否关闭FD_SET(fileno(fp), &rset);/添加文件标识符FD_SET(sockfd, &rset);/添加套接字标识符maxfdp1 = max(fileno(fp), sockfd) + 1;/ 等待文件和套接字之一可读Select (maxfdp1, &rset, NULL, NULL, NULL);Select举例客户程序(续)if ( FD_ISSET(sockfd, &rset) ) / 套接字可读/ 收到服务器发来的终结符?if (Readline(sockfd, recvline, MAXLINE) = 0) / 标准输入端已经关闭
17、?if (stdineof = 1)return;/* normal termination */elseerr_quit(str_cli: server terminated );Fputs(recvline, stdout);Select举例客户程序(续)if ( FD_ISSET(fileno(fp), &rset) ) / 标准输入可读/标准输入收到终结符?if (Fgets(sendline, MAXLINE, fp) = NULL) stdineof = 1;Shutdown(sockfd, SHUT_WR);/* send FIN */如果收到终结符,不再需要标准输入端FD_CL
18、R(fileno(fp), &rset);continue;Writen(sockfd, sendline, strlen(sendline);Select举例服务器程序服务器程序标准输入、输出和错误监听已连接的客户套接字Select举例服务器程序服务器程序建立了一个客户连接Select举例服务器程序服务器程序建立了两个客户连接Select举例服务器程序服务器程序第一个客户连接结束Select举例服务器程序Int main(int argc, char *argv)inti, maxi, maxfd, listenfd, connfd, sockfd;intnready, clientFD_S
19、ETSIZE;ssize_tn;fd_setrset, allset;charlineMAXLINE;socklen_tclilen;struct sockaddr_incliaddr, 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);Select举例
20、服务器程序(续)Bind(listenfd, (SA *) &servaddr, sizeof(servaddr);Listen(listenfd, LISTENQ);maxfd = listenfd;/* initialize */maxi = -1;/* index into client array */for (i = 0; i FD_SETSIZE; i+)clienti = -1;/* -1 indicates available entry */FD_ZERO(&allset);FD_SET(listenfd, &allset); /最开始,只判断监听套接字最开始,只判断监听套接
21、字描述符描述符Select举例服务器程序(续)for ( ; ; ) rset = allset;/* structure assignment */ nready记录可读描述符个数nready = Select(maxfd+1, &rset, NULL, NULL, NULL);if ( FD_ISSET(listenfd, &rset) ) /* new client connection */clilen = sizeof(cliaddr);connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);for (i = 0; i FD_SETSI
22、ZE; i+)if (clienti maxfd)maxfd = connfd;/* for select */if (i maxi)maxi = i;/* max index in client array */if (-nready = 0)continue;/* 不存在其它可读描述符 */Select举例服务器程序(续)for (i = 0; i = maxi; i+) /* check all clients for data */ 测试第i个客服连接的描述符if ( (sockfd = clienti) 0)continue;if (FD_ISSET(sockfd, &rset) if ( (n = Readline(sockfd, line, MAXLINE) = 0) Close(sockfd);FD_CLR(sockfd, &allset);clienti = -1; elseWriten(sockfd, line, n);if (-nready = 0)break;/* no more readable descriptors */