2014年(11)
分类: LINUX
2014-06-06 17:47:20
I/O复用,我们就可以调用系统调用select和poll!在这两个系统调用中的某一个阻塞,而不是真正的阻塞I/O系统调用!select函数可以指示内核等待多个事件中的任一个发生,仅在一个或多个事件发生,或者等待一个足够的时间后才唤醒进程!
select包括5个参数,分别是: 需要监视的文件描述符的最大值加1,select监视的度文件描述符集,select监视的写描述符集,select监视的异常处理文件描述符集合,和超时参数。其中超时参数有三个值,NULL为永远等待;0为从不等待;struct timeval是一个结构体,可设置时间。
select 定义
int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);
返回值:正常——文件描述符个数;超时——0;出错——-1
maxfdp1 : 描述字最大值
readset : 读描述字集
writeset : 写描述字集
exceptset :异常条件的描述字集
timeout : 等待时间
maxfdp1 最大描述字
当select刚开始设计时,操作系统常对每个进程可用的最大描述字数上限作出限制(4.2BSD的限制为31),select也就用相同的限制值。unix版本对每个进程的描述字数根本不作限制 (仅受限于内存量和管理性限制),
readset 套接口准备好读
套接口接收缓冲区中的数据字节数>=套接口接收缓冲区低潮限度的当前值,连接的读这一关闭(接收了FIN的TCP连接)套接口是一个监听套接口旦已完成的连接数为非0。有一个套接口错误待处理。
writeset 套接口准备好写
套接口发送缓冲区中的可用空间字节数>=套接口发送缓冲区低潮限度的当前值,且或者(i)套接口已连接,或者(i)套接口不要求连接。连接的写这一半关闭。对这样的套接口的写操作将产生信SIGPIPE。有一个套接口错误待处理。对这样的套接口的写操作将不阻塞且返回一个错误(一1)
exceptset异常条件待处理
如果一个套接口存在带外数据或者仍处于带外标记,那它有异常条件待处理。带外数据(out of band data),有时也称为加速数据(expedited data),是指连接双方中的一方发生重要事情,想要迅速地通知对方。
这种通知在已经排队等待发送的任何“普通”(有时称为“带内”)数据之前发送。带外数据设计为比普通数据有更高的优先级。带外数据是映射到现有的连接中的,而不是在客户机和服务器间再用一个连接。
最后一个参数是一个结构体的指针,表示等待内核中的一组描述符任一个准备好需要花费的时间!其中timeval指定了秒数和微秒数。
struct timeval{
long tv_sec;//秒数
long tv_usec;//微秒数
};
将 timeout设置为空指针时,会永远等待下去,等待固定的时间:如果timeout指向的timeval中的具体的值时,会等待一个固定的时间,不等待立刻返回,这时timeval中的tv_sec和tv_usec为0.
系统提供了4个宏对select( )描述符集进行操作:
1.FD_ZERO(fd_set *set) 清除监视的文件描述符set所有位(清0)
2.FD_SET(int fd , fd_set *set)设置文件描述符集set对应于文件描述符fd的位
3.FD_CLR(int fd, fd_set *set)删除文件描述符集set对应文件描述符fd的位(置0)
4.FD_ISSET(int fd ,fd_set *set)判断fd 是否在文件集set中
描述字集:是一个整数数组,每个数中的每一位对应一个描述字。
数组的第一个元素对应于描述字0-31,数组户的第二个元素对应于描述字32-63。
例如下面一段代码:
fd_set readset; //定义描述字集数据类型
FD_ZERO(&readset); //对描述字集初始化
FD_SET(5, &readset); //打开描述字的第5位
FD_SET(33, &readset); //打开描述字的第33位
FD_ISSET(5, &rest) //测试描述字的第4位
FD_CLR(5, &rset) //关闭描述字的第4位
则文件描述符集readset中对应于文件描述符6和33的相应位被置为1,如图1所示:
再执行如下程序后:
FD_CLR(5, &readset);
则文件描述符集readset对应于文件描述符6的相应位被置为0,如图2所示:
通常,操作系统通过宏FD_SETSIZE来声明在一个进程中select所能操作的文件描述符的最大数目。一般情况下被定义为1024.一个整数占4个字节,既32位,那么就是用包含32个元素的整数数组来表示文件描述符集。我们可以在头文件中修改这个值来改变select使用的文件描述符集的大小,但是需要注意的是:必须重新编译内核才能使修改后的值有效。
如果我们对其中的个一不感兴趣的话,可以设置为空指针。如果我们把三个都设为空指针,就实现了一个比sleep更准确的定时器。
注意select函数的第一个参数maxfdp1,是所有加入集合的句柄值的最大那个值还要加1。比如我们的描述符为1 4 5,那么maxfdp1就为6.描述符从0开始。当我们调用函数时,指定我们关心的描述符集,当返回时,指示那些描述符已经准备好了。怎么样才算准备好呢!下面列出几种情况:
下列四个条件中的任何一个满足时,套接口准备好读:
(1) 套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮限度的当前值。可以通过SO_REVILOAT来设置此低潮限度。
(2)连接的读这一半关闭,也就是接收了FIN的TCP连接,
(3)套接口是一个监听套接口且已完成的连接数为非0.
(4)有一个套接口错误等处理。
下列三个条件中的任一个满足时,套接口准备好写:
(1) 套接口发送缓冲区的可用空间字节娄大于等于套接口发送缓冲区低潮限度的当前值且或者(i)套接口已连接,或者(ii)套接口不要求连接。
(2)连接的写这一半关闭,对这样的套接口写操作将产生信号SIGPIEP。
(3)有一个套接口错误待处理。
下面是select函数的一个例子,主要参考网上的例子,并进行的适当的改变!并增加了客户端的程序。修改不fd[i]数组,可是实现动态的管理,也解决了,当一个客户不断的断开再连接时,服务器也断开的情况。
对套接口的处理
对方TCP发送数据,套接口就变为可读且read返回大于0
对方TCP发送一个FIN(对方进程终止),套接口就变为可读且read返回0(文件结束)。
对方TCP发送一个RST (对方主机崩溃并重新启动),套接口变为可读且返回-1
/#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) + 1; //获得最大描述字
Select(maxfdp1, &rset, NULL, NULL, NULL); //对是否可读进行测试
if (FD_ISSET(sockfd, &rset)) //#####如果套接口可读
{
if (Readline(sockfd, recvline, MAXLINE) = = 0) //读入一行
err_quit("str_cli: server termi. premat.."); //对方终止时退出
Fputs(recvline, stdout); //写到标准输出
}
if (FD_ISSET(fileno(fp), &rset)) //####如果标准输入可读
{
if (Fgets(sendline, MAXLINE, fp) == NULL) //读入一行
return; // 遇到^D时退出子程序
Writen(sockfd, sendline, strlen(sendline)); //写入套接口
}
}
}