I0/select

本文主要介绍了select系统调用,其可在指定时间内监听文件描述符上的可读、可写和异常等事件。详细说明了select API的参数,如nfds、readfds等,还阐述了文件描述符的就绪条件,包括socket可读、可写及异常的情况,最后提及了处理带外数据的相关内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

IO/select

select 系统调用

select系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述上的可读、可写和异常等事件。

select API

select 系统调用的原型如下:

#include<sys/select.h>
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

参数解释:
1)nfds参数指定被监听的文件描述符的总数。通常设置为 select 监听的所有文件描述符中的最大值加1,这是因为文件描述符是从0开始计数的。

2)readfds、writefds 和 exceptfds 参数分别指可读、可写和异常等事件对应的文件描述符集合。select 调用返回时,内核将修改它们来通知应用程序那些文件描述符已经就绪。这3个参数是fd_set结构指针类型。fd_set 结构体的定义如下:

 #include<typesizes.h>
 #define __FD_SETSIZE 1024

 #include<sys/select.h>
 #define FD_SETSIZE __FD_SETSIZE
 typedef 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 结构体仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。

需要注意的一点是:

fd_set能容纳的文件描述符数量由 FD_SETSIZE 指定,其限制了select能同时处理的文件描述符的总量。

为了便于操作,我们使用下面的一系列宏(MACROS)来访问 fd_set 结构体中的位:

 FD_ZERO(fd_set *fdset);					/*清除 fdset 的所有位 */
 FD_SET(int fd,fd_set *fdset);				/*设置 fdset 的位 fd */
 FD_CLR(int fd,fd_set *fdset);				/*清除 fdset 的位 fd */
 int FD_ISSET(int fd,fd_set *fdset);		/*测试 fdset 的位 fd 是否被设置*/

3)timeout 参数用来设置 select 函数的超时时间,它是一个 timeval 结构类型的指针。它的结构体定义如下:

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

需要注意的是:select 调用失败时 timeout 的值是不确定的,所以我们不能完全相信 timeout 的值。

如果参数 timeout 的值为0,则 select 立即返回。
如果参数 timeout 的值为 NULL,则 select 一直阻塞,直到某个文件描述符就绪。

select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果超时,则返回 0 。失败返回 -1 并设置 errno。如果在 select 等待期间,程序接收到信号,则 select 立即返回 -1,并设置 errno 为 EINTR。

文件描述符就绪条件

在网络编程中,下列情况下 socket 可读:

  1. socket 内核接收缓存区中的字节数大于或等于其低水位标记 SO_RCVLOWAT。此时,我们可以无阻塞地读该 socket,并且读操作返回的字节数大于 0。
  2. socket 通信的对方关闭连接。此时对该 socket 的读操作将返回 0。
  3. 监听 socket 上有新的连接请求。
  4. socket 上有未处理的错误。此时我们可以使用 getsockopt 来读取和清除该错误。

下列情况下 socket 可写:

  1. socket 内核发送缓冲区中的可用字节数大于或等于其低水位标记 SO_SNDLOWAT。此时,我们可以无阻塞地写该 socket,并且写操作返回的字节数大于 0。
  2. socket 的写操作被关闭。对于写操作被关闭的 socket 执行写操作将触发一个 SIGPIPE 信号。
  3. socket 使用非阻塞 connect 连接成功或者失败(超时)之后。
  4. socket 上有未处理的错误。此时我们可以使用 getsockopt 来读取和清除该错误。

网络程序中, select 能处理的异常情况只有一种:socket 上接收到带外数据

处理带外数据

socket 上接收到普通数据和带外数据(点击查看)都将使 select 返回,但 socket 处于不同的就绪状态:前者处于可读状态,后者处于异常状态。下面是 socket 同时接收普通数据和带外数据的一个例子:

 #include<sys/types.h>
 #include<sys/socket.h>
 #include<netinet/in.h>
 #include<arpa/inet.h>
 #include<assert.h>
 #include<stdio.h>
 #include<unistd.h>
 #include<errno.h>
 #include<string.h>
 #include<fcntl.h>
 #include<stdlib.h>
 #define BACKLOG 5


 int main(int argc,char *argv[])
 {
     if(argc <= 2)
     {
         printf("usage: %s ip_address port_number\n",basename(argv[0]));
         return 1;
     }

     const char* ip = argv[1];
     int port = atoi(argv[2]);

     int ret = 0;
     struct sockaddr_in address;
     inet_pton(AF_INET,ip,&address.sin_addr);
     address.sin_port = htons(port);

     int listenfd = socket(AF_INET,SOCK_STREAM,0);
     assert(listenfd >= 0);
     ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
     assert(ret != -1);
     ret = listen(listenfd,BACKLOG);
     assert(ret != -1);

     struct sockaddr_in client_address;
     socklen_t client_addrlength = sizeof(client_address);
     int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_ad    drlength);
     if(connfd < 0)
     {
         printf("errno is : %d\n",errno);
         close(listenfd);
     }

     char buf[1024];
     fd_set read_fds;
     fd_set exception_fds;
     FD_ZERO(&read_fds);
     FD_ZERO(&exception_fds);

     while(1)
     {
         memset(buf,'\0',sizeof(buf));
         FD_SET(connfd,&read_fds);
         FD_SET(connfd,&exception_fds);
         ret = select(connfd+1,&read_fds,NULL,&exception_fds,NULL);
         if(ret < 0)
         {
             printf("selection failure\n");
             break;
         }

         if(FD_ISSET(connfd,&read_fds))
         {
             ret = recv(connfd,buf,sizeof(buf)-1,0);
             if(ret <= 0)
                 break;
             printf("get %d bytes of normal data:%s\n",ret,buf);
         }else if(FD_ISSET(connfd,&exception_fds))
         {
             ret = recv(connfd,buf,sizeof(buf)-1,MSG_OOB);
             if(ret <= 0)
                 break;
             printf("get %d bytes of oob data: %s\n",ret,buf);
         }
     }
     close(connfd);
     close(listenfd);
     return 0;
 }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值