前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >I/O复用——select函数

I/O复用——select函数

原创
作者头像
jackieluo
发布2018-12-02 16:43:38
1K0
发布2018-12-02 16:43:38
举报
文章被收录于专栏:Jackie技术随笔Jackie技术随笔

I/O复用——select函数

select函数

select函数让进程告诉内核,等待数个事件,某个事件发生或者达到指定时间时,唤醒进程。

例如,我们可以调用select函数,通知内核,以下几种情况需要返回,

  • 描述字集合{1,4,5}中任意一个描述字准备好被读
  • 描述字集合{2,7}中任意一个描述字准备好写
  • 描述字集合{1,4}中任意一个描述字发生异常待处理
  • 已经过去10秒

上述过程,表示进程告诉内核,进程关注哪些描述字(读写或者异常),以及等待多长时间。这里说的描述字并不仅限于套接口,任何描述字都可以应用于select函数。

代码语言:txt
复制
#include	<sys/time.h>
#include	<sys/select.h>

int select(int maxfdp1, fd_set * readset, fd_set * writeset, fd_set * exceptset, struct timeval * timeout)

最后一个参数,指定一个内核等待描述字的超时时间,结构体timeval指定了秒级和微秒级成员。

代码语言:txt
复制
struct timeval {
  long tv_sec; /* seconds */
  long tv_usec;/* and microseconds */
};

根据设置这一参数的不同,可能出现下列三种情况:

  1. 将参数timeout置为空指针。内核将永远等待下去,等待描述字准备好I/O或异常时才返回。
  2. 将参数timeout中的时间设为不为0的固定值。内核会在描述字准备好I/O、异常,或者等待超过设定时间时返回。
  3. 将参数timeout中的时间设为0。不等待,检查描述字后立即返回,即轮询(polling)。

中间的三个参数,分别对应进程需要内核关注的读、写及异常的描述字集合,类型为fd_set,例如,定义前面说到的被读的描述字集合:

代码语言:txt
复制
fd_set rset;
FD_ZERO(&rset);
FD_SET(1, &rset);
FD_SET(4, &rset);
FD_SET(5, &rset);

如果对某个条件不关心,将其置为空指针即可,如果这三个参数都置为空指针,而最后的参数不为空指针。select函数实际上就变成了一个比函数sleep函数更精确的定时器(sleep精确到秒)。

第一个参数指定内核关注描述字的边界,值是集合中最大的描述字加1,内核会从0开始,依次测试边界内的所有描述字。例如,对于上面{1,4,5}的描述字集合,第一个参数的值应该是6。

描述字集合的几个参数均为值-结果参数,内核返回的时候会修改它们的值,告诉进程哪些描述字已经准备好。返回时,进程可以通过宏FD_ISSET来测试描述字集合中的描述字,如果已经准备好,这些描述字的值会置为1。由于我们对结果的关注,所以一定要注意第一个参数的正确性,否则本该置为1的描述字可能会被置为0。

函数的返回值表示所有三个描述字集中已经准备好的总位数,有三种情况:

  1. 返回0。在描述字准备好之前到达超时设置。
  2. 返回大于0。返回已经准备好的描述字总位数。
  3. 返回-1。异常,例如被信号中断等。

什么是“描述字准备好”

前面一直讨论的“描述字准备好”,在select函数处理的时候,具体条件如下:

准备好读

下面四个条件任意满足一个,套接口准备好读:

  1. 套接口接收缓冲区中的数据字节数大于等于套接口接收缓冲区低潮限度(默认为1),套接口的读操作将不阻塞并且返回大于0的值(准备好读入的数据量)。
  2. 连接的读这一半关闭(接收到FIN分节),套接口的读操作将不阻塞并且返回0(即文件结束符)。
  3. 套接口是监听套接口且已完成的连接非0。
  4. 有一个套接口错误待处理。套接口的读操作将不阻塞并且返回一个错误(-1)。

准备好写

下面三个条件任意满足一个,套接口准备好写:

  1. 套接口发送缓冲区中的可用空间字节数大于等于套接口发送缓冲区低潮限度(默认为2048),且套接口已连接或者套接口不要求连接(例如UDP套接口)。套接口的写操作将不阻塞并且返回大于0的值(例如传输层接收的字节数)。
  2. 连接的写这一半关闭,对这样的套接口的写操作将产生信号SIGPIPE
  3. 有一个套接口错误待处理。套接口的读操作将不阻塞并且返回一个错误(-1)。

“描述字准备好”总结

select来说套接口准备好的条件的总结如下,

条件

是否可读

是否可写

是否异常

有数据可读

关闭连接的读一半

给监听套接口准备好新连接

有可用于写的空间

关闭连接的写一半

待处理错误

TCP带外数据

在客户端程序中使用select

修改客户端的函数str_cli,使用select,这样服务器进程一终止,客户就能马上得到通知。原来版本的问题在于套接口上发生事件时,阻塞于fgets调用的客户端无法及时处理。新版本则阻塞于select调用,无论是标准输入,还是套接口事件,客户端都可以及时处理。

在新版函数str_cli中,由select处理以下条件:

  1. 如果对方TCP发送数据,套接口就变为可读且read返回大于0的值(数据字节数)。
  2. 如果对方TCP发送一个FIN(对方进程终止),套接口就变为可读且read返回0(文件结束符)。
  3. 如果对方TCP发送一个RST(对方主机崩溃并重新启动),套接口就变为可读且read返回-1,errno则含有明确的错误码。
函数str_cli中由select处理的条件
函数str_cli中由select处理的条件

修改函数str_cli

str_cli函数select版本与初始版本对比
str_cli函数select版本与初始版本对比
使用函数select

在新版的str_cli函数中,使用select函数,在返回可读条件时,分别处理可读套接口和可读标准输入。

处理可读套接口

如果select返回套接口可读,则读取数据并输出打印。

处理标准输入

如果select返回标准输入可读,则调用fgets阻塞读入一行,并写到套接口。

重新运行客户端和服务器,并依样找到服务器子进程的PID,杀掉进程,

代码语言:txt
复制
[root@VM_0_6_centos ~]# ps -la
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S     0  5774  5188  0  80   0 -  1596 inet_c pts/0    00:00:00 tcpserv04
1 S     0  8271  5774  0  80   0 -  1596 sk_wai pts/0    00:00:00 tcpserv04
0 R     0  8391  8353  0  80   0 - 38300 -      pts/2    00:00:00 ps
[root@VM_0_6_centos ~]# kill -9 8271

此时客户端立即通知用户服务器进程已终止,

代码语言:txt
复制
jackieluo@JACKIELUO-MB1 ~/Desktop/unpv13e/select ./tcpcli01 150.107.102.37
hello
hello
str_cli: server terminated prematurely

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I/O复用——select函数
    • select函数
      • 什么是“描述字准备好”
        • 准备好读
        • 准备好写
        • “描述字准备好”总结
      • 在客户端程序中使用select
        • 修改函数str_cli
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
    http://www.vxiaotou.com