Linux 下select 模型

系统提供select函数来实现多路复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。

函数原型:


  1. int select(int maxfd,fd_set *rdset,fd_set *wrset,  fd_set *exset,struct timeval *timeout);  


参数说明:

maxfd是需要监视的最大的文件描述符值+1;数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset, writeset, exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的 )。设这个值是为了提高效率,使函数不必检查fd_set的所有1024位。

readset: 用来检查可读性的一组文件描述字。

writeset: 用来检查可写性的一组文件描述字。

exceptset: 用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)

关于文件句柄,其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr。


fd_set 

#define __NFDBITS (8 * sizeof(unsigned long))                //每个ulong型可以表示多少个bit,
#define __FD_SETSIZE 1024                                          //socket最大取值为1024
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)     //bitmap一共有1024个bit,共需要多少个ulong
 
typedef struct {
    unsigned long fds_bits [__FDSET_LONGS];                 //用ulong数组来表示bitmap
} __kernel_fd_set;
 
typedef __kernel_fd_set   fd_set;


 timeout为结构timeval,用来设置select()的等待时间,其结构定义如下:


  1. struct timeval  
  2. {  
  3.     time_t tv_sec;//second  
  4.     time_t tv_usec;//minisecond  
  5. }; 

如果参数timeout设为:

    1.  timeout = NULL (阻塞:直到有一个fd位被置为1函数才返回)

    2.  timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置为1或者时间耗尽,函数均返回)

    3.  timeout所指向的结构,时间设为0(非阻塞:函数检查完每一个fd后立即返回)

     


下面的宏提供了处理这三种描述词组的方式:


FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位

对应的操作如下:

1
2
3
4
5
6
7
//每个ulong为32位,可以表示32个bit。
//fd  >> 5 即 fd / 32,找到对应的ulong下标i;fd & 31 即fd % 32,找到在ulong[i]内部的位置
 
#define __FD_SET(fd, fdsetp)   (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31)))             //设置对应的bit
#define __FD_CLR(fd, fdsetp)   (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31)))            //清除对应的bit
#define __FD_ISSET(fd, fdsetp)   ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0)     //判断对应的bit是否为1
#define __FD_ZERO(fdsetp)   (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp))))                             //memset bitmap


理解fd_set

 为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。

(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。

(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)

(3)若再加入fd=2,fd=1,则set变为0001,0011

(4)执行select(6,&set,0,0,0)阻塞等待

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。


 基于上面的讨论,可以轻松得出select模型的特点:

  (1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务 器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽 然可调,但调整上限受于编译内核时的变量值。

  (2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select 返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个 参数。

  (3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有事件发生)。





函数返回值:

1. 执行成功则返回文件描述词状态已改变的个数,

2. 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回;

3. 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。

错误值可能为:
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足

常见的程序片段如下:

fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}



服务端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <sys/time.h>
#include <arpa/inet.h>

#define backlog 5
#define BUFFSIZE 1024

int create_listenfd(const char *ip, unsigned short port)
{
    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == listenfd)
    {
        perror("socket");
        return -1;
    }
    
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = (ip == NULL ? INADDR_ANY :inet_addr(ip));
    
 
    if(-1 == bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)))
    {
        perror("bind");
        close(listenfd);
        return -1;
    }
    printf("bind success!\n");
    
 
    if(-1 == listen(listenfd, backlog))
    {
        perror("listen");
        close(listenfd);
        return -1;
    }
    printf("listening ...\n");
    
    return listenfd;
}

int main(int argc, char *argv[])
{
    int listenfd = create_listenfd(NULL,atoi(argv[2]));
    if(-1 == listenfd) 
    {
        return 0;
    }
    
    char buff[BUFFSIZE];
    int recvbytes, sendbytes;
    fd_set readfds, tmpfds;
    int maxfd  = listenfd;
    printf("maxfd = %d\n",maxfd);

    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);
    FD_SET(listenfd, &readfds);

    while(1)
    {
        tmpfds = readfds;

        struct timeval tm = {
            .tv_sec = 3,
            .tv_usec = 0
        };
    
        int ret = select(maxfd+1, &tmpfds, NULL, NULL, &tm);
        if(-1 == ret)
        {
            perror("select");
            break;
        }
        else if(0 == ret)
        {
            printf("timeout...\n");
        }
        else
        {
            for(int i = 0; i <= maxfd; i++)
            {
                if(FD_ISSET(i, &tmpfds))
                {
                    if(i == STDIN_FILENO)
                    {
                        fgets(buff, sizeof(buff), stdin);
                        printf("gets:%s",buff);
                    }
                    else if(i == listenfd)
                    {
                        int connectfd = accept(listenfd, NULL, NULL);
                        if(-1 == connectfd)
                        {
                            perror("accept");
                            close(listenfd);
                            return 0;
                        }
                        printf("A new client(fd=%d) is connect success!\n", connectfd);
                        
                        FD_SET(connectfd, &readfds);
                        maxfd = (connectfd > listenfd ? connectfd : listenfd);
                        printf("new maxfd = %d\n",maxfd);
                    }
                    else
                    {
                        recvbytes = recv(i, buff, sizeof(buff), 0);
                        if(recvbytes < 0)
                        {
                            perror("recv");
                            FD_CLR(i, &readfds);
                            close(i);
                            break;
                        }
                        if(recvbytes == 0)
                        {
                            printf("client(fd=%d) is closed!\n", i);
                            FD_CLR(i, &readfds);
                            close(i);
                            break;
                        }
                        printf("server recv from client(fd=%d):%s", i, buff);
                    }
                    
                }
            }
        }
    }
    
    close(listenfd);

    return 0;
}


客户端

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#define backlog 5
#define BUFFSIZE 1024

int main(int argc, char *argv[])
{
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(atoi(argv[2]));
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    if(-1 == connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)))
    {
        perror("connect");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    printf("connect success!\n");

    char buff[BUFFSIZE];
    int recvbytes, sendbytes;
    while(1)
    {
        fgets(buff, sizeof(buff), stdin);
        if(0 == strncmp(buff, "quit", 4))
        {
            printf("client quit!\n");
            break;
        }
        sendbytes = send(sockfd, buff, strlen(buff)+1, 0);
        if(sendbytes <= 0)
        {
            perror("send");
            break;
        }
    }

    close(sockfd);

    return 0;
}


运行


 ./server 127.0.0.1 8888
 ./client 127.0.0.1 8888





已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页