FD_SET的使用

FD_SET的使用

引用自【一文搞懂】FD_SET的使用
如有侵请告知将删

定义

一个long类型的数组,提供给select()机制使用的一种数据结构。主要功能是建立联系。其中每一个数组元素都能与任意一个打开的句柄(socket句柄、文件、命名管道、设备句柄等)建立联系。但是这种建立联系的工作是必须由程序员自己去完成的。

fd_set是什么?

fd_set是一个long类型的数组。我们可以认为这是一个很大的字节数组。

先来一小段代码理解一下fd_set这个数组。

int main()
{
	SOCKET socket = {0};	// 定义一个socket对象
	fd_set fdset = {0};		// 声明并定义,如果不赋初值,fd_set中存储的则是随机值

	FD_ZERO(&fdset);
	FD_SET(1, &fdset); // ’联系‘就是在这里产生的,以下4个操作会产生其他4个联系
	FD_SET(2, &fdset);
	FD_SET(3, &fdset);
	FD_SET(7, &fdset);
	FD_SET(socket, &fdset);

	int isset = FD_ISSET(socket, &fdset); // ’联系‘就是在这里产生的
	printf("isset = %d\n", isset); // isset = 1
	FD_CLR(socket, &fdset);
	isset = FD_ISSET(socket, &fdset); // isset = 0
	printf("isset = %d\n", isset);
	return 0;
}

可以看到,fd_set是一个长度为64的数组,由于代码进行了初始化,所以每一位都是0。在调用FD_SET的过程中,相当于vector.push_back的操作

到底有多少个set,则是通过fd_count来决定的

FD_SETFD_ZEROFD_ISSETFD_CLR的作用

// 这里的fd 实际使用都是以 句柄 传入
FD_ZERO(fd_set *fdset);              // 将set清零使集合中不含任何fd
FD_SET(int fd, fd_set *fdset);       // 将fd加入set集合
FD_CLR(int fd, fd_set *fdset);       // 将fd从set集合中清除
FD_ISSET(int fd, fd_set *fdset);     // 检测fd是否在set集合中,不在则返回0
  1. FD_ZERO就是把当前fd_set所有位的数字都置为0。
  2. FD_SET实现了句柄和fd_set的联系,可以把fd(代码2-1),也就是句柄加入到fd_set中。
  3. FD_CLR清除所绑定的联系,注意注意:这里只清除你传进去的fd和fd_set之间的联系。需要注意的是,FD_CLR的操作类似于链表节点的删除(后续节点会填补被删除节点)。例如第一个问题中的代码;
  4. FD_ISSET宏接口。如果绑定的联系在则返回1,反之,则返回0。

如何通过fd_set(结合select())判断句柄的状态

select()函数原型:

int select(	int maxfdpl, fd_set *restrict readfds,
 		fd_set *restrict writefds,fd_set *restrict exceptfds,
 		struct timeval *restrict typfr);
// 返回值∶准备就绪的描述符数目;若超时,返回0;若出错,返回-1

select 函数使我们可以执行I/O多路转接。传给 select 的参数告诉内核∶

我们所关心的描述符;

对于每个描述符我们所关心的条件(是否想从一个给定的描述符读,是否想写一个给定的描述符,是否关心一个给定描述符的异常条件);

愿意等待多长时间(可以永远等待、等待一个固定的时间或者根本不等待)。

select 返回时,内核告诉我们∶

已准备好的描述符的总数量;

对于读、写或异常这3个条件中的每一个,哪些描述符已准备好。

使用这种返回信息,就可调用相应的 I/O函数(一般是 read 或 write),并且确知该函数不会阻塞。

  • socket非阻塞
    如果要设置socket为非阻塞的状态,则需要调用ioctlsocket(m_Socket, FIONBIO, &ul);来设置。其中的ul是一个unsigned long类型的变量,在此函数接口中,ul == 1表示设置当前的m_Socket为非阻塞状态。

  • select()函数中间的三个参数readfds、writefds、exceptfds,这三个参数是指向描述符集的指针,描述符集说明了我们关心的 可读、可写、异常 的结合

  • select()的中间3个参数中的任意一个(或全部)可以是空指针,当你不需要进行操作判断读写异常的时候可以这么做。如果3个指针都是NULL,则select提供了比sleep更精确的定时器。(什么意思?sleep等待整数秒,而 select 的等待时间则可以小于1秒,其实际精度取决于系统时钟。)

  • select()的第一个参数maxfdp1的意思是“最大文件描述符编号加1”。还是得先明白一个概念,即fd_set的每一位只能使用一次,只能标志一种状态。为避免发生重复应用的情况,如下代码,就需要通过第一个参数去控制。也就是第一个参数maxfdp1。那么第一个参数值如何选取?

    • 设置为FD_SETSIZE。这是<sys/select.h>的一个常量,它指定最大描述符数(通常是1024)。但是一般情况下,该数过于大,一般的程序也就是3~10个描述符。所以一般情况下,选择手动指定。
    • 手动指定,在所有的描述符集中,选择我们关注的最大的描述符数即可。下边代码中,指定的最大描述符数是3,因此select函数的第一个参数为4(= 3+1),即最大描述符编号值加1。
    fd_set readset, writeset;
    FD_ZERO(&readset);
    FD_ZERO(&writeset);
    FD_SET(0, &readset);
    FD_SET(3, &readset);
    FD_SET(1, &writeset);
    FD_SET(2, &writeset);
    select(4, &readset, &writeset, NULL, NULL); // 该处的select就会返回-1

select()有3个可能的返回值:

  1. 返回值-1表示出错。这是可能发生的,例如,在所指定的描述符一个都没有准备好时捕捉到一个信号。在此种情况下,一个描述符集都不修改。
  2. 返回值0表示没有描述符准备好。若指定的描述符一个都没准备好,指定的时间就过了,那么就会发生这种情况。此时,所有描述符集都不修改。
  3. 一个正返回值,说明了已经准备好的描述符数。该值时3个描述符集中已经准备好的描述符之和,所以如果通过描述符已准备好读和写,那么在返回值中会对其计两次数。在这种情况下,3个描述符集中仍旧打开的位对应于已准备好的描述符。

对于“准备好”的含义要作一些更具体的说明。

  • 若对读集(readfds)中的一个描述符进行的 read操作不会阻塞,则认为此描述符是准备好的。

  • 若对写集(writefds)中的一个描述符进行的write 操作不会阻塞,则认为此描述符是准备好的。

  • 若对异常条件集(exceptfds)中的一个描述符有一个未决异常条件,则认为此描述符是准备好的。现在,异常条件包括∶在网络连接上到达带外的数据,或者在处于数据包模式的伪终端上发生了某些条件。

  • 对于读、写和异常条件,普通文件的文件描述符总是返回准备好。

一个描述符阻塞与否并不影响 select 是否阻塞,理解这一点很重要。也就是说,如果希望读个非阻塞描述符,并且以超时值为5秒调用 select,则 select 最多阻塞5s。相类似,如果指定一个无限的超时值,则在该描述符数据准备好,或捕捉到一个信号之前,select会一直阻塞。

如果在一个描述符上碰到了文件尾端,则select 会认为该描述符是可读的。然后调用 read,它返回0,这是 UNIX系统指示到达文件尾端的方法。(很多人错误地认为,当到达文件尾端时,select会指示一个异常条件。)

全篇都在说的 读、写、异常 是怎么判断的呢?

答案是FD_ISSET

  1. 在使用前我们通过FD_SET去建立这种读写异常的联系
  2. select()的时候会修改fd_set的值,而这个修改完之后的值就是我们可以拿去判断的东西。我们可以在select之后增加判断条件
if (!FD_ISSET(m_Socket, &readset))
{
    cout << "sock not in readset!" << endl;
}
if (FD_ISSET(m_Socket, &writeset))
{
    cout << "sock not in writeset!" << endl;
}
if (FD_ISSET(m_Socket, &exceptset))
{
    cout << "getsockopt fail!" << endl;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值