Linux-select机制

select中文释义表示选择

为实现socket非阻塞。通过将fd句柄放在集合中(一维数组),通过轮询的方式来判断当前是否有事件发生。

select作用

监视文件描述符的变化(可读、可写或异常)

在Linux系统中,一切都以文件的形式存在,包括网络通讯也是。因此select通常用来监视socket文件句柄。

简单理解为

客户端
socket
服务端

监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了

我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了.

监视这些文件描述符的变化,读变化表示可以从这些文件中读取数据(简单理解为接收数据),写变化表示可以向这些文件中写入数据(发送数据)。

头文件

#include <sys/select.h>

函数原型

int select (int maxfd + 1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval * timeout);

参数说明

参数一:最大的文件描述符加1
参数二:用来检查可读性的一组文件描述字。(接收的数据)
参数三:用来检查可写性的一组文件描述字。(发送的数据)
参数四:用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
参数五:一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。timeval结构的定义:

struct timeval{
long tv_sec; // seconds
long tv_usec; // microseconds
}

timeout: 有三种可能:

  1. timeout = NULL (阻塞:直到有一个fd位被置为1函数才返回)
  2. timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)
  3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每一个fd后立即返回)

readset writeset exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为NULL。如果三个指针都为NULL,我们就有了一个比sleep()函数更为精确的定时器(sleep()以毫秒为最小单位,这个以微秒为单位)。

参数一

使用最大的文件描述符可以保证select能够轮询到每个监视的文件描述符,加1的原因是fd_set是一维数组。从数组下标0开始存储。

并且参数一还有一个最大的作用是提高效率,使得select不必轮询检查默认fd_set的所有1024位。(#define __FD_SETSIZE 1024)

返回值

>0:就绪描述字的正数目(返回对应位仍然为1的fd的总数)
-1:出错
0 :超时(没有可读写或错误的文件)

select缺陷

  1. 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;(在linux内核头文件中,有这样的定义:#define __FD_SETSIZE 1024)

  2. 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;

  3. select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;

  4. select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。

    相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。

拿select模型为例,假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。

如何查看当前Linux系统 select最大支持多少?

在Linux下,我们使用ulimit -n 命令可以看到单个进程能够打开的最大文件句柄数量(socket连接也算在里面)。系统默认值1024。”

FD相关接口

select文件描述符基本流程

定义fd_set变量,用来存储fd文件描述符。

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;
  
  #define  __FD_SETSIZE    1024

需要配合FD_ZERO使用,否则会出现不可预知的后果。就跟平时定义个结构体,memset的原理一样。要清空fd_set,便于存储fd。

fd_set set;

FD_ZERO(&set);

FD_ZERO(&set);        /*将set清零使集合中不含任何fd*/

FD_SET(fd, &set);      /*将fd加入set集合*/

FD_CLR(fd, &set);      /*将fd从set集合中清除*/

FD_ISSET(fd, &set);   /*测试fd是否在set集合中*/

接口使用实例

FD_ZERO(&set);        /*将set的所有位置0,如set在内存中占8位则将set置为00000000*/

FD_SET(0, &set);       /*将set的第0位置1,如set原来是00000000,则现在变为100000000,这样fd==1的文件描述字就被加进set中了*/

FD_CLR(4, &set);       /*将set的第4位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了*/

FD_ISSET(5, &set);     /*测试set的第5位是否为1,如果原来set是10000100,则返回非零,表明fd==5的文件描述符在set中,否则返回0*/

注意:fd的最大值必须<FD_SETSIZE。

accept函数主要会使用server端的fd,然后返回新增fd用于client进行通讯。

accept()的参数listenfd是先前的监听文件描述符,而accept()的返回值是另外一个文件描述符connfd,之后与客户端之间就通过这个connfd通讯,最后关闭connfd断开连接,而不关闭listenfd,再次回到循环开头listenfd仍然用作accept的参数。accept()成功返回一个文件描述符,出错返回-1。

实例代码

Socket相关简单实例

参考资料

select(Linux 网络编程)_百度百科

Linux fd_set用法

Linux select用法

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值