Socket select FD_ISSET

本文介绍Linux环境下如何利用gettimeofday函数获取系统时间,并通过示例代码展示了非阻塞socket的应用,特别是如何使用select函数高效管理多个socket连接。

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

DESCRIPTION    The functions gettimeofday and settimeofday can get and set the time as
    well as a timezone. The tv argument is a timeval struct, as specified
    in <sys/time.h>:

    struct timeval {
          time_t       tv_sec;     /* seconds */
          suseconds_t   tv_usec; /* microseconds */
    };

其中对tv_usec的说明为时间的毫秒部分。 而在实际中,该函数以及Linux内核返回的timeval
类型的时间值,tv_usec代表的是微秒精度(10的-6次方秒)。

测试代码如下:

#include <stdio.h>
#include <sys/time.h>
#include <time.h>

    int gettimeofday(struct timeval *tv, struct timezone *tz);



int main(int argc,char * argv[]){

    struct timeval tv;
    while(1){
          gettimeofday(&tv,NULL);
          printf("time %u:%u\n",tv.tv_sec,tv.tv_usec);
          sleep(2);

    }
    return 0;

}


返回结果为:

evil@dcenter:~/tmp$ ./a.out
time 1142077839:903605
time 1142077841:910129
time 1142077843:920155
time 1142077845:930180
time 1142077847:940205
time 1142077849:950231
time 1142077851:960256
time 1142077853:970280
time 1142077855:980307
time 1142077857:990331












linux 的socket函数分为阻塞和非阻塞两种方式,比如accept函数,在阻塞模式下,它会一直等待有客户连接。而在非阻塞情况下,会立刻返回。我们一般都希望程序能够运行在非阻塞模式下。一种方法就是做一个死循环,不断去查询各个socket的状态,但是这样会浪费大量的cpu时间。解决这个问题的一个方法就是使用select函数。使用select函数可以以非阻塞的方式和多个socket通信。当有socket需要处理时,select函数立刻返回,期间并不会占用cpu时间。

例程分析:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define MYPORT 1234 // 侦听端口

#define BACKLOG 5 // 最大可连接客户端数量

#define BUF_SIZE 200

int fd_A[BACKLOG]; // 连接的FD数组
int conn_amount; // 当前连接的数量


void showclient()
{
int i;
printf("client amount: %d\n", conn_amount);
for (i = 0; i < BACKLOG; i++)
{
printf("[%d]:%d ", i, fd_A[i]);
}
printf("\n\n");
}

int main(void)
{
int sock_fd, new_fd; // 侦听sock_fd, 新连接new_fd
struct sockaddr_in server_addr; // server address information
struct sockaddr_in client_addr; // connector's address information
socklen_t sin_size;
int yes = 1;
char buf[BUF_SIZE];
int ret;
int i;


//创建侦听Socket
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Create listening socket error!");
exit(1);
}
//配置侦听Socket
//SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑。
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1)
{
perror("setsockopt error!");
exit(1);
}

server_addr.sin_family = AF_INET; // host byte order
server_addr.sin_port = htons(MYPORT); // short, network byte order
server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

//绑定新创建的Socket到指定的IP和端口
if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind error!");
exit(1);
}
//开始侦听,最大连接数为BACKLOG
if (listen(sock_fd, BACKLOG) == -1)

{
perror("listen error!");
exit(1);
}

printf("listen port %d\n", MYPORT);

//监控文件描述符集合
fd_set fdsr;
//监控文件描述符集合中最大的文件号
int maxsock;
//Select超时返回的时间。
struct timeval    tv;

conn_amount = 0;
sin_size = sizeof(client_addr);
maxsock = sock_fd;
while (1)
{
// 初始化文件描述符集合 initialize file descriptor set
FD_ZERO(&fdsr);
// 把Sock_fd加入到文件描述符集合
FD_SET(sock_fd, &fdsr);

// 超时设置30秒
tv.tv_sec = 30;
tv.tv_usec = 0;

// 把活动的socket的句柄加入到文件描述符集合中
for (i = 0; i < BACKLOG; i++)
{
if (fd_A[i] != 0)
{
FD_SET(fd_A[i], &fdsr);
}
}
//Select 函数原型
//int select(nfds, readfds, writefds, exceptfds, timeout)
//nfds: select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件中的

//最大文件号加一
//readfds:select监视的可读文件句柄集合
//writefds:select监视的可写文件句柄集合。
//exceptfds:select监视的异常文件句柄集合。
//timeout:本次select的超时结束时间。

ret = select(maxsock + 1, &fdsr, NULL, NULL, &tv);

if (ret < 0)
{
perror("select error!");
break;
}
else if (ret == 0)
{
printf("timeout\n");
continue;
}

// 轮询各个文件描述符(socket)
for (i = 0; i < conn_amount; i++)
{
//FD_ISSET(int fd, fdset *fdset):检查fdset联系的文件句柄fd是否可读写,

// >0表示可读写。
if (FD_ISSET(fd_A[i], &fdsr))
{
//接收数据
ret = recv(fd_A[i], buf, sizeof(buf), 0);
if (ret <= 0) //接收数据出错
{
printf("client[%d] close\n", i);
close(fd_A[i]);
FD_CLR(fd_A[i], &fdsr);
fd_A[i] = 0;
}
else // 数据接收成功
    {
//将接收数据的最后一位补0
if (ret < BUF_SIZE)

memset(&buf[ret], '\0', 1);
printf("client[%d] send:%s\n", i, buf);
}
}
}

// 检查是否有新连接进来,如果有新连接进来,接收连接,生成新socket,

//并加入到监控文件描述符集合中。
if (FD_ISSET(sock_fd, &fdsr))
{
//接受连接
new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
if (new_fd <= 0)
{
perror("accept socket error!");
continue;
}

// 将新的连接加入到监控文件描述符集合
if (conn_amount < BACKLOG)
{
fd_A[conn_amount++] = new_fd;
printf("new connection client[%d] %s:%d\n", conn_amount,inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
if (new_fd > maxsock)
maxsock = new_fd;
}
else
{
printf("max connections arrive, exit\n");
send(new_fd, "bye", 4, 0);
close(new_fd);
break;
}
}
showclient();
}

// 关闭所有连接
for (i = 0; i < BACKLOG; i++)
{
if (fd_A[i] != 0)
{
close(fd_A[i]);
}
}
exit(0);
}




























使用select函数可以以非阻塞的方式和多个socket通信。程序只是演示select函数的使用,功能非常简单,即使某个连接关闭以后也不会修改当前连接数,连接数达到最大值后会终止程序。

1. 程序使用了一个数组fd_A,通信开始后把需要通信的多个socket描述符都放入此数组。

2. 首先生成一个叫sock_fd的socket描述符,用于监听端口。

3. 将sock_fd和数组fd_A中不为0的描述符放入select将检查的集合fdsr。

4. 处理fdsr中可以接收数据的连接。如果是sock_fd,表明有新连接加入,将新加入连接的socket描述符放置到fd_A。


#include <stdio.h>
#include
<stdlib.h>
#include
<unistd.h>
#include
<errno.h>
#include
<string.h>
#include
<sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<arpa/inet.h>

#define MYPORT 1234    // the port users will be connecting to

#define BACKLOG 5     // how many pending connections queue will hold

#define BUF_SIZE 200

int fd_A[BACKLOG];    // accepted connection fd
int conn_amount;    // current connection amount

void showclient()
{
   
int i;
    printf(
"client amount: %d\n", conn_amount);
   
for (i = 0; i < BACKLOG; i++) {
        printf(
"[%d]:%d  ", i, fd_A[i]);
    }
    printf(
"\n\n");
}

int main(void)
{
   
int sock_fd, new_fd;  // listen on sock_fd, new connection on new_fd
    struct sockaddr_in server_addr;    // server address information
    struct sockaddr_in client_addr; // connector's address information
    socklen_t sin_size;
   
int yes = 1;
   
char buf[BUF_SIZE];
   
int ret;
   
int i;

   
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror(
"socket");
        exit(
1);
    }

   
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
        perror(
"setsockopt");
        exit(
1);
    }
   
    server_addr.sin_family
= AF_INET;         // host byte order
    server_addr.sin_port = htons(MYPORT);     // short, network byte order
    server_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
    memset(server_addr.sin_zero, '\0', sizeof(server_addr.sin_zero));

   
if (bind(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror(
"bind");
        exit(
1);
    }

   
if (listen(sock_fd, BACKLOG) == -1) {
        perror(
"listen");
        exit(
1);
    }

    printf(
"listen port %d\n", MYPORT);

    fd_set fdsr;
   
int maxsock;
   
struct timeval tv;

    conn_amount
= 0;
    sin_size
= sizeof(client_addr);
    maxsock
= sock_fd;
   
while (1) {
       
// initialize file descriptor set
        FD_ZERO(&fdsr);
        FD_SET(sock_fd,
&fdsr);

       
// timeout setting
        tv.tv_sec = 30;
        tv.tv_usec
= 0;

       
// add active connection to fd set
        for (i = 0; i < BACKLOG; i++) {
           
if (fd_A[i] != 0) {
                FD_SET(fd_A[i],
&fdsr);
            }
        }

        ret
= select(maxsock + 1, &fdsr, NULL, NULL, &tv);
       
if (ret < 0) {
            perror(
"select");
           
break;
        }
else if (ret == 0) {
            printf(
"timeout\n");
           
continue;
        }

       
// check every fd in the set
        for (i = 0; i < conn_amount; i++) {
           
if (FD_ISSET(fd_A[i], &fdsr)) {
                ret
= recv(fd_A[i], buf, sizeof(buf), 0);
               
if (ret <= 0) {        // client close
                    printf("client[%d] close\n", i);
                    close(fd_A[i]);
                    FD_CLR(fd_A[i],
&fdsr);
                    fd_A[i]
= 0;
                }
else {        // receive data
                    if (ret < BUF_SIZE)
                        memset(
&buf[ret], '\0', 1);
                    printf(
"client[%d] send:%s\n", i, buf);
                }
            }
        }

       
// check whether a new connection comes
        if (FD_ISSET(sock_fd, &fdsr)) {
            new_fd
= accept(sock_fd, (struct sockaddr *)&client_addr, &sin_size);
           
if (new_fd <= 0) {
                perror(
"accept");
               
continue;
            }

           
// add to fd queue
            if (conn_amount < BACKLOG) {
                fd_A[conn_amount
++] = new_fd;
                printf(
"new connection client[%d] %s:%d\n", conn_amount,
                        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
               
if (new_fd > maxsock)
                    maxsock
= new_fd;
            }
           
else {
                printf(
"max connections arrive, exit\n");
                send(new_fd,
"bye", 4, 0);
                close(new_fd);
               
break;
            }
        }
        showclient();
    }

   
// close other connections
    for (i = 0; i < BACKLOG; i++) {
       
if (fd_A[i] != 0) {
            close(fd_A[i]);
        }
    }

    exit(
0);
}
<think>好的,用户问的是关于FD_ISSET函数在C或C++ socket编程中的用法。首先,我需要回忆一下FD_ISSET的相关知识。FD_ISSET是用于检查一个文件描述符是否在select系统调用返回的某个集合中,比如可读、可写或异常集合。通常在多路复用I/O操作中使用,帮助程序同时监控多个套接字的状态。 接下来,我应该先解释FD_ISSET的作用和它在select函数中的位置。用户可能需要知道如何正确使用这个函数来检测就绪的文件描述符。需要提到FD_SET、FD_ZERO等宏的配合使用,因为它们是设置文件描述符集合的基础步骤。 然后,考虑到用户可能对select的整体流程不太清楚,应该分步骤说明:初始化fd_set,使用FD_SET添加关心的描述符,调用select,之后用FD_ISSET检查哪些描述符就绪。要强调FD_ISSETselect返回后的使用时机,以及它的返回值含义。 另外,常见的问题可能包括忘记初始化fd_set,或者在循环中错误地重用fd_set而没有重新初始化。需要提醒用户在每次调用select前都要重新设置fd_set,因为select会修改这些集合。 可能还需要提到错误处理,比如检查select的返回值,处理错误和超时情况。给出一个代码示例会更有帮助,这样用户可以直观地看到FD_ISSET在实际中的应用。示例应该包括创建套接字、设置监听、使用select等待连接,以及使用FD_ISSET检查是否有新的连接到达。 此外,用户可能会遇到FD_ISSET总是返回false的问题,这可能是因为文件描述符没有正确添加到集合中,或者在select调用前集合被修改了。需要指出这些可能的错误点,并给出解决方法,比如使用FD_SET添加描述符后,在select调用前不要修改集合,或者在循环中每次重新初始化。 最后,生成相关问题可以帮助用户进一步学习,比如select函数的参数解释,处理多个客户端的最佳实践,或者替代select的其他函数如poll或epoll的区别和使用场景。这些相关问题应该围绕用户的问题展开,帮助他们深入理解和解决实际编程中的问题。</think>### FD_ISSET函数用法详解 `FD_ISSET`是C/C++网络编程中用于检查文件描述符是否在`select()`系统调用返回的就绪集合中的宏,主要用于多路复用I/O操作。以下为具体用法和示例: #### 1. 函数作用 - **功能**: 判断指定文件描述符是否在`fd_set`集合中被标记为就绪状态(可读/可写/异常)[^1]。 - **调用时机**: 在`select()`返回后,遍历所有监控的文件描述符,检查哪些触发了事件。 - **返回值**: 若文件描述符在集合中则返回非零值,否则返回0。 #### 2. 使用步骤 1. **初始化集合** 使用`FD_ZERO`清空集合,`FD_SET`添加需监控的文件描述符: ```c fd_set read_fds; FD_ZERO(&read_fds); // 清空集合 FD_SET(socket_fd, &read_fds); // 添加socket到读集合 ``` 2. **调用select** 设置超时并监控文件描述符状态变化: ```c struct timeval timeout = {5, 0}; // 5秒超时 int ready = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); ``` 3. **检查就绪状态** 使用`FD_ISSET`遍历检查: ```c if (FD_ISSET(socket_fd, &read_fds)) { // 处理可读事件(如接受新连接或读取数据) } ``` #### 3. 完整示例 ```c #include <sys/select.h> #include <stdio.h> #include <unistd.h> int main() { int server_fd = socket(AF_INET, SOCK_STREAM, 0); // 假设已绑定并监听 fd_set read_fds; while (1) { FD_ZERO(&read_fds); FD_SET(server_fd, &read_fds); int max_fd = server_fd; int ready = select(max_fd + 1, &read_fds, NULL, NULL, NULL); if (ready == -1) { perror("select error"); break; } if (FD_ISSET(server_fd, &read_fds)) { int client_fd = accept(server_fd, NULL, NULL); // 接受新连接 printf("New connection: %d\n", client_fd); } } return 0; } ``` #### 4. 常见问题 - **未重置集合**: 每次调用`select`前需重新调用`FD_ZERO`和`FD_SET`,因为`select`会修改集合内容。 - **性能问题**: 监控大量文件描述符时,建议改用`epoll`(Linux)或`kqueue`(BSD)。 - **返回值处理**: 若`select`返回0表示超时,负数表示错误。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值