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 */

最新文章

  1. SpringMVC常用注解的用法
  2. tar命令的使用
  3. About Mysql 5.7 Installation
  4. 20145301&amp;20145321&amp;20145335实验五
  5. Centos 安装 NodeJS
  6. mysql修改definer方法
  7. ASP.NET版CKEditor与CKFinder的配置使用
  8. jQuery formValidator表单验证插件
  9. 记一次Sql优化过程
  10. 【Zookeeper学习】Apache Zookeeper项目简介
  11. 【转】Emmagee app性能测试工具使用教程
  12. thinkphp中使用PHPEXCEL导出数据
  13. CSU 1559 订外卖
  14. motan源码分析七:序列化
  15. Webpack 学习手记
  16. solr配置IKAnalyzer抛出ClassNotFoundException
  17. Java数值类型之间转换
  18. zabbix中Templates的jmx相关key调试方法
  19. jQuery Validation Plugin
  20. SQL Server删除表及删除表中数据的方法

热门文章

  1. Mysql实战之主从复制的读写分离
  2. 多线程(实现Runnable接口)
  3. [USACO12Jan][luogu3041] Video Game Combos [AC自动机+dp]
  4. poj 2836 Rectangular Covering
  5. 用ashx还是aspx写ajax响应
  6. GridView数据导入Excel/Excel数据读入GridView
  7. mdf 与 mdb的对比
  8. jquery的toggle动画效果显示隐藏
  9. LeetCode OJ--4Sum *
  10. python 编程模型