多路IO转接服务器实现方法一:select()函数

2023-05-16

采用多进程与多线程的方法来实现并发服务器时,监听的工作由server应用程序自身通过accept函数不断去监听。当客户端连接较多时,这种方法会大大降低程序执行效率,消耗CPU资源(CPU需要在不同进/线程中切换执行)。
多进程与多线程实现并发服务器方法可以参考以下两篇文章:

  • 多进程并发服务器实现
  • 多线程并发服务器实现

因为以上两种方法的局限性所以出现了采用多路IO转接的方式来设计服务器,该类服务器实现的思想为应用程序本身不在监听客户端连接,转而由内核来代替监听。主要使用的方法有三种,其一为 select()函数。

select函数

  1. select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数不能改变select监听文件个数
  2. 解决1024以下个客户端时使用select十分合适,但是如果链接客户端过多,select采用的轮询模型,会大大降低服务器响应效率,应采用其他方法。

函数原型

int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);

  • 返回值
    • 成功:返回所监听的所有监听集合中满足条件的总数。
    • 失败:返回-1,并设置errno。
  • 参数
    • nfds:监控的文件描述符集里最大文件描述符加1,此参数会告诉内核检测前多少个文件描述符的状态
    • readfds:监控有读数据到达的文件描述符集合。传入传出参数
    • writefds:监控有写数据到达的文件描述符集合。传入传出参数
    • exceptfds:监控异常发生到达文件描述符集合。传入传出参数
    • timeout:定时阻塞监控时间,3种情况
    1. NULL,永远等下去
    2. 设置timeval,等待固定时间
    3. 设置timeval里时间均为0,检查描述字后立即返回,轮询
    • struct timeval结构体
      struct timeval{
      	long tv_sec;//分
      	long tv_usec;//微秒
      }
      

四个工具函数

想要看的更详细,请参考man手册

//fd_set类型为位图集合
void FD_CLR(int fd, fd_set *set);//将fd从set集合中清除出去,相当于将对应位置为0
int  FD_ISSET(int fd, fd_set *set);//判断fd是否在set集合中,返回1为在集合中,0为假,即不在集合中
void FD_SET(int fd, fd_set *set);//将fd设置到set集合中,相当于将对应位置为1
void FD_ZERO(fd_set *set);//将文件描述符集合清空,位图清空相当于全部置为0

例子

服务端代码实现
特别要举例说的是rset参数,在调用select函数前调用了rset = allset语句,假如此时赋值后rset中有fd1、fd2、fd3三个需要被监听的文件描述符,但是这三个文件描述符中只有fd2文件描述符满足了读的监听条件,那么在调用完select函数后,select函数会将fd1、fd3文件描述符从rset集合中剔除,rset集合中只会存在满足读监听条件的文件描述符。
同样地,readfdswritefdsexceptfds三个传入传出参数皆是如此。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<sys/types.h>
#include<sys/time.h>
#include<ctype.h>
#define PORT 8888
#define IP "127.0.0.1"
int main(){
    //1、建立套接字
    int sfd = socket(AF_INET,SOCK_STREAM,0);

    //2、绑定
    struct sockaddr_in serv_addr;
    bzero(&serv_addr,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr);
    socklen_t serv_len = sizeof(serv_addr);
    int ret = bind(sfd,(struct sockaddr*)&serv_addr,serv_len);
    if(ret != 0){
        printf("bind err:%s\n",strerror(ret));
        exit(1);
    }

    //3、监听
    listen(sfd,128);

    //定义一个客户端数组,用来存放监听文件描述符,以免select到时候循坏0~1023个文件描述符
    int client[FD_SETSIZE];

    //将数组内值全部初始化为-1
    for(int i = 0; i < FD_SETSIZE;i++){
        client[i] = -1;
    }

    fd_set rset,allset;//rset 用来保存满足读监听条件的文件描述符集合,allset用来保存要监听的文件描述符集合
    FD_ZERO(&allset);
    FD_SET(sfd,&allset);
    int nready,i;
    int index = -1;//定义数组下标
    int maxfd = sfd;//定义一个最大文件描述符,select第一个参数:监听的文件描述符集里最大的文件描述符+1
    while(1){
        //rset中存放监听条件为读的文件描述符
        rset = allset;

        //只监听读集合,写集合与异常集合暂时不监听,timeout参数传NULL表示永不超时
        /*select调用后会更改rset集中文件描述符集,只保留符合监听条件的文件描述符*/
        nready = select(maxfd+1,&rset,NULL,NULL,NULL);
        if(nready < 0){
            printf("监听的集合中没有满足条件的文件描述符\n");
        }

        int sockfd;
        //判断sfd是否存在于满足监听条件的集合中
        if(FD_ISSET(sfd,&rset)){
            //如果监听到了有满足条件的文件描述符,开始accept()
            struct sockaddr_in clie_addr;
            socklen_t clie_len = sizeof(clie_addr);
            int cfd = accept(sfd,(struct sockaddr*)&clie_addr,&clie_len);//返回一个新的客户端的文件描述符
            if(cfd == -1){
                perror("accept error");
                exit(2);
            }
            char buf[BUFSIZ];
            printf("%sconnected...;port:%d\n",inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,buf,sizeof(buf)),ntohs(clie_addr.sin_port));//打印谁连接了上来

            for(i = 0; i < FD_SETSIZE;i++){
                if(client[i] < 0){
                    client[i] = cfd;
                    break;
                }
            }
            //由于select()函数只能有1024个文件描述符,所以进行判断,如果超过了就应该退出
            if(i == FD_SETSIZE){
                printf("too many connecting...\n");
                exit(1);
            }
            FD_SET(cfd,&allset);
            //更新文件描述符集中最大的文件描述符
            if(cfd > maxfd){
                maxfd = cfd;
            }

            if(i > index){
                index = i;
            }

            if(--nready == 0){
                continue;
            }
        }
        //遍历client[i]数组,查看数组中是否需要监听的文件描述符
        int len;
        char rwbuf[BUFSIZ];

        for(i = 0; i <= index;i++)
        {
            if((sockfd=client[i]) < 0){
                //满足此语句说明client[]中i位置是-1,并将client[i]的值赋给sockfd
                continue;
            }
            //如果sockfd文件描述符在rset读文件描述符集中,说明是要监听的
            if(FD_ISSET(sockfd,&rset)){
                len = read(sockfd,rwbuf,sizeof(rwbuf));
                if(len==0){
                    //说明客户端关闭了
                    //将这个文件描述符从allset中清除,在对应的client[]中i位置改回-1
                    printf("------%d--disconnected\n",i);
                    close(sockfd);
                    FD_CLR(sockfd,&allset);
                    client[i] = -1;
                }else if(len > 0){
                    for(int j = 0; j < len;j++){
                        rwbuf[j] = toupper(rwbuf[j]);
                    }
                    write(sockfd,rwbuf,len);
                }
                if(--nready == 0){
                    break;
                }
            }
        }

    }
    close(sfd);
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多路IO转接服务器实现方法一:select()函数 的相关文章

  • 将多个值插入隐藏字段

    我有一个选择列表 您可以在其中选择多个城市 选择城市时 我想将邮政编码添加到隐藏字段 我现在的解决方案将值插入到隐藏字段 但是 当 fx 时它会覆盖该值 单击一个新城市 它应该只附加到值中 例如 value value1 value2 va
  • 获取多选列表框的选定值

    我有一个多选模式的列表框 它与数据库中的 15 个值绑定数据 我有以下代码来显示列表框中选定的每个项目的选定值 foreach var list in list box SelectedItems MessageBox Show list
  • MySQL 返回用户排名最高的事件

    我目前使用以下查询来获取每个用户的详细信息 SELECT u sums total votes sums no of events FROM user u LEFT JOIN SELECT us user uid count ev even
  • 如果通过 SQL 查询结果没有找到记录,则应为 0

    我正在使用火鸟 我需要以下结果 但我没有得到我需要的结果 我尝试了以下查询 SELECT CASE EXTRACT MONTH FROM pd Date WHEN 1 THEN January WHEN 2 THEN February WH
  • SQL select通常是如何实现的

    我有两节课 class PopulationMember public void operationOnThisMember1 void operationOnThisMember2 private Population populalti
  • jQuery - 选择同一级别的div

    我想在单击按钮时选择一个特定的div 唯一的问题是 它必须是buttonClicked的父div的div 示例 div class container div class box h2 Langtidsparkering h2 div cl
  • 如何使用jq通配符

    我有以下 json details car bmw addresses ext 118 21 8 0 29 version 4 addr 89 Psr version 6 addr 56 apT The key ext 118 21 8 0
  • 如何解决postgresql中group by和聚合函数的问题

    我正在尝试编写一个查询来划分两个 SQL 语句 但它显示了我 ERROR column temp missed must appear in the GROUP BY clause or be used in an aggregate fu
  • 在 BEFORE INSERT 触发器中使用 IF EXISTS (SELECT ...) (Oracle)

    我的代码不起作用 Oracle 告诉我创建触发器时出现构建错误 显然我无法获得有关构建错误的更准确信息 我以前确实没有做过很多SQL 所以我对语法不太熟悉 我有一种预感 Oracle 不喜欢我的 IF EXISTS SELECT THEN
  • VB SQL 语句未选择正确的行

    我试图使用 SELECT 语句在我的数据库中 选择 一个人 但它没有选择正确的人 我也不确定为什么 我正在使用访问数据库 数据库连接代码 Imports System Data OleDb Module Database Connectio
  • jQuery:如何仅根据表标题从表的列中选择值

    我有一个带有标题 ID 的表 我需要选择此标题下的所有字段 我无权访问源代码 并且该表中没有使用任何类 关于如何完成这件事有什么想法吗 要获取第一列 function var col td nth child 1
  • 选择前 n 个字符相等的行(MySQL)

    我有一张带有玩家句柄的桌子 如下所示 1 N Laka 2 N James 3 nor Brian 4 nor John 5 Player 2 6 Spectator 7 N Joe 从那里我想选择第一个 n 字符匹配的所有玩家 但我不知道
  • chrome 中选择选项元素的额外填充

    我有一个选择元素 用户可以在其中选择分类和描述 仅在 Chrome 浏览器中 我有一个额外的填充 无法使用 padding 0 或其他 css 标签删除它 Chrome 的屏幕 https i stack imgur com m3iIb p
  • AngularJS - 设置下拉列表的选定值不起作用

    我在这里复制了我的问题 http jsfiddle net U3pVM 2840 http jsfiddle net U3pVM 2840 正如标题所示 我无法设置使用 ng options 填充的选择的选定值 我已经搜索并尝试了我找到的所
  • 在一个查询中对同一个表进行多个 COUNT SELECT

    对于某些人来说 这可能看起来很简单 但我就是无法理解 我一遍又一遍地从同一个表中进行多个 MS SQL SELECT 查询 SELECT count Page as tAEC FROM someTable WHERE Page LIKE A
  • HTML 选择框,从 servlet 中选择数据

    再会 我在 html 中的选择框上遇到问题 我位于简单 CRUD 项目的编辑部分 在用户可以编辑之前 将首先显示所选数据 然后我通过 servlet 在数据库中检索它 现在我希望我检索的数据成为我的选择框中选定的数据 默认 product
  • 什么是更好的?子查询或内连接十个表?

    一个旧系统已抵达我们的办公室进行一些更改和修复 但它也存在性能问题 我们并不确切知道这种缓慢的根源是什么 当我们重构旧代码时 我们发现了几个具有以下模式的 sql 查询 出于示例目的 简化了查询 SELECT SELECT X FROM A
  • 如何使用 Rrank() 函数创建新的ties.method? [复制]

    这个问题在这里已经有答案了 我试图按人口和日期排序这个数据框 所以我使用order and rank 功能 gt df lt data frame idgeoville c 5 8 4 3 4 5 8 8 date c rep 1950 4
  • MySQL select with 语句

    我正在学习更多 SQL 并遇到了一个 问题 我有两个表 如下面的链接http www sqlfiddle com 2 403d4 1 http www sqlfiddle com 2 403d4 1 编辑 由于我这个周末所做的所有 SQL
  • 查看oracle中重复行的所有数据

    我有一个有 6 列的表 id name type id code lat long 前三个是必需的 ID是私钥 按序列自动插入 我有一些重复的行 正如两者所定义的name and type id是平等的 但我想查看受骗者的所有数据 我可以很

随机推荐