UNIX网络编程之IO复用(一):select系统调用

来源:互联网 发布:免费瓷砖设计软件 编辑:程序博客网 时间:2024/06/05 05:01

select系统调用的原型

  select系统调用的功能是:在一段指定的时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。它的原型如下:

#include <sys/select.h>#include <sys/time.h>int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);

两种数据结构fd_set和timeval

  我们先来了解select系统调用涉及到的数据类型。
  首先介绍fd_set。它表示文件描述符的集合,用宏实现,定义如下:

#include <typesizes.h>#define __FD_SETSIZE 1024#include <sys/select.h>#define FD_SETSIZE __FD_SETSIZEtypedef long int __fd_mask;#undef __NFDBITS#define __NFDBITS (8 * (int)sizeof(__fd_mask))typedef struct{#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;

  由上述定义可知,fd_set结构体仅包含一个整型数组,数组中的每一位标记一个文件描述符。fd_set能容纳的文件描述符的数量由FD_SETSIZE指定,这就限制了select能同时监听的文件描述符的数量。
UNIX提供了如下一组宏来访问fd_set结构体中的位:

#include <sys/select.h>FD_ZERO(fd_set *fdset); // 清除fdset的所有位FD_SET(int fd, fd_set *fdset); // 设置fdset的位fdFD_CLR(int fd, fd_set *fdset); // 清除fdset的位fdint FD_ISSET(int fd, fd_set *fdset); // 测试fdset的位fd是否被设置

  然后介绍struct timeval结构体,它的定义如下:

struct timeval{    long tv_sec; // 秒数    long tv_usec; // 微秒数}

各参数和返回值的含义

  然后了解各个参数代表的含义:

  1. maxfdp1是一个值参数,表示感兴趣的文件描述符的最大值加1。
  2. readset、writeset和exceptset都是值-结果参数。进入select时,作为值参数,分别表示感兴趣的文件描述符上的可读、可写和异常事件的集合;从select返回时,作为结果参数,分别表示感兴趣的文件描述符上的可读就绪、可写就绪和异常就绪的事件的集合。
  3. timeout也是一个值-结果参数。进入select时,作为值参数,表示select可以阻塞的最大时间;从select返回时,作为结果参数,表示剩余的时间,通过剩余的时间,可以计算进程阻塞在select上的时间。如果给timeout的tv_sec成员和tv_usec成员都传递0,则select立即返回。如果给timeout传递NULL,则select一直阻塞,直到某个文件描述符就绪。

  返回值的含义:
  select成功时返回就绪(可读,可写和异常)的文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,则select返回0。select失败时返回-1并设置errno。如果在select阻塞期间,程序收到信号,则select被信号中断,立即返回-1,并设置errno=EINTR。

文件描述符的就绪条件

  了解了select系统调用原型的各部分含义后,我们需要知道哪些条件下文件描述符可读就绪,哪些条件下文件描述符可写就绪,以及哪些条件下文件描述符出现异常。现总结如下:

  1. 可读就绪的条件
    • socket内核接收缓冲区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞的读该socket,并且读操作返回的字节数大于0。
    • socket通信的对方关闭连接,即接收到对方的FIN报文。此时对该socket的读操作将返回0。其实,反过来我们可以通过读操作返回0判断对方是否关闭了连接。
    • 监听socket上有未处理的连接请求,即socket通信双方完成了三次握手。此时,我们可以通过accept系统调用获取连接。
    • socket上有未处理的错误。此时,我们可以使用getsockopt来读取和清除该错误。
  2. 可写就绪的条件
    • socket内核发送缓冲区的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时,我们可以无阻塞的写该socket,并且写操作返回的字节数大于0。
    • socket的写操作被关闭。对写操作关闭的socket执行写操作将触发一个SIGPIPE信号。通常,如果socket通信的对方使用close关闭连接时,如果本方发送缓冲区不为空,那么可以执行第一次写操作,对方的TCP会回复一个RST报文,此时本方socket关闭连接,当本方执行第二次写操作时将触发一个SIGPIPE信号。
    • socket使用非阻塞connect连接成功或失败(超时)之后。此时,无论连接成功或失败,都可以对socket进行写操作。不过连接失败时,写数据必然失败。所以我们可以在select返回后再使用非阻塞connect连接一次,若errno=EISCONN,那么说明连接成功,可以进行数据的读写。
    • socket上有未处理的错误。此时我们可以用getsockopt来读取和清除该错误。
  3. 异常的条件
    • select能处理的异常情况只有一种:socket上接收到带外数据。

  未完待续。。。

原创粉丝点击