• Linux网络编程3-select模型



    1.select函数

    #include 
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
    /*
    函数作用:委托内核监控可读、可写、异常事件
    
    函数参数:
    	nfds:输入参数,告诉内核要监控文件描述符的范围,一般取值为最大文件描述符+1
    	readfds:
    		输入参数:告诉内核要监控哪些文件描述符
    		输出参数:内核告诉应用程序哪些文件描述符有变化
    	writefds:
    		输入参数:告诉内核要监控哪些文件描述符
    		输出参数:内核告诉应用程序哪些文件描述符有变化
    	exceptfds:
    		输入输出参数,一般表示异常事件
    	timeout:超时时间:
    		NULL:表示永久阻塞,直到有事件发生
    		0:表示不阻塞,不管有没有事件发生,都会立刻返回
    		>0:表示阻塞的时长,若没有超过超时时长,则一直阻塞
    			若在时长内,有事件发生,则立刻返回,
    			若超过时长,则立刻返回
    
    返回值:
    	成功返回发生变化的文件描述符个数。
    	
    */
    
    
    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);           // 清空文件描述符集
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    2.使用select模型-服务器端开发流程

    //1 创建socket, 得到监听文件描述符lfd---socket()
    //2 设置端口复用-----setsockopt()
    //3 将lfd和IP  PORT绑定----bind()
    //4 设置监听---listen()
    //5 
    fd_set readfds;  //定义文件描述符集变量
    fd_set tmpfds;
    FD_ZERO(&readfds);  //清空文件描述符集变量
    FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
    maxfd = lfd;
    while(1)
    {
        tmpfds = readfds;
        nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
        if(nready<0)
        {
            if(errno==EINTR)//被信号中断
            {
                continue;
            }
            break;
        }
        
        //有客户端连接请求到来
        if(FD_ISSET(lfd, &tmpfds))
        {
            //接受新的客户端连接请求
            cfd = accept(lfd, NULL, NULL);
            
            //将cfd加入到readfds集合中
            FD_SET(cfd, &readfds);
            
            //修改内核监控的文件描述符的范围
            if(maxfd<cfd)
            {
                maxfd = cfd;
            }
            //如果只有连接请求,就不用执行下面的代码处理客户端的数据。
            if(--nready==0)
            {
                continue;
            }
        }
        
        
        //有客户端数据发来
        for(i=lfd+1; i<=maxfd; i++)
        {
            if(FD_ISSET(i, &tmpfds))
            {
                //read数据
                n = read(i, buf, sizeof(buf));
                if(n<=0)
                {
                    close(i);
                    //将文件描述符i从内核中去除
                    FD_CLR(i, &readfds);
                }
                else
                {
                    printf("n=[%d], buf=[%s]\n", n, buf);
                    // 转大写
                    //write应答数据给客户端
                	write(i, buf, n);
                }
                
                // 本次select返回的可变文件描述符处理完,提前结束for循环
            	if(--nready==0)
            	{
            	    break;
            	}
            } 
        }
        
        close(lfd);
        
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    3.select服务器代码

    //select版本的服务器
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "wrap.h"
    
    int main() {
        //创建socket
        int lfd = Socket(AF_INET, SOCK_STREAM, 0);
    
        //设置端口复用
        int opt = 1;
        setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
    
        //绑定--将lfd 和 IP PORT绑定
        struct sockaddr_in serv;
        bzero(&serv, sizeof(serv));
        serv.sin_family = AF_INET;
        serv.sin_port = htons(8888);
        serv.sin_addr.s_addr = htonl(INADDR_ANY);
        Bind(lfd, (struct sockaddr *) &serv, sizeof(serv));
    
        //监听
        Listen(lfd, 128);
    
        fd_set readfds;  //定义文件描述符集变量
        fd_set tmpfds;
        FD_ZERO(&readfds);  //清空文件描述符集变量
        FD_ZERO(&tmpfds);
        FD_SET(lfd, &readfds);//将lfd加入到readfds集合中;
        int maxfd = lfd;
        int cfd;
        while (1) {
            tmpfds = readfds;
            int nready = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
            if (nready < 0) {
                if (errno == EINTR)//被信号中断
                {
                    continue;
                }
                break;
            }
    
            //有客户端连接请求到来
            if (FD_ISSET(lfd, &tmpfds)) {
                //接受新的客户端连接请求
                cfd = accept(lfd, NULL, NULL);
    
                //将cfd加入到readfds集合中
                FD_SET(cfd, &readfds);
    
                //修改内核监控的文件描述符的范围
                if (maxfd < cfd) {
                    maxfd = cfd;
                }
                //如果只有连接请求,就不用执行下面的代码处理客户端的数据。
                if (--nready == 0) {
                    continue;
                }
            }
    
    
            //有客户端数据发来
            for (int i = lfd + 1; i <= maxfd; i++) {
                if (FD_ISSET(i, &tmpfds)) {
    
                    char buf[1024];
                    bzero(buf,sizeof(buf));
    
                    //read数据
                    int n = read(i, buf, sizeof(buf));
                    if (n <= 0) {
                        close(i);
                        //将文件描述符i从内核中去除
                        FD_CLR(i, &readfds);
                    } else {
                        printf("n=[%d], buf=[%s]\n", n, buf);
                        // 转大写
                        //write应答数据给客户端
                        write(i, buf, n);
                    }
    
                    // 本次select返回的可变文件描述符处理完,提前结束for循环
                    if (--nready == 0) {
                        break;
                    }
                }
            }
    
        } // end while(1)
    
        Close(lfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100

    4.select优缺点

    select优点:
    1.一个进程可以支持多个客户端
    2.select支持跨平台

    select缺点:
    1.代码编写困难
    2.会涉及到用户区到内核区的来回拷贝
    3.当客户端多个连接, 但少数活跃的情况, select效率较低
    例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下
    4.最大支持1024个客户端连接
    select最大支持1024个客户端连接不是有文件描述符表最多可以支持1024个文件描述符限制的, 而是由FD_SETSIZE=1024限制的.
    FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核.

  • 相关阅读:
    计算机网络:随机访问介质访问控制之CSMA/CA协议
    【Linux】多线程基础
    计网第五章(运输层)(五)(TCP拥塞控制)
    amis 事件动作 和 行为按钮 常用用法
    Pathos: Nethack Codex 游戏指南
    【python项目推荐】键盘监控--统计打字频率
    Spring 中使用MyBatis
    Xilinx Artix7-100T低端FPGA解码MIPI视频,基于MIPI CSI-2 RX Subsystem架构实现,提供工程源码和技术支持
    FPGA设计流程
    【项目实战课】大语言模型提示词(Prompt)工程实战
  • 原文地址:https://blog.csdn.net/ArthurHai521/article/details/133583201