• Linux C++ 网络编程学习系列(2)——多路IO之select实现


    select实现多路IO

    1. 源码地址:https://github.com/whuwzp/linuxc/tree/master/select
    2. 源码说明:
      • server.cpp: 监听127.1:6666,功能是将收到的小写转大写
      • include/wrap.cpp: 封装的一些socket基本操作,加了基本的错误处理

    1. 概要

    int select(int nfds, fd_set *restrict readfds,
           fd_set *restrict writefds, fd_set *restrict errorfds,
           struct timeval *restrict timeout);
    void FD_CLR(int fd, fd_set *fdset);
    int FD_ISSET(int fd, fd_set *fdset);
    void FD_SET(int fd, fd_set *fdset);
    void FD_ZERO(fd_set *fdset);
    

    1.1 select函数

    //原型
    int select(int nfds, fd_set *restrict readfds,
           fd_set *restrict writefds, fd_set *restrict errorfds,
           struct timeval *restrict timeout);
    //我的代码中的
    nselect = select(fd_max + 1, &readfds, nullptr, nullptr, nullptr);
    
    1. fd_set类型数据: 用位图表示1024个fd(文件描述符)集合,sizeof看了该类型大小为128字节,也就是1024bit,应该是第n个bit表示fd为n的文件描述符,这也符合默认select最多可以监听1024个fd
    2. readfds,writefds,errorfds: 分别是读,写,错误的集合
    3. 以上的fds是输入输出参数,既是函数输入又是输出(以下以readfds为例)
      • 输入: select依次去看readfds的1024bit,谁为1(或为0,这是猜想的),如果为1,假设第n个为1,那么select就会监听fd==n的文件描述符
      • 输出: 如果监听的可读的话,那么就把这个bit置为1,假设第n个为1,则表示fd==n的文件描述符可读
    4. timeout: 就是超时返回, nullptr是永久等待
    5. nfds: 最大的文件描述符,应该是为了节约时间吧,这样就不用去轮询1024个fd,例如只有一个fd为5,那么只用轮询5前的几个,5之后就不用管了

    注意:

    1. 由于fds是输入输出参数,所以每次select之后fds的内容都可能被修改,所以下次select前需要给fds重新赋值
    2. FD_SETSIZE默认就是1024

    1.2 FD_xxx函数

    void FD_CLR(int fd, fd_set *fdset);
    int FD_ISSET(int fd, fd_set *fdset);
    void FD_SET(int fd, fd_set *fdset);
    void FD_ZERO(fd_set *fdset);
    
    • FD_CLR: 清除fdset中的fd这个文件描述符,看看fd_set函数,这个实现应该很简单,就是直接第fd个bit置为0
    • FD_ISSET: 判断fd是不是在fdset中,这个用于判断select之后,fd是否有信号到来,实现也很简单,就是看看第fd个bit是不是1
    • FD_SET: 这个是把fd加入到fdset中,一般在select前,设置需要监听的fds
    • FD_ZERO: 清零

    2. 核心代码

    #include "include/wrap.h"
    #include <arpa/inet.h>
    #include <ctype.h>
    #include <errno.h>
    #include <netinet/in.h>
    #include <pthread.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <stdnoreturn.h>
    #include <string.h>
    #include <sys/select.h>
    #include <sys/socket.h>
    #include <sys/types.h> /* See NOTES */
    #include <unistd.h>
    #include <wait.h>
    #define LOCALIP "127.0.0.1"
    #define PORT 6666
    
    void handler(char *in, char *out) {
        for (int i = 0; i < (int)strlen(out) + 1; ++i) {
            out[i] = toupper(in[i]);
        }
    }
    
    int workthread(const int &fd_client) {
        char recvbuf[2048] = {0};
        char sendbuf[2048] = {0};
        int  ret           = 0;
    
        ret = (int)Read(fd_client, recvbuf, 2048);
        if (ret <= 0) {
            printf("ret==0
    ");
            return ret;
        }
    
        handler(recvbuf, sendbuf);
    
        ret = (int)Write(fd_client, sendbuf, strlen(sendbuf) + 1);
        return ret;
    }
    
    void startsock(int &fd, struct sockaddr_in &addr, const char *ip,
                   const int port) {
        fd = Socket(AF_INET, SOCK_STREAM, 0);
        memset(&addr, 0, sizeof(addr));
        addr.sin_family      = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip);
        addr.sin_port        = htons(port);
    }
    int main() {
        int                fd_server = 0;
        int                fd_client = 0;
        int                ret       = 0;
        struct sockaddr_in sock_client;
        struct sockaddr_in sock_server;
        socklen_t          client_len = (socklen_t)sizeof(sock_client);
        int                opt        = 0;
        fd_set             readfds;//作为select的参数
        fd_set             allfds;//保存fds,因为readfds会被修改, 见 1.1 select 注意
        int                fd_clients[FD_SETSIZE]; //用于保存客户端连接的fd
        memset(fd_clients, -1, sizeof(fd_clients)); //用-1表明是没有用过的fd,方便后面找空位
        int maxi    = 0;
        int fd_max  = 0;
        int nselect = 0;
        int i       = 0;
    
        startsock(fd_server, sock_server, LOCALIP, PORT);
        opt = 1;
        Setsockopt(fd_server, SOL_SOCKET, SO_REUSEADDR, &opt,
                   (socklen_t)sizeof(opt));
        Bind(fd_server, (struct sockaddr *)&sock_server, sizeof(sock_server));
        Listen(fd_server, 5);
    
        FD_ZERO(&allfds);
        FD_ZERO(&readfds);
        FD_SET(fd_server, &allfds);//把listenfd加入待监听的
        fd_max = fd_server;
    
        while (true) {
            readfds = allfds; //见 1.1 select 注意
            printf("selecting...
    ");
            nselect = select(fd_max + 1, &readfds, nullptr, nullptr, nullptr);
            if (nselect == -1) {
                if (errno == EINTR) {
                    continue;
                } else {
                    perror_exit("select failed");
                }
            }
            printf("get %d select
    ", nselect);
            if (FD_ISSET(fd_server, &readfds)) { //看看listenfd有没有信号,就是有没有新连接
                fd_client =
                    Accept(fd_server, (struct sockaddr *)&sock_client, &client_len);
                printf("accept: %s: %d
    ", inet_ntoa(sock_client.sin_addr),
                       ntohs(sock_client.sin_port));
                for (i = 0; i < FD_SETSIZE; ++i) {
                    if (fd_clients[i] != -1) continue; //找数组中没有用过的空位存fd_client
                    fd_clients[i] = fd_client;
                    break;
                }
                printf("i: %d, FD_SETSIZE: %d
    ", i, FD_SETSIZE);
                if (i == FD_SETSIZE) perror_exit("too many clients");//超过1024
                if (i > maxi) maxi = i;
                if (fd_client > fd_max) fd_max = fd_client;//找最大的fd
                FD_SET(fd_client, &allfds);//加入监听集合
                nselect--;
            }
            printf("going to find client, maxi: %d, nselect: %d
    ", maxi, nselect);
            for (i = 0; (i <= maxi) && (nselect > 0); ++i) {//maxi为了节约时间,不用轮询1024次
                if (FD_ISSET(fd_clients[i], &readfds) == 0) continue;//轮询哪个clientfd有信号
                printf("find client %d
    ", i + 1);
               ret = workthread(fd_clients[i]);//处理
                if (ret <= 0) {//断开
                    Close(fd_clients[i]);
                    FD_CLR(fd_clients[i], &allfds);
                    fd_clients[i] = -1;
                }
                nselect--;
            }
            sleep(3);
        }
        Close(fd_server);
        for (i = 0; i < FD_SETSIZE; ++i) {
            if (fd_clients[i] == -1) continue;
            Close(fd_clients[i]);
        }
    }
    

    注意: 代码中maxi和fd_max的操作看上去很繁琐,其实都是为了节约时间,例如

    1. maxi是标记当前fd_clients中用到的最大索引值,这样我们找哪个fd有信号时,就不用轮询fd_clients中1024个fd了;
    2. fd_max是最大的fd,这样nfds就可以方便设置,select就不用监听1024个
    3. 如果觉得繁琐,其实可以全部改成1024,就是浪费

    3. 参考网址

    1. https://www.cnblogs.com/alantu2018/p/8612722.html#top
    2. https://www.bilibili.com/video/av53016117
  • 相关阅读:
    [转]ARM平台下独占访问指令LDREX和STREX
    ARM MMU
    在字符串中查找子字符串并提取它
    获得字符串的长度
    连接字符串
    循环用于迭代数组中的项目
    循环的标准
    if ... else 语句
    添加一个图像切换器
    css 中的z-index
  • 原文地址:https://www.cnblogs.com/whuwzp/p/linux_cpp_network_2.html
Copyright © 2020-2023  润新知