在前面的文章中介绍了五种 I/O 模型《I/O 模型》,这里介绍 I/O 模型中 I/O 多路复用在 TCP 套接字编程中的使用。在 I/O 多路复用中主要是 select 和 poll 函数的使用。
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在一个或多个事件发生或超过指定时间后才被唤醒。进程调用 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 */
};
#include <sys/select.h> int FD_ISSET(int fd, fd_set *fdset); //测试描述符fd是否在描述符集中设置;若fd在描述符集中则返回非0值,否则返回0 void 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;
#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);/* 打开标准文件指针 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));
}
}
}
/* * 函数功能:关闭套接字上的输入或输出; * 返回值:若成功则返回0,若出错返回-1; * 函数原型: */ #include <sys/socket.h> int shutdown(int sockfd, int how); /* * 说明: * sockfd表示待操作的套接字描述符; * how表示具体操作,取值如下: * (1)SHUT_RD 关闭读端,即不能接收数据 * (2)SHUT_WR 关闭写端,即不能发送数据 * (3)SHUT_RDWR 关闭读、写端,即不能发送和接收数据 * */
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;
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 */
else
err_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);
}
}
}
/* * 函数功能:和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 */
}; 原文地址:http://blog.csdn.net/chenhanzhun/article/details/41910345