UNP学习笔记(第六章 I/O复用)
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复用)的更多相关文章
- JVM学习笔记-第六章-类文件结构
JVM学习笔记-第六章-类文件结构 6.3 Class类文件的结构 本章中,笔者只是通俗地将任意一个有效的类或接口锁应当满足的格式称为"Class文件格式",实际上它完全不需要以磁 ...
- C Primer Plus 学习笔记 -- 前六章
记录自己学习C Primer Plus的学习笔记 第一章 C语言高效在于C语言通常是汇编语言才具有的微调控能力设计的一系列内部指令 C不是面向对象编程 编译器把源代码转化成中间代码,链接器把中间代码和 ...
- Linux学习笔记(第六章)
第六章-档案权限与目录配置#chgrp:改变档案的所属群组#chown:改变档案的拥有者#chmod:改变档案的权限及属性 chown用法 chmod用法: r:4 w:2 x:1对于文档: 对于目录 ...
- o'Reill的SVG精髓(第二版)学习笔记——第六章
第六章:坐标系统变换 想要旋转.缩放或者移动图片到新的位置.可以给对应的SVG元素添加transform属性. 6.1 translate变换 可以为<use>元素使用x和y属性,以在特性 ...
- 学习笔记 第六章 使用CSS美化图片
第六章 使用CSS美化图片 6.1 在网页中插入图片 GIF图像 跨平台能力,无兼容性问题: 具有减少颜色显示数目而极度压缩文件的能力,不会降低图像的品质(无损压缩): 支持背景透明功能,便于图像 ...
- Java 学习笔记 ------第六章 继承与多态
本章学习目标: 了解继承的目的 了解继承与多态的关系 知道如何重新定义方法 认识java.lang.object 简介垃圾回收机制 一.继承 继承是java面向对象编程技术的一块基石,因为它允许创建分 ...
- UNP学习笔记(第二章:传输层)
本章的焦点是传输层,包括TCP.UDP和SCTP. 绝大多数客户/服务器网络应用使用TCP或UDP.SCTP是一个较新的协议. UDP是一个简单的.不可靠的数据报协议.而TCP是一个复杂.可靠的字节流 ...
- 《Python基础教程(第二版)》学习笔记 -> 第六章 抽象
抽象和结构 本章将会介绍如何让将语句组织成函数,还会详细介绍参数(parameter)和作用域(scope)的概念,以及递归的概念及其在程序中的用途. 创建函数 函数可以调用,它执行某种行为,并返回某 ...
- [Python学习笔记][第六章Python面向对象程序设计]
1月29日学习内容 Python面向对象程序设计 类的定义与使用 类定义语法 使用class关键词 class Car: def infor(self): print("This is ca ...
- UNP学习笔记(第一章 简介)
环境搭建 1.下载解压unpv13e.tar.gz 2.进入目录执行 ./configurecd lib //进入lib目录make //执行make命令 3.将生成的libunp.a静态库复制到/u ...
随机推荐
- 免费tk域名+freewebhostingarea空间
1.申请免费域名 进入http://www.dot.tk(推荐注册tk域名),申请一个新的域名,每次申请12个月以下是免费的,到期前14天可以免费续期 在此页面执行下一步之前,需要进行设置DNS服务器 ...
- spring rest 请求怎样添加Basic Auth请求頭
请自行揣摩代码 package com.hudai.platform.manager.util; import java.net.URI; import java.net.URISyntaxExcep ...
- [NOIP2013] 提高组 洛谷P1979 华容道
题目描述 [问题描述] 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间. 小 ...
- 【CF1068A】Birthday(签到)
题意:有N种棋子,M个人,已有K种收藏,要求最小的每个人送的棋子数使得最坏情况下至少有L种新的收藏,无解输出-1 N,M,K,L<=1e18 思路: #include<cstdio> ...
- js 字符串长度截取
<script> function cutstr(str, len) { var temp, icount = 0, patrn = /[^\x00-\xff]/, strre = &qu ...
- linux下的用户组管理
用户组管理分两类 如果赋给组什么权限,那么组员就会有什么权限 1.私有组 在创建新用户的时候,就要为用户指定组.如果没有指定,就会默认创建一个与用户名相同的用户组,这个用户组只有该用户一个用户,就被称 ...
- 基址重定位表&.reloc节区
第16-17章 - 基址重定位表&.reloc节区 @date: 2016/11/31 @author: dlive 0x01 PE重定位 若加载的是DLL.SYS文件,且在ImageBase ...
- 【Visual Studio】简单内存泄漏检测方法 解决 Detected memory leaks! 问题(转)
原文转自 http://blog.csdn.net/u011430225/article/details/47840647 我的环境是: XP SP2.VS2003 最近在一个项目中, 程序退出后都出 ...
- nslookup命令详解【转】
转自:http://blog.chinaunix.net/uid-28933499-id-3750357.html NSlookup命令的用法 用了域名服务器后,经常要查询域名的解析情况,nslook ...
- 01.mp4v2应用—mp4转h264
1.h264文件基本功能 NAL 头 0x00 0x00 0x00 0x01 sps :nal+0x67开头 pps :nal+0x68开头 I帧 0x65 开头 ...... 2.mp4v2提取26 ...