select

select
(IO多路复用)


  • select系统调⽤是⽤来让我们的程序监视多个⽂件描述符的状态变化的;
  • 程序会停在select这⾥等待,直到被监视的⽂件描述符有⼀个或多个发⽣了状态改变;

#include <sys/select.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout);
//fd_set文件描述符集合 ( 位图 )

参数:
1. 需要监视的集合里边的最大值文件描述符 +1(减少遍历次数,增加效率)
2. 文件描述符集合的 读就绪
3. 文件描述符集合的 写就绪
4. 文件描述符集合的 异常就绪
5. timeout取值

  • NULL:则表⽰select()没有timeout,select将⼀直被阻塞,直到某个⽂件描述符上发⽣了事件;
  • 0:仅检测描述符集合的状态,然后⽴即返回,并不等待外部事件的发⽣。
  • 特定的时间值:如果在指定的时间段⾥没有事件发⽣,select将超时返回

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的全部位


读就绪

  • socket内核中, 接收缓冲区中的字节数, ⼤于等于低⽔位标记SO_RCVLOWAT. 此时可以⽆阻塞的读该⽂件描述符, 并且返回值⼤于0;
  • socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
  • 监听的socket上有新的连接请求;
  • socket上有未处理的错误;

写就绪

  • socket内核中, 发送缓冲区中的可⽤字节数(发送缓冲区的空闲位置⼤⼩), ⼤于等于低⽔位标记SO_SNDLOWAT, 此时可以⽆阻塞的写, 并且返回值⼤于0;
  • socket的写操作被关闭(close或者shutdown). 对⼀个写操作被关闭的socket进⾏写操作, 会触发SIGPIPE信号;
  • socket使⽤⾮阻塞connect连接成功或失败之后;
  • socket上有未读取的错误

异常就绪

  • TCP首部的紧急指针

select实现多客户端访问的回显服务器

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

typedef struct sockaddr sockaddr; 
typedef struct sockaddr_in sockaddr_in;


typedef struct FdSet{
    fd_set set;
    int max_fd;
} FdSet;

void FdSetInit(FdSet* fds){
    fds->max_fd = -1;
    FD_ZERO(&fds->set);
}

void FdSetAdd(FdSet* fds,int fd){
    FD_SET(fd,&fds->set);
    if(fd > fds->max_fd){
        fds->max_fd = fd;
    }
}

//删除完找最大,此处采用从前遍历,还可采用从后遍历
//后效率相对高
void FdSetDel(FdSet* fds,int fd){
    FD_CLR(fd,&fds->set);
    int maxfd = -1;
    int i = 0;
    for(;i <= fds->max_fd;++i){
        if(!FD_ISSET(i,&fds->set)){
            continue;
        }
        if(i > maxfd){
            maxfd = i;
        }
    }
    fds->max_fd = maxfd;
}




int ServerInit(const char* ip,short port){
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd < 0){
        perror("socket");
        return -1;
    }
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);
    addr.sin_port = htons(port);

    int ret = bind(fd,(sockaddr*)&addr,sizeof(addr));
    if(ret < 0){
        perror("bind");
        return -1;
    }
    ret = listen(fd,10);
    if(ret < 0){
        perror("listen");
        return -1;
    }
    return fd;

}

int ProcessRequest(int new_sock){
    char buf[1024] = {0};
    ssize_t read_size = read(new_sock,buf,sizeof(buf)-1);
    if(read_size < 0){
        perror("read");
        return -1;
    }
    if(read_size == 0){
        printf("[client %d] disconnected\n",new_sock);
        //如果没有关闭文件描述符,即四次挥手没有完成,文件描述符泄露,服务器出现大量的CLOSE_WAIT
        close(new_sock);
        return 0;
    }

    buf[read_size] = '\0';
    printf("[client %d]%s\n",new_sock,buf);
    write(new_sock,buf,strlen(buf));
    return 1;
}


int main(int argc,char* argv[]){
    if(argc != 3){
        printf("Usage: ./server [IP] [prot]\n");
        return 1;
    }

    int listen_sock = ServerInit(argv[1],atoi(argv[2]));

    if(listen_sock < 0){
        printf("ServerInit faild\n");
        return 1;
    }
    printf("ServerInit OK!\n");

    FdSet fds;
    FdSetInit(&fds);
    FdSetAdd(&fds,listen_sock);

    while(1){

    //备份 解决文件描述符丢失
    FdSet tmp = fds;


    int ret = select(fds.max_fd + 1,&tmp.set,NULL,NULL,NULL);
         if(ret < 0){
            perror("select");
            continue;
        }
    if(FD_ISSET(listen_sock,&tmp.set)){
        //有新客户端连上服务器
        sockaddr_in  peer;
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock,(sockaddr*)&peer,&len);
        if(new_sock < 0 ){
            perror("accept");
            continue;
        }
        FdSetAdd(&fds,new_sock);
        printf("[client %d] connect!\n",new_sock);
    }
    else{
        int i = 0;
        for(;i<=tmp.max_fd;++i ){
            if(!FD_ISSET(i,&tmp.set)){
                continue;
            }
           int ret =  ProcessRequest(i);


           if(ret == 0){
               FdSetDel(&fds,i);
           }
        }
    }//else
 }
    return 0;
}

select特点

  • 可监控的⽂件描述符个数取决与sizeof(fd_set)的值. (centos7测 128*8 = 1024)(位图,故*8)

    • 将fd加⼊select监控集的同时,还要再使⽤⼀个数据结构array保存放到select监控集中的fd,
    • ⼀是⽤于再select 返回后,array作为源数据和fdset进⾏FDISSET判断。
    • ⼆(备份)是select返回后会把以前加⼊的但并⽆事件发⽣的fd清空,则每次开始select前都要重新从array取得fd逐⼀加⼊(FD_ZERO最先),扫描array的同时取得fd最⼤值maxfd,⽤于select的第⼀个参数。
      这里写图片描述

select缺点

  • 每次调⽤select, 都需要⼿动设置fd集合, 从接⼝使⽤⾓度来说也⾮常不便.
  • 每次调⽤select,都需要把fd集合从⽤户态拷⻉到内核态,这个开销在fd很多时会很⼤
  • 同时每次调⽤select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很⼤
  • select⽀持的⽂件描述符数量太⼩
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值