IO多路转接之select函数详解

12 篇文章 0 订阅

IO多路转接之select:

函数功能:监视描述符集合中的描述符状态变化。程序会在select函数等待,直到有描述符就绪。

函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

参数:

nfds:当前描述符集合最大描述符+1

readfds:监控可读事件的描述符集合

writefds:监控可写事件的描述符集合

execptfds:监控异常事件的描述符集合

timeout:用来设置监控时间,如果指定时间没有描述符就绪,则超时返回。NULL:表示一直等待,直到有描述符事件就绪。

关于fd_set这个结构体,我们来看一下它的定义:

typedef struct
{
/*XPG4.2requiresthismembername.Otherwiseavoidthename
fromtheglobalnamespace.*/
#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;

typedef long int __fd_mask;

将上述代码简化,我们其实可以得到,fd_set就是一个长整型数组 long int fds_bits[32];而我们用位图来表示描述符,这样我们可以表示的描述符最多有1024个(描述符集合)

fd_set的接口,用来方便操作位图:

void FD_CLR(int fd, fd_set *set);      // 用来清除描述词组set中相关fd的位   
int  FD_ISSET(int fd, fd_set *set);    // 用来测试描述词组set中相关fd的位是否为真   存在则返回非零,不存在返回零。
void FD_SET(int fd, fd_set *set);      // 用来设置描述词组set中相关fd的位   
void FD_ZERO(fd_set *set);             // 用来清除描述词组set的全部位

接下来我们通过代码来详细了解fd_set。

程序运行结果如下:

可以看到fds_bits[0]变为1024,这是因为,我们执行函数FD_SET(10,&fds)后,将fd_set(即fds_bits[0])第十个比特位设置为1

即 00000000 00000000 00000010 00000000 大小为1024。每一个数组元素有32位,即可以表示32个描述符。那么我们将上述程序改为执行FD_SET(32,&fds)程序运行结果会怎样呢?

运行结果如下:

可以看出来,fds_bits[1]变为1,这是因为我们设置第32位的时候,该位置在数组的第二个元素中(即fds_bits[1]),

00000000 00000000 00000000 00000001 大小为1 。

接下来我们再来看timeval结构:

struct timeval {
               long    tv_sec;         /* seconds 秒*/
               long    tv_usec;        /* microseconds 百万分之一秒,微秒*/
           };

我们可以利用timeout这个参数来设定select函数等待的时间,最多可以精确到微秒,这也是select函数的优点之一,一旦超过这个等待时间且没有发生描述符时间就绪,select函数则超时返回0。也可以将timeout设置为NULL,意为一直等待,直到有描述符时间就绪。

函数返回值:函数执行成功则返回就绪描述符个数,返回0代表等待超时。返回非零代表出错,错误原因存于errno。可能的错误: * EBADF ⽂件描述词为⽆效的或该⽂件已关闭 * EINTR 此调⽤被信号所中断。

函数执行过程:函数执行时并不会告诉我们哪个描述符事件就绪了,需要我自己处理,同时函数会将没有就绪的描述符置零。

将fd加⼊select监控集的同时,还要再使⽤⼀个数据结构array保存放到select监控集中的描述符,
⼀是⽤于在select 返回后,array作为源数据和fds进⾏FD_ISSET判断。
⼆是select返回后会把以前加⼊的但并⽆事件发⽣的描述符清空(置零),则每次开始select前都要重新从array取
得描述符逐⼀加⼊(先FD_ZERO),扫描array的同时取得描述符的最大值,用于函数的第一个参数

优缺点:

                 1.select能够支持监控的描述符个数是有限的,由FD_SETSIZE来确定(1024)
                 2.select的原理是轮询判断就绪,描述符越多循环越多,因此性能会随和描述符增多而下降
                 3.select并不会直接告诉用户哪个描述符就绪,因此我们需要遍历所有的描述符看哪一个就绪,因此编码复杂。
                 4.select每次有就绪事件发生都会修改描述符集合(把没有就绪的剔除出去),因此我们要监控的描述符每次循环都需要重新添加
                 5.每次都需要将用户态的监控集合拷贝到内核态

                 1.监控的超时时间比较精细
                 2.select可以跨平台    (win也有)

对于tcp服务器,读事件就绪有两种情况:

1.有客户端新的连接到来——监听描述符读事件就绪

2.有客户端数据到来——客户端描述符读事件就绪

接下来用select编写tcp服务器,实现单线程处理多个客户端连接和数据传输。

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

int main(int argc,char* argv[])
{
    int i,j;
    if(argc!=2)
    {
        printf("./name port!");
        return -1;
    }
    struct sockaddr_in server_addr;
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(atoi(argv[1]));
    server_addr.sin_addr.s_addr=INADDR_ANY;

    int listen_sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(listen_sockfd<0)
    {
        perror("create socket error!");
        return 1;
    }
    if(bind(listen_sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0)
    {
        perror("bind error!");
        return 1;
    }
    if(listen(listen_sockfd,5)<0)
    {
        perror("listen error!");
        return 1;
    }
    fd_set read_fds;
    int fd_list[1024];
    int max_fd;
    for(i=0;i<1024;i++)
    {
        fd_list[i]=-1;
    }
    fd_list[0]=listen_sockfd;
    while(1)
    {
        max_fd=listen_sockfd;
        FD_ZERO(&read_fds);
        for(i=0;i<1024;i++)
        {
            if(fd_list[i]!=-1)
            {
                FD_SET(fd_list[i],&read_fds);
                max_fd=max_fd>fd_list[i]?max_fd:fd_list[i];
            }
        }
        struct timeval tv;
        tv.tv_sec=3;
        tv.tv_usec=0;
        int ret=select(max_fd+1,&read_fds,NULL,NULL,&tv);
        if(ret<0)
        {
            perror("select error!");
            continue;
        }
        else if(ret==0)
        {
            printf("no sockfd ready\n");
            continue;
        }
        for(i=0;i<max_fd+1;i++)
        {
            if(FD_ISSET(i,&read_fds))
            {
                if(i==listen_sockfd)
                {
                    struct sockaddr_in client_addr;
                    socklen_t len =sizeof(client_addr);
                    int new_fd=accept(listen_sockfd,(struct sockaddr*)&client_addr,&len);
                    if(new_fd<0)
                    {
                        perror("accepr error!");
                        continue;
                    }
                    printf("a new client connect!\n");
                    for(j=0;j<1024;j++)
                    {
                        if(fd_list[j]==-1)
                        {
                            fd_list[j]=new_fd;
                            max_fd=max_fd>new_fd?max_fd:new_fd;
                            break;
                        }
                    }

                }
                else
                {
                    char buff[1024]={0};
                    int rlen=recv(i,buff,1023,0);
                    if(rlen<=0)
                    {
                        if(errno==EAGAIN||errno==EINTR)
                        {
                            continue;
                        }
                        perror("recv error!");
                        close(i);
                        for(j=0;j<1024;j++)
                        {
                            if(i==fd_list[j])
                            {
                                fd_list[j]=-1;
                            }
                        }
                    }
                    printf("client: %s\n",buff);
                }
            }
        }
    }


    close(listen_sockfd);
    return 0;
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`select` 函数是一种 I/O 多路复用的机制,用于同时监听多个文件描述符的状态变化。它可以使用单个系统调用同时监视多个文件描述符,并在有一个或多个文件描述符就绪时通知应用程序。 `select` 函数的原型如下: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - `nfds`:待监视的最大文件描述符加 1。 - `readfds`:可读文件描述符集合。 - `writefds`:可写文件描述符集合。 - `exceptfds`:异常条件文件描述符集合。 - `timeout`:超时时间,如果为 `NULL` 则为阻塞模式,即一直等待直到有文件描述符就绪;如果为零时间(`tv_sec` 和 `tv_usec` 均为 0),则为非阻塞模式,即立即返回;否则为指定超时时间。 `select` 函数的返回值表示就绪文件描述符的数量,如果返回值为 0,则表示超时;如果返回值为 -1,则表示出错。 使用 `select` 函数的一般流程如下: 1. 初始化需要监视的文件描述符集合。 2. 调用 `select` 函数等待文件描述符就绪。 3. 检查返回值确定哪些文件描述符已经就绪。 4. 处理就绪的文件描述符。 5. 重复上述步骤。 需要注意的是,`select` 函数有一些限制,比如只能监视的文件描述符数量有限,一般为 1024 或更小。此外,在某些平台上,使用 `select` 函数可能会有性能上的限制,可以考虑使用更高效的机制,如 `poll` 或 `epoll`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值