《网络编程》I/O 多路复用
来源:互联网 发布:sql创建默认值 编辑:程序博客网 时间:2024/06/13 23:00
在前面的文章中介绍了五种 I/O 模型《I/O 模型》,这里介绍 I/O 模型中 I/O 多路复用在 TCP 套接字编程中的使用。在 I/O 多路复用中主要是 select 和 poll 函数的使用。
select 函数
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在一个或多个事件发生或超过指定时间后才被唤醒。进程调用 select 函数是告知内核,进程对哪些描述符(读、写或异常)感兴趣以及等待的时间。
/* IO多路复用 *//* * 函数功能: * 返回值:准备就绪的描述符数,若超时则返回0,出错则返回-1; * 函数原型: */#include <sys/select.h>int select(int maxfdpl, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *tvptr);/* * 说明: * 参数maxfdpl是“最大描述符加1”,即指定待测试的描述符个数; * 参数readfds、writefds、exceptfds是指向描述符集的指针,即让内核测试读、写或异常条件的描述符; * 时间参数有三种取值: * tvptr == NULL; * 永远等待;若捕获到信号则中断此无限期等待;当所指定的描述符中的一个已准备好或捕获到信号则返回; * 若捕获到信号,则select返回-1,errno设置为EINTR; * * tvptr->tv_sec == 0 && tvptr->tv_usec == 0; * 完全不等待;测试所有描述符并立即返回,这是得到多个描述符的状态而不阻塞select函数的轮回方法; * * tvptr->sec != 0 || tvptr->usec != 0; * 等待指定的秒数和微妙数;当指定的描述符已准备好,或超过指定的时间立即返回; * 若超过指定的时间还没有描述符准备好,则返回0; * * tvptr的结构如下: */struct timeval{ long tv_sec; /* seconds */ long tv_usec; /* and microseconds */};
我们可以通过以下函数对 fd_set 数据结构进行处理。声明了一个描述符集后,必须使用 FD_ZERO 清空其所有位达到初始化,然后才可以设置各个位;从 select 返回时,使用 FD_ISSET 测试该集中的一个给定位是否仍旧设置;
#include <sys/select.h>int FD_ISSET(int fd, fd_set *fdset); //测试描述符fd是否在描述符集中设置;若fd在描述符集中则返回非0值,否则返回0void FD_CLR(int fd, fd_set *fdset); //清除在fdset中指定的位fd;void FD_SET(int fd, fd_set *fdset); //设置fd在fdset中指定的位;void FD_ZERO(fd_set *fdset); //清除整个fdset;即所有描述符位都为0;
select 函数有三个可能的返回值:
- 返回值 -1 表示出错。这种情况下,将不修改其中任何描述符集。
- 返回值 0 表示没有描述符准备就绪。若指定的描述符都没有准备就绪,而且指定的时间已经超过,则发生这种情况。此时描述符集都被清 0。
- 正返回值表示已经准备就绪的描述符数,该值是三个描述符集中已准备好的描述符之和。三个描述符集中仍旧打开的位对应与已准备就绪的描述符。
描述符就绪条件
套接字描述符准备好读,必须满足以下条件之一:
- 该套接字接收缓冲区中的数据字节数 不小于 套接字接收缓冲区低水位标记的当前大小。对这样的套接字执行读操作不会阻塞并将返回一个大于 0 的值。可以通过 SO_RCVLOWAT 套接字选项设置该套接字的低水位标记;
- 该连接的读半部分关闭(即一端已接收到 FIN 的 TCP 连接)。对这样的套接字的读操作将不阻塞并返回 0(即 EOF 字符);
- 该套接字是一个监听套接字且已完成的连接数不为 0 。对这样的套接字的 accept 通常不会阻塞;
- 其上有一个套接字错误待处理。对这样的套接字的读操作将不阻塞并返回 -1(即返回一个错误),并把 errno 设置为确切的错误条件;
套接字描述符准备好写,必须满足以下条件之一:
- 该套接字发送缓冲区中的可用空间字节数 不小于 套接字发送缓冲区低水位标记的当前大小,并且或该套接字已连接,或该套接字不需要连接(如 UDP 套接字)。这意味着若把这样的套接字设置为非阻塞,写操作将不阻塞并返回一个正值;
- 该链接的写半部关闭。对这样的套接字的写操作将产生 SIGPIPE 信号;
- 使用非阻塞式 connect 的套接字已建立连接,或connect 已经连接失败;
- 其上有一个套接字错误待处理。对这样的套接字的写操作将不阻塞并返回 -1,同时把 errno 设置成确切的错误条件;
若一个套接字存在带外数据 或仍处于 带外标记,那么它有异常条件待处理。注:当某个套接字发生错误时,它将由 select 标记为既可读又可写。
在前面《基于 TCP 套接字编程的分析》我们知道,当杀死服务器子进程时,同时客户端阻塞于 fgets 调用。客户端 TCP 已经接收到来自服务器子进程的 FIN 报文段,当服务器 TCP 接收到来自客户端的数据时,因为先前打开的套接字的进程已经通过 kill 函数终止,于是响应一个 RST 。然而客户端阻塞于从标准输入读入过程,并没有收到 RST,因此 readline 返回 0(表示 EOF ),则客户端此时并未预期收到 EOF ,则以出错信息”server terminated prematurely“退出。若要避免这种情况,可以使用 I/O 多路复用解决。则使用 select 函数修改前面文章的客户端处理函数 str_cli 函数。这样服务器进程一终止,客户端立即得到通知。
客户端处理函数修改后具有以下的功能:
- 若对端 TCP 发送数据,那么套接字变为可读,并且 read 返回一个大于 0 的值;
- 若对端 TCP 发送一个 FIN ,那么套接字变为可读,并且 read 返回 0(即 EOF 字符);
- 若对端 TCP 发送一个 RST,那么套接字变为可读,并且 read 返回 -1,而 errno 中含有确切的错误代码;
#include"unp.h"voidstr_cli(FILE *fp, int sockfd){intmaxfdp1;fd_setrset;charsendline[MAXLINE], recvline[MAXLINE];FD_ZERO(&rset);/* 初始化 */for ( ; ; ) {FD_SET(fileno(fp), &rset);/* 打开标准文件指针 fp 描述符位 */FD_SET(sockfd, &rset);/* 打开套接字 sockfd 位 */maxfdp1 = max(fileno(fp), sockfd) + 1;Select(maxfdp1, &rset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &rset)) {/* socket is readable */if (Readline(sockfd, recvline, MAXLINE) == 0) /* 若返回时套接字可读,则先读入从服务器回射的文本,并显示到标准输出 */err_quit("str_cli: server terminated prematurely");Fputs(recvline, stdout);}if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */if (Fgets(sendline, MAXLINE, fp) == NULL) /* 若返回时标准输入可读,就先用 fgets 读入文本行,再用 writen 把它写到套接字中 */return;/* all done */Writen(sockfd, sendline, strlen(sendline));}}}
shutdown 函数
终止网络连接通常使用 close 函数,但是 close 有自己的缺陷,我们可以使用 shutdown 函数来避免这些缺陷:
- close 把描述符的引用计数减 1,仅在该计数变为 0 时才关闭套接字。然而使用 shutdown 函数可以不管引用计数就激发 TCP 的正常连接终止序列;
- close 终止读和写两个方向的数据传送,而 shutdown 函数可以只关闭读或写的一端,或两端都关闭;
/* * 函数功能:关闭套接字上的输入或输出; * 返回值:若成功则返回0,若出错返回-1; * 函数原型: */#include <sys/socket.h>int shutdown(int sockfd, int how);/* * 说明: * sockfd表示待操作的套接字描述符; * how表示具体操作,取值如下: * (1)SHUT_RD 关闭读端,即不能接收数据 * (2)SHUT_WR 关闭写端,即不能发送数据 * (3)SHUT_RDWR 关闭读、写端,即不能发送和接收数据 * */
str_cli 函数
这是最终的版本:
#include"unp.h"voidstr_cli(FILE *fp, int sockfd){intmaxfdp1, stdineof;fd_setrset;charbuf[MAXLINE];intn;stdineof = 0;/* 表示在主循环中 select 标准输入为可读 */FD_ZERO(&rset);for ( ; ; ) {if (stdineof == 0)FD_SET(fileno(fp), &rset);FD_SET(sockfd, &rset);maxfdp1 = max(fileno(fp), sockfd) + 1;Select(maxfdp1, &rset, NULL, NULL, NULL);<span style="white-space:pre"></span>/* 当在套接字上读到 EOF 字符,若我们已在标准输入键入 EOF,则正常终止,若没有在标准输入键入 EOF,表示服务器已过早终止 */if (FD_ISSET(sockfd, &rset)) {/* socket is readable */if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {if (stdineof == 1)return;/* normal termination */elseerr_quit("str_cli: server terminated prematurely");}Write(fileno(stdout), buf, n);}<span style="white-space:pre"></span>/* 当在标准输入遇到 EOF,则关闭写端,即客户端不能向服务器发送数据 */if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {stdineof = 1;Shutdown(sockfd, SHUT_WR);/* send FIN */FD_CLR(fileno(fp), &rset);continue;}Writen(sockfd, buf, n);}}}
poll 函数
该函数与 select 函数类似,只是程序员接口不同。该函数不是为每个状态构造描述符集,而是构造一个 pollfd 结构数组,每个数组元素指定一个描述符编号以及对其所关心的状态。
/* * 函数功能:和select函数类似; * 函数原型: */#include <poll.h>int poll(struct pollfd fdarray[], nfds_t nfds, int timeout);/* * 说明: * timeout == -1; 永远等待。 * timeout == 0; 不等待,测试所有的描述符并立即返回。 * timeout > 0; 等待timeout毫秒,当指定的描述符之一已经准备好,或指定的时间值已经超过时立即返回。 */
struct pollfd{int fd; /* file descriptor to check,or <0 to ignore */short events; /* events of interest on fd */short revents; /* events that occurred on fd */};
参考资料:
《Unix 网络编程》
2 0
- 《网络编程》I/O 多路复用
- Linux 网络编程中的I/O多路复用
- 网络编程(3)学习多路复用(I/O)函数select
- 网络编程(4)select函数实现I/O多路复用服务器
- Linux网络编程---I/O多路复用 之 select
- Linux网络编程---I/O多路复用之epoll
- 嵌入式linux网络编程之I/O多路复用select
- Linux网络编程——I/O多路复用
- I/O多路复用服务器编程
- Epoll编程-I/O多路复用
- I/O多路复用服务器编程
- epoll编程-I/O多路复用
- [Linux C编程]I/O多路复用
- Linux系统文件I/O编程(三)---I/O多路复用
- Linux系统文件I/O编程(三)---I/O多路复用
- linux网络编程学习笔记之六 -----I/O多路复用服务端
- Python案例-网络编程-I/O多路复用-select方法用例
- Linux IPC之Socket网络编程I/O多路复用相关模型及区别
- 黑马程序员——面向对象的总结
- NSMutableAttributedString 的使用方法,设置格式
- 基于CentOS-6.3-x86_64-mini自定义镜像
- 耶稣有13个门徒,其中有一个就是出卖耶稣的叛徒,请用排除法找出这位叛徒:13人围坐一圈,从第一个开始报号:1,2,3,1,2,3……,凡是报到“3”就退出圈子,最后留在圈内的人就是出卖耶稣的叛徒,请找
- 安卓面试必问二为什么会有消息机制
- 《网络编程》I/O 多路复用
- 通过判断ie的版本动态的加载js等其他文件,来解决不兼容问题
- servletcontext 的详细介绍
- Maven 使用指南(3): 简述Maven工作过程
- ceph存储 ceph中restful设计原理
- Xcode Build Settings中的Architecture概述
- getHibernateTemplate().execute(new HibernateCallback())方法
- SGU a^b-b^a
- php面向对象_get(),_set()的用法