I/O模型

首先我们将查看UNIX下可用的5种I/O模型的基本区别:

1.阻塞式I/O

2.非阻塞式I/O

3.I/O复用(select和poll)

4.信号驱动式I/O(SIGIO)

5.异步I/O(POSIX的aio_系列函数)

阻塞式I/O模型

最流行的I/O模型是阻塞式I/O模型,下面以数据报套接字作为例子,有如下的情形

非阻塞式I/O模型

进程把一个套接字设置成非阻塞式通知内核:当锁请求的I/O操作非得把本进程投入睡眠才能完成时,不要把本进程投入睡眠,而是返回一个错误

前三次调用recvfrom时没有数据可返回,因此内核转而立即返回一个EWOULDBLOCK错误。

I/O复用模型

我们可以调用select和poll,阻塞在这两个系统调用中的某一个之上,而不是在真正的I/O系统调用上

下面小节将详细讲这两个函数的用法

信号驱动式I/O模型

我们也可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。

在信号发生之后,在信号处理函数中调用recvfrom读取数据报。

异步I/O模型

信号驱动式I/O是由内核通知我们何时可以启动一个I/O操作,而异步I/O模型是由内核通知我们I/O操作何时完成。

select函数

之前apue的学习笔记也有详细的用法 http://www.cnblogs.com/runnyu/p/4645754.html

#include <sys/select.h>
int select(int maxfdp1,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,struct timeval *restrict tvptr);

参数tvptr指定愿意等待的时间,有下面3种情况

1.tvptr==NULL 永远等待,直到所指定的描述符中的一个已经准备好或捕捉到一个信号返回。如果捕捉到一个信号,则select返回-1,errno设置为EINTR

2.tvptr->tv_sec==0 && tvptr->tv_usec==0 不等待,测试所有指定的描述符并立即返回

3.tvptr->tv_sec!=0 || tvptr->tv_usec!=0  等待指定的秒数和微秒数。当指定的描述符之一已准备好,或当指定的时间值已经超过时立即返回

中间3个参数readfds、writefds和exceptfds是指向描述符集的指针。这3个描述符集说明了我们关心的可读、可写和处于异常条件的描述符集合。

对于描述符集fd_set结构,提供了下面4个函数

#include <sys/select.h>
int FD_ISSET(int fd,fd_set *fdset);
void FD_CLR(int fd,fd_set *fdset);
void FD_SET(int fd,fd_set *fdset);
void FD_ZERO(fd_set *fdset);

select第一个参数maxfdp1的意思是“最大文件描述符编号值加1”,在上面3个描述符集中找到最大描述符编号值,然后加1就是第一个参数值。

select有3个可能的返回值

1.返回值-1表示出错。如果在所指定的描述符一个都没准备好时捕捉到一个信号,则返回-1

2.返回0表示没有描述符准备好,指定的时间就过了。

3.一个正返回值说明了已经准备好的描述符数,在这种情况下,3个描述符集中依旧打开的位对应于已准备好的描述符。

使用select修订str_cli函数

新版本改为阻塞于select调用,或是等待标准输入可读,或是等待套接字可读。

代码如下

 #include    "unp.h"

 void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1;
fd_set rset;
char sendline[MAXLINE], recvline[MAXLINE]; FD_ZERO(&rset);
for ( ; ; ) {
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + ;
Select(maxfdp1, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if (Readline(sockfd, recvline, MAXLINE) == )
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)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}

批量输入

不幸的是,我们的str_cli函数仍然不正确。考虑下面的情况:

我们在发送数据给服务器的时候,应该等待应答。这段时间是往返时间加上服务器的处理时间。

在批量发送数据的时候,当写完最后一个请求后,我们并不能立即关闭连接,因为在客户端跟服务器之间的管道中还有其他的请求和应答

再看一下我们的str_cli函数:当我们在标准输入键入EOF的时候,str_cli函数就此返回到main函数,随后main函数将终止。

因此标准输入中的EOF并不意味着我们同时也完成了从套接字的读入:可能仍有请求在去往服务器的路上,或者仍有应答在返回客户的路上。

我们需要的是一种关闭TCP连接其中一半的方法。这将由下一节shutdown函数来完成。

shutdown函数

#inlcude <sys/socket.h>
int shutdown(int sockfd,int howto)
//返回:若成功则为0,若出错则为-1

该函数的行为依赖于howto参数的值

SHUT_RD   关闭连接的读这一半。套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。

SHUT_WR  关闭连接的写的这一半。当前留在套接字发送缓冲区中的数据将被发送掉,后跟TCP的正常连接终止序列。

SHUT_RDWR   连接的读半部和写半部都被关闭。

str_cli函数(再修订版)

使用shutdown来允许我们正确地处理批量输入。这个版本也废弃了以文本行为中心的代码,从而消除因为缓冲带来的复杂性。

 #include    "unp.h"

 void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n; stdineof = ;
FD_ZERO(&rset);
for ( ; ; ) {
if (stdineof == )
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + ;
Select(maxfdp1, &rset, NULL, NULL, NULL); if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, buf, MAXLINE)) == ) {
if (stdineof == )
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
} Write(fileno(stdout), buf, n);
} if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = Read(fileno(fp), buf, MAXLINE)) == ) {
stdineof = ;
Shutdown(sockfd, SHUT_WR); /* send FIN */
FD_CLR(fileno(fp), &rset);
continue;
} Writen(sockfd, buf, n);
}
}
}

stdineof是一个初始化为0的新标志:只要该标志位0,每次在主循环中我们总是select标准输入的可读性。

当我们在标准输入上碰到EOF时,我们把新标志stdineof置为1,调用shutdown以发送FIN,在rset中清除标准输入。

当我们在套接字上读到EOF时,如果我们已在标准输入上遇到EOF(stdineof==1),那就是正常的终止,于是函数返回。

TCP回射服务器程序(修订版)

回顾第五章讲解的TCP回射服务器陈谷,把它重写成使用select来处理任意个客户的单进程程序,而不是为每个客户派生一个子进程。

 /* include fig01 */
#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, ); 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; /* initialize */
maxi = -; /* index into client[] array */
for (i = ; i < FD_SETSIZE; i++)
client[i] = -; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
/* end fig01 */ /* include fig02 */
for ( ; ; ) {
rset = allset; /* structure assignment */
nready = Select(maxfd+, &rset, NULL, NULL, NULL); if (FD_ISSET(listenfd, &rset)) { /* new client connection */
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, , NULL),
ntohs(cliaddr.sin_port));
#endif for (i = ; i < FD_SETSIZE; i++)
if (client[i] < ) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE)
err_quit("too many clients"); FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */ if (--nready <= )
continue; /* no more readable descriptors */
} for (i = ; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < )
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == ) {
/*4connection closed by client */
Close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -;
} else
Writen(sockfd, buf, n); if (--nready <= )
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */

利用select来等待某个事件发生:新客户连接的建立,数据、FIN或RST的到达。服务器将维护client数组来标识accept返回的套接字描述符。

pselect函数

POSIX.1也定义了一个select的变体,称为pselect

#include <sys/select.h>
int pselect(int maxfdp1,fd_set *restrict readfds,fd_set *restrict writefds,fd_set *restrict exceptfds,const struct timespec *restrict tsptr,const sigset_t *restrict sigmask);

除了下列几点外,pselect与select相同

1.超时值使用的结构不一样,pselect使用的是timespec结构

2.pselect的超时值被声明为const,保证了调用pselect不会改变此值

3.pselect可使用可选信号屏蔽字,在调用pselect时,以原子操作的方式安装该信号屏蔽字。在返回时,恢复以前的信号屏蔽字。

poll函数

#include <poll.h>
int poll(struct pollfd fdarray[],nfds_t nfds,int timeout);

与select不同,poll构造一个pollfd结构的数组,每个数组元素指定一个描述符编号以及我们对描述符感兴趣的条件。

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

fdarray数组中的元素数由nfds指定

要测试的条件由events成员指定,函数在相应的revents成员中返回该描述符的状态。

结构中的events/revents成员应设置为下图所示值的一个或几个

timeout参数指定poll函数返回前等待多长时间。它是一个指定应等待毫秒值的正值

TCP回射服务器程序(再修订版)

我们使用poll代替select重写上面的TCP回射服务器程序。改用poll后,我们只需分配一个pollfd结构的数组来维护客户信息,而不必分配另一个数组。

 /* include fig01 */
#include "unp.h"
#include <limits.h> /* for OPEN_MAX */ int
main(int argc, char **argv)
{
int i, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE];
socklen_t clilen;
struct pollfd client[OPEN_MAX];
struct sockaddr_in cliaddr, servaddr; listenfd = Socket(AF_INET, SOCK_STREAM, ); 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); client[].fd = listenfd;
client[].events = POLLRDNORM;
for (i = ; i < OPEN_MAX; i++)
client[i].fd = -; /* -1 indicates available entry */
maxi = ; /* max index into client[] array */
/* end fig01 */ /* include fig02 */
for ( ; ; ) {
nready = Poll(client, maxi+, INFTIM); if (client[].revents & POLLRDNORM) { /* new client connection */
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
#ifdef NOTDEF
printf("new client: %s\n", Sock_ntop((SA *) &cliaddr, clilen));
#endif for (i = ; i < OPEN_MAX; i++)
if (client[i].fd < ) {
client[i].fd = connfd; /* save descriptor */
break;
}
if (i == OPEN_MAX)
err_quit("too many clients"); client[i].events = POLLRDNORM;
if (i > maxi)
maxi = i; /* max index in client[] array */ if (--nready <= )
continue; /* no more readable descriptors */
} for (i = ; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i].fd) < )
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) {
if ( (n = read(sockfd, buf, MAXLINE)) < ) {
if (errno == ECONNRESET) {
/*4connection reset by client */
#ifdef NOTDEF
printf("client[%d] aborted connection\n", i);
#endif
Close(sockfd);
client[i].fd = -;
} else
err_sys("read error");
} else if (n == ) {
/*4connection closed by client */
#ifdef NOTDEF
printf("client[%d] closed connection\n", i);
#endif
Close(sockfd);
client[i].fd = -;
} else
Writen(sockfd, buf, n); if (--nready <= )
break; /* no more readable descriptors */
}
}
}
}
/* end fig02 */

UNP学习笔记(第六章 I/O复用)的更多相关文章

  1. JVM学习笔记-第六章-类文件结构

    JVM学习笔记-第六章-类文件结构 6.3 Class类文件的结构 本章中,笔者只是通俗地将任意一个有效的类或接口锁应当满足的格式称为"Class文件格式",实际上它完全不需要以磁 ...

  2. C Primer Plus 学习笔记 -- 前六章

    记录自己学习C Primer Plus的学习笔记 第一章 C语言高效在于C语言通常是汇编语言才具有的微调控能力设计的一系列内部指令 C不是面向对象编程 编译器把源代码转化成中间代码,链接器把中间代码和 ...

  3. Linux学习笔记(第六章)

    第六章-档案权限与目录配置#chgrp:改变档案的所属群组#chown:改变档案的拥有者#chmod:改变档案的权限及属性 chown用法 chmod用法: r:4 w:2 x:1对于文档: 对于目录 ...

  4. o'Reill的SVG精髓(第二版)学习笔记——第六章

    第六章:坐标系统变换 想要旋转.缩放或者移动图片到新的位置.可以给对应的SVG元素添加transform属性. 6.1 translate变换 可以为<use>元素使用x和y属性,以在特性 ...

  5. 学习笔记 第六章 使用CSS美化图片

    第六章  使用CSS美化图片 6.1  在网页中插入图片 GIF图像 跨平台能力,无兼容性问题: 具有减少颜色显示数目而极度压缩文件的能力,不会降低图像的品质(无损压缩): 支持背景透明功能,便于图像 ...

  6. Java 学习笔记 ------第六章 继承与多态

    本章学习目标: 了解继承的目的 了解继承与多态的关系 知道如何重新定义方法 认识java.lang.object 简介垃圾回收机制 一.继承 继承是java面向对象编程技术的一块基石,因为它允许创建分 ...

  7. UNP学习笔记(第二章:传输层)

    本章的焦点是传输层,包括TCP.UDP和SCTP. 绝大多数客户/服务器网络应用使用TCP或UDP.SCTP是一个较新的协议. UDP是一个简单的.不可靠的数据报协议.而TCP是一个复杂.可靠的字节流 ...

  8. 《Python基础教程(第二版)》学习笔记 -> 第六章 抽象

    抽象和结构 本章将会介绍如何让将语句组织成函数,还会详细介绍参数(parameter)和作用域(scope)的概念,以及递归的概念及其在程序中的用途. 创建函数 函数可以调用,它执行某种行为,并返回某 ...

  9. [Python学习笔记][第六章Python面向对象程序设计]

    1月29日学习内容 Python面向对象程序设计 类的定义与使用 类定义语法 使用class关键词 class Car: def infor(self): print("This is ca ...

  10. UNP学习笔记(第一章 简介)

    环境搭建 1.下载解压unpv13e.tar.gz 2.进入目录执行 ./configurecd lib //进入lib目录make //执行make命令 3.将生成的libunp.a静态库复制到/u ...

随机推荐

  1. 挑战程序竞赛例题 4.1 Random Walk(高斯消元求期望值)

    给你一幅N*M的地图,地图中有不能到达的障碍物'#'与可以走的点'.',从(1,1)开始走到(N,M),其中每一次走动均等概率地向周围的可达的格子走去,求到达(N,M)的期望步数.(N,M<=1 ...

  2. [bzoj3944] sum [杜教筛模板]

    题面: 传送门 就是让你求$ \varphi\left(i\right) $以及$ \mu\left(i\right) $的前缀和 思路: 就是杜教筛的模板 我们把套路公式拿出来: $ g\left( ...

  3. FZU 2041 二分枚举

    思路:先O(n)预处理出ri[i][j],le[i][j],分别表示第i个位置向右边移动出j个空格需要的步数,表示第i个位置向左边移动出j个空格需要的步数. 然后枚举间隙处,二分判段最大间隔. #in ...

  4. react自定义组件属性类型检测

    react当中的props-type用来检测传入组件当中的数据是否符合组件的要求,但是之前的只是能做些简单常规的判断,如果需要做复杂的判断,就需要使用到自定义函数来做类型检测了. 下面是官网的例子 c ...

  5. 【BZOJ4402】Claris的剑(组合计数)

    题意: 给定数列的定义: 1.每个元素都是正整数 2.每个元素不能超过M 3.相邻两个元素的差的绝对值必须是1 4.第一个元素的值必须是1 求有多少个长度不超过N的合法的本质不同的序列 两个序列本质不 ...

  6. duilib入门简明教程 -- 前言(1) (转)

    原文转自 :http://www.cnblogs.com/Alberl/p/3341956.html     关于duilib的介绍就不多讲了,一来不熟,二来小伙伴们想必已经对比了多个界面库,也无需赘 ...

  7. 《手把手教你学C语言》学习笔记(1)---C语言的特点

    学习C语言的原因,主要是需要使用C语言编程,我用故我学,应该是最主要的原因了. C语言的定位:C语言严格意义上只能算是中级语言,是面向过程编程语言的集大成者,虽然这种语言有很多的问题,但总体而言是瑕不 ...

  8. linux内核情景分析之信号实现

    信号在进程间通信是异步的,每个进程的task_struct结构有一个sig指针,指向一个signal_struct结构 定义如下 struct signal_struct { atomic_t cou ...

  9. 修改手機的 input source 及 charger 及 usb 相關電路後,容易忽略的事項

    input source 及 charger 需要注意, 是否可以在關機的狀況下充電, 當然 開機充電 是一定要的. usb 部分需要注意, 是否可以在沒有電或者是有電的狀況下 download 程式 ...

  10. 网络编程socket-SocketServer-FTP

    16章 网络编程?应该是讲网络之间如何编程来进行通信的章节 16.1.1 客户端/服务器架构?客户端请求访问,服务器端监听请求,处理请求,进行相应的模式16.1.2 客户端/服务器编程?服务器端:创建 ...