I/O复用——select(全)

目录

select  API 

select 原理

select 编程

select 总结

select  API 

select系统调用的原型如下:

头文件 #include<sys/select.h>

int select(int maxfd,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout)

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

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

readfds、writefds 和 exceptfds: 指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件描述符。select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。
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_SETSZIE指定,这就限制了select能同时处理的文件描述符的总量;

由于位的操作的过于频繁,使用下面的一系列宏来访问fd_set结构体中的位:

#include <sys.select.h>
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是否被设置

timeout参数:设置select函数的超时时间。它是一个timeval结构体类型的指针,采用指针参数是因为内核将修改它以告诉应用程序select等待了多久。不过我们不能完全信任select调用返回后的timeout值,比如调用失败后timeout值是不确定的。

timeval结构体定义如下:

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

select提供了一个微秒级别的定时方式。如果给timeout变量的tv_sec成员和tv_usec成员都传递0,则select将立即返回。如果给timeout传递为NULL,则select将一直阻塞,直到某个文件描述符就绪。

select原理

假设用户空间 服务端监听四个socket对应的fd;

  • (1)首先拷贝fd进入内核空间 ;
  • (2)内核空间进行顺序检查,检查是否有就绪的fd;
  • (3)内核空间将就绪的fd的数量返回用户空间;

如果内核检查没有就绪的fd; 

如果内核检查没有就绪的fd,则select会阻塞,如果对应的socket有数据包到达;检查socket对应的等待队列是否有进程阻塞;有的话唤醒该进程,进程将再次检查是否有就绪的描述符;如果有就绪的fd,则会标记该fd并且返回给用户空间;

我们看以看出来 select函数返回值是int,因此返回的是文件描述符的数量;通过设置fd_set对应的数组;

select编程

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

#define FD_MAX    10
int socket_init();
void fds_init(int fds[])
{
    for( int i = 0; i < FD_MAX; i++ )
    {
        fds[i] = -1;
    }
}
void fds_add( int fds[], int fd)
{
    for( int i = 0; i < FD_MAX; i++ )
    {
        if ( fds[i] == -1 )
        {
            fds[i] = fd;
            break;
        }
    }
}

void fds_del( int fds[], int fd)
{
    for( int i = 0; i < FD_MAX; i++ )
    {
        if( fds[i] == fd )
        {
            fds[i] = -1;
            break;
        }
    }
}
int main()
{
    int sockfd = socket_init();
    if( sockfd == -1 )
    {
        printf("socket init failed\n");
        exit(1);
    }

    int fds[FD_MAX];
    fds_init(fds);
    fds_add(fds,sockfd);

    fd_set fdset; 

    while( 1 )
    {
        FD_ZERO(&fdset);
        int maxfd = -1;
        for( int i = 0; i < FD_MAX; i++ )
        {
            if ( fds[i] == -1 )
            {
                continue;
            }

            FD_SET(fds[i],&fdset);
            if ( maxfd < fds[i] )
            {
                maxfd = fds[i];
            }

        }

        struct timeval tv = {5,0};
        int n = select(maxfd+1,&fdset,NULL,NULL,&tv);
        if ( n == -1 )
        {
            continue;
        }
        else if ( n == 0 )
        {
            printf("time out\n");
            continue;
        }
        else
        {
            for( int i = 0; i < FD_MAX; i++ )
            {
                if ( fds[i] == -1 )
                {
                    continue;
                }

                if ( FD_ISSET(fds[i],&fdset) )
                {
                    if ( fds[i] == sockfd )
                    {
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
                        if ( c < 0 )
                        {
                            continue;
                        }
                        printf("accept c=%d\n",c);
                        fds_add(fds,c);
                    }
                    else
                    {
                        char buff[128] = {0};
                        int num = recv(fds[i],buff,127,0);
                        if ( num <= 0 )
                        {
                            close(fds[i]);
                            fds_del(fds,fds[i]);
                            printf("client close\n");
                        }
                        else
                        {
                            printf("recv(%d)=%s\n",fds[i],buff);
                            send(fds[i],"ok",2,0);
                        }
                    }
                }
            }
        }
    }


}
int socket_init()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if( sockfd == -1 )
    {
        return -1;
    }
    
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if ( res == -1 )
    {
        return -1;
    }

    res = listen(sockfd,5);
    if ( res == -1 )
    {
        return -1;
    }

    return sockfd;
}

select总结

select将socket是否就绪检查逻辑下沉到操作系统层面,避免了系统调用,只告诉告诉用户有事件就绪,但是没有告诉用户具体那个FD;

  • 优点:不需要每个FD都进行依次系统调用,解决了频繁的用户态内核态切换的问题;
  • 缺点:单进程监听的FD存在限制,默认1024;(可以修改)
  •            每次调用需要将FD从用用户态拷贝到内核态;
  •            不知道具体的那个文件描述符就绪,需要遍历全部文件描述符;
  •            入参的3个fd_set集合每次调用都要重置;
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值