linux基础——linux下多路IO复用接口之select/poll

函数select和poll用于IO复用,它们都是监视一个或多个文件描述符集合,可以进行IO操作的时候就返回。

一、函数select()
select允许内核等待多个事件中的任何一个发生,并且只要有一个或多个事件发生或者经历了一段指定的时间后才唤醒它。
函数原形如下:
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
函数select的参数含义如下:
nfds:要监视的文件描述符集合中的文件描述符的最大值加1。
readfds:设置需要检测有数据可读的文件描述符集合。
writefds:设置需要检测有数据可写的文件描述符集合。
exceptfds:设置需要检测出错的文件描述符集合。
注意: readfds、writefds和exceptfds必须在每次select前设置要监视的文件描述符的集合,select返回的时候,内核会清除掉其中没有发生事件的文件描述符,只留下发生事件的文件描述符。
timeout:设置select调用的等待时间,-1表示永久阻塞,0代表无等待,正数代表等待相应的时长直到有事件发生或出错才返回。
timeout结构如下:
struct timeval
{
    time_t tv_sec;//秒
    long tv_usec;//微秒
}
fd_set是可以直接进行赋值的,对文件描述符集合fd_set操作的的4个函数:
FD_ZERO(fd_set *fdset):清理文件描述符集合。
FD_SET(int fd, fd_set *fdset):向某个文件描述符集合中加入文件描述符。
FD_CLR(int fd, fd_set *fdset):向某个文件描述符的集合中取出某个文件描述符。
FD_ISSET(int fd, fd_set *fdset):测试某个文件描述符是否是某个集合中的一员。
常用框架:
...  
int client_fds[CLIENTNUM];  
while (true)  
{  
    //每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦 
    maxfd = -1;//最大文件描述符初始化为-1  
    FD_ZERO(read_fds);  
    FD_SET(srvfd, read_fds);//添加要监听的套接字  
    maxfd = srvfd;  
  
    tv.tv_sec = 30;//设置select等待时长  
    timeout.tv_sec = 1;//阻塞1秒后超时返回      
    timeout.tv_usec = 0;      
  
    //添加已经连接上的客户端套接字到要监听的集合中  
    for (i = 0; i < CLIENTNUM; i++)  
    {  
        if (client_fds[i] != -1)//合法的文件描述符  
        {  
            FD_SET(client_fds[i], &read_fds);  
            FD_SET(client_fds[i], &write_fds);  
            if (maxfd < client_fds[i])  
            {  
                maxfd = client_fds[i];  
            }  
        }  
    }  
  
    ret = select(maxfd + 1, &read_fds, &write_fds, NULL, &timeout);  
  
    if (ret == -1)//错误发生  
    {  
        fprintf(stderr, "select error:%s.\n", strerror(errno));  
        return;  
    }  
    if (ret == 0)//超时  
    {  
        fprintf(stdout, "select is timeout.\n");  
        continue;  
    }  
  
    if (FD_ISSET(srvfd, read_fds))  
    {  
        accept_client_proc(srvfd);//监听客户端请求  
    }  
    else  
    {  
        recv_client_msg(read_fds, write_fds);//接受处理客户端消息(可读or可写)  
    }  
}  

accept_client_proc函数如下:

static int accept_client_proc(int client_fds[], int srvfd)
{
    ...
    while (true)
    {
        clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen);

        if (clifd == -1)
        {
            ...//出错进行处理
        }

        fprintf(stdout, "accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);

        //将新的连接描述符添加到数组中
        int i;
        for (i = 0; i < CLIENTNUM; i++)
        {
            if (client_fds[i] != -1)
            {
                client_fds[i] = clifd;
                return 1;
            }
        }

        if (i == CLIENTNUM)
        {
            fprintf(stderr,"too many clients.\n");
            return -1;
        }
    }
}

recv_client_msg函数如下:

static void recv_client_msg(int client_fds[], fd_set *read_fds, fd_set *write_fds)
{
    int i = 0, n = 0;
    int clifd;
    char buf[MAXLINE] = {0};
    for (i = 0;i < CLIENTNUM;i++)
    {
        clifd = client_fds[i];

        if (clifd < 0)
        {
            continue;
        }

        if (FD_ISSET(clifd, read_fds))
        {
            ...//对发生读事件的文件描述符进行处理
        }
        if(FD_ISSET(clifd, write_fds))
        {
            ...//对发生写事件的文件描述符进行处理
        }
    }
}


二、函数poll()
poll函数是监视在fds数组指明的一组文件描述符上发生的动作,当满足条件或者超时的时候回退出。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
poll函数监视在fds数组指明的一组文件描述符上发生的动作,当满足条件或者超时时退出。
fds:指向结构体pollfd数组的指针,监视的文件描述符和条件放在里面
nfds:是比监视的最大描述符大1的值
timeout:超时时间,单位毫秒,负值表示永远等待。
struct pollfd
{
    int fd;//要监视的文件描述符
    short events;//请求的事件,请求监视的事件,输入参数
    short revents;//返回的事件,返回的具体事件,输出参数
};
常用框架:
...
struct pollfd client_fds[CLIENTNUM];
int maxfd;
//添加监听描述符
clientfds[0].fd = listenfd;
clientfds[0].events = POLLIN;
//初始化客户连接描述符
for (int i = 1; i < CLIENTNUM; i++)
{
    client_fds[i].fd = -1;
}
maxfd = 0;
//循环处理
while (true)
{
    ret = poll(client_fds, maxfd + 1, -1);//永久等待直到有描述符可用或出错才返回

    if (ret == -1)
    {
        perror("poll error:");
        return -1;
    }
    //测试监听描述符是否准备好
    if (client_fds[0].revents & POLLIN)
    {
        cliaddrlen = sizeof(cliaddr);
        //接受新的连接
        if ((connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddrlen)) == -1)
        {
            if (errno == EINTR)
            {
                continue;
            }
            else
            {
               perror("accept error:");
               return -1;
            }
        }
        fprintf(stdout, "accept a new client: %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
        //将新的连接描述符添加到数组中
        for (int i = 1; i < CLIENTNUM; i++)
        {
            if (client_fds[i].fd < 0)
            {
                client_fds[i].fd = connfd;
                break;
            }
        }
        if (i == CLIENTNUM)
        {
            fprintf(stderr,"too many clients.\n");
            return -1;
        }
        
        client_fds[i].events = POLLIN;//将新的描述符添加到可读描述符集合中

        //记录描述符的最大值
        if (maxfd < client_fds[i].fd)
        {
            maxfd = client_fds[i].fd;
        }
    }
    //处理客户连接
    handle_connection(client_fds, maxfd);
}
handle_connection函数如下:
static void handle_connection(struct pollfd *connfds,int num)
{
    for (int i = 1; i <= num; i++)
    {
        if (connfds[i].fd < 0)
        {
            continue;//描述符不合法
        }
        if (connfds[i].revents & POLLIN)
        {
            ...//对发生读事件的文件描述符进行处理
        }
        if (connfds[i].revents & POLLOUT)
        {
            ...//对发生写事件的文件描述符进行处理
        }
    }
}


参考:
《UNIX环境高级编程》
《linux网络编程》

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值