Linux多路IO复用:select

1. 多路IO复用

内核监听多个socket文件描述符读写缓冲区属性的变化,若某个文件描述符的读缓冲区有变化,则将该事件告诉应用层。

内核提供多路IO复用的API:select、poll(使用较少)、epoll。


2. select

2.1 select原理

用户态创建文件描述符位图,将需要被监听的文件描述符置1,同时拷贝一份文件描述符位图做备份;

每调用一次select就将该文件描述符位图拷贝给内核,内核监听文件描述符是否有事件,将有事件的文件描述符保留,其余文件描述符置0,然后拷贝给用户态;

用户态遍历内核拷贝过来的文件描述符位图,若lfd(监听文件描述符)有事件则表示有新连接到来,则将新连接的文件描述符加入到备份的文件描述符位图中;

若其他文件描述符有事件,则进行处理;若有客户端关闭,则从备份文件描述符中删除该客户端的文件描述符;

下次再监听时则将备份的文件描述符位图拷贝到内核态进行监听。

2.2 select优缺点

优点:

        ① 是POSIX标准,跨平台较好:Linux、Windows、MAC OS;

缺点:

        ① 文件描述符数最大限制为1024,;可修改,但需要重新编译内核;

        ② 仅返回有变化的文件描述符个数,需要自己遍历才知道是哪个文件描述符发生变化;

        ③ 文件描述符位图需要在用户空间和内核空间来回拷贝;

        ④ 客户端较多,但只有少量客户端活跃时,效率低,因为每次都遍历所有的客户端。

2.3 select API

#include<sys/select.h>
#include<sys/types.h>

int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
/*
功能:
    监听多个文件描述符的属性(读、写、异常)变化。
参数:
    nfds:最大文件描述符+1
    readfds:需要监听的读文件描述符的集合
    writefds:需要监听的写文件描述符的集合,通常为NULL
    exceptfds:需要监听的异常文件描述符的集合,通常为NULL
    timeout:
            > 0:监听超时时间(多久监听一次);
            0:无文件描述符变化则立即返回;
            NULL:阻塞监听到有文件描述符变化才返回。
返回值:
    > 0:变化的文件描述符的个数;
    = 0:没有文件描述符发生变化;
    < 0 :调用发生错误,会设置errno。
*/

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

void FD_CLR(int fd, fd_set* set);  // 从文件描述符位图set中移除文件描述符fd
int FD_ISSET(int fd, fd_set* set); // 判断文件描述符fd是否在文件描述符位图set中
void FD_SET(int fd, fd_set* set);  // 将文件描述符fd添加到文件描述符位图set中
void FD_ZERO(fd_set* set);         // 清空文件描述符位图set

2.4 select实现简单并发服务器示例:

#include<stdio.h>
#include<sys/select.h>
#include<sys/types.h>
#include<sys/time.h>
#include<unistd.h>
#include"wrap.h"

#define PORT 8888

int main(int argc, const char* argv[]) {

    // 1.创建socket,bind
    int lfd = tcp4bind(PORT, NULL);

    // 2.监听
    Listen(lfd, 128);

    // 3.while select监听
    int maxfd = lfd; // 最初监听到最大的文件描述符
    fd_set oldset, rset;
    FD_ZERO(&oldset); // 清空集合
    FD_ZERO(&rset);   // 清空集合
    FD_SET(lfd, &oldset);  // 将lfd加入oldset
    while (1) {

        rset = oldset; // 备份oldset
        int n = select(maxfd + 1, &rset, NULL, NULL, NULL); // select监听
        if (n < 0) {
            perror("select");
            break;
        } else if (0 == n) {
            continue; // 无文件描述符变化则继续监听
        } else { // 有文件描述符发生变化

            if (FD_ISSET(lfd, &rset)) { // lfd有变化, 表示有新连接到来
                struct sockaddr_in cliaddr;
                socklen_t len = sizeof(cliaddr);
                char ip[16] = "";

                // 提取新连接
                int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);

                printf("新连接到来:IP = %s, port = %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16),
                    ntohs(cliaddr.sin_port));
                FD_SET(cfd, &oldset); // 将cfd加入到oldset中
                if (cfd > maxfd) { // 更新maxfd
                    maxfd = cfd;
                }
                if (--n == 0) { // 如果只有lfd有变化,则继续下一轮监听
                    continue;
                }
            }

            // cfd有变化,遍历lfd之后的文件描述符是否有变化
            for (int i = lfd + 1; i <= maxfd; i++) {
                if (FD_ISSET(i, &rset)) { // 若文件描述符i有变化
                    char buf[1500] = ""; // 1500是以太网最大传输单元
                    int ret = Read(i, buf, 1500);
                    if (ret < 0) { // 出错
                        perror("read");
                        close(i);  // 关闭文件描述符
                        FD_CLR(i, &oldset);  // 从oldset中删除该文件描述符
                    } else if (0 == ret) {
                        printf("客户端关闭\n");
                        close(i);  // 关闭文件描述符
                        FD_CLR(i, &oldset);  // 从oldset中删除该文件描述符
                    } else {
                        printf("客户端:%s\n", buf);
                        write(i, buf, ret);
                    }
                }
            }
        }
    }
    return 0;
}

运行结果:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的马师兄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值