当前位置: 首页 > article >正文

linux中实现多路复用服务器

linux中实现多路复用服务器

使用一个进程(且只有主线程)同时监控若干个文件描述符的读写情况,这种读写模式称为多路复用

多用于TCP的服务端,用于监控客户端的连接和数据的发送

优点:

不需要频繁地创建、销毁进程,从而节约了内存资源、时间资源,也避免了进程之间的竞争、等待

缺点:

要求单个客户端的任务不能太过于耗时,否则其他客户端就会感知到卡顿

select:
    fd_set 是文件描述符的集合,使用以下函数操作:
    void FD_CLR(int fd, fd_set *set);
    功能:从集合set中删除fd文件描述符

    int  FD_ISSET(int fd, fd_set *set);
    功能:判断集合set中是否存在fd文件描述符,可以用于监视多个文件描述符以检测哪些文件描述符准备好进行读、写或异常处理。
    
    void FD_SET(int fd, fd_set *set);
    功能:向集合set中添加fd文件描述符

    void FD_ZERO(fd_set *set);
    功能:清空集合set

    int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
    功能:同时监控多个文件描述的读、写、异常操作
    nfds:被监控的文件描述符中的最大值+1
    readfds:监控读操作的文件描述符集合
    writefds:监控写操作的文件描述符集合
    exceptfds:监控异常操作的文件描述符集合
    timeout:设置超时时间
        NULL    一直阻塞,直到某个文件描述符发生了变化
        00微秒 非阻塞
        大于0秒 等待超时时间,超时返回0
        struct timeval {
               long    tv_sec;  //  秒
               long    tv_usec; //  微秒
           };
    返回值:监控到发生相关操作的文件描述符的个数,超时返回0,错误返回-1

    注意:
        readfds、writefds、exceptfds 这三个集合参数既是输入也是输出,调用select时这三个集合需要存储被监控的文件描述符,当由于有文件描述符发生了相应的操作而导致函数返回时,这三个集合中存储了这些文件描述符并返回给调用者

    select设计不合理的地方:
        1、每次调用select都需要向它重新传递被监控的文件描述符集合
        2、调用结束后如果想知道具体是哪个文件描述符发生了相关操作,必须对所有被监控的文件描述符进行一遍测试
    select的优点:
        它是最早的多路复用函数,几乎所有的操作系统都支持,兼容性很高

pselect:
    int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);
    功能:大致与select
    区别:
        1、超时时间的结构类型不同
        struct timespec {
            long    tv_sec;     //秒
            long    tv_nsec;    //纳秒
        };
        2、pselect监控时可以通过sigmask参数设置要屏蔽的信号,可以保障pselect监控时不受这些信号干扰
    共同点:本质上与select差别不大、select的缺点pelect也是一样的,只是个别功能有所增强而已

服务器具体代码实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc,const char* argv[])
{
	//创建套接字
    int svr_fd = socket(AF_INET, SOCK_STREAM, 0);
	if(svr_fd < 0)
	{
        perror("socket");
        return -1;
    }
    //准备通信地址
    struct sockaddr_in addr={};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);//设置端口号
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");//设置ip地址
    socklen_t addrlen = sizeof(addr);
    //绑定地址
    if(bind(svr_fd, (struct sockaddr*)&addr, addrlen) < 0)
    {
        perror("bind");
        return -1;
    }
    //监听
    if(listen(svr_fd, 5) < 0)
    {
        perror("listen");
        return -1;
    }
    //定义一个文件描述符集合,并清空
    fd_set rfds;
    FD_ZERO(&rfds);
    //将套接字加入到集合中
    FD_SET(svr_fd, &rfds);

    //定义超时时间
    struct timeval timeout={};
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    //记录最大的文件描述符
    int max_fd = svr_fd;//当前socket描述符就这一个,所以也是最大值
    char buf[1024];
    size_t buf_size = sizeof(buf);
    while(1)
    {
        //若有多个新的连接,集合会发生变化,而把之前的没发生变化的文件描述符给覆盖了,所以需要复制一份
        fd_set ready_copy=rfds;
        int ret = select(max_fd+1, &ready_copy, NULL, NULL, &timeout);//select函数返回值表示有多少文件描述符发生变化
        if(ret > 0)
        {
          //一开始,先测网络等待的socket描述符
            if(FD_ISSET(svr_fd, &ready_copy))//如果有新的连接
            {
                //调用accept()连接客户端
                int clt_fd = accept(svr_fd,(struct sockaddr*)&addr, &addrlen);
                if(clt_fd < 0)
                {
                    perror("accept");
                    continue;
                }
                else
                {
                    //把客户端的描述符加入到集合中
                    FD_SET(clt_fd, &rfds);
                    if(clt_fd > max_fd)
                    {
                        max_fd = clt_fd;
                    }
                }
            }
            else
            {
                //测试其他socket描述符是否发生变化
                for(int i=3; i<=max_fd; i++)
                {
                    if(FD_ISSET(i, &ready_copy) && i!= svr_fd)
                    {
                        int ret = recv(i, buf, buf_size, 0);
                        if(ret < 0)
                        {
                            FD_CLR(i, &rfds);
                            printf("客户端%d断开连接\n", i);
                            continue;
                        }
                        printf("recv:%s bits:%d\n", buf, ret);
                        strcat(buf,":return");
                        ret=send(i,buf,strlen(buf)+1,0);
                        if(ret <= 0 || 0==strcmp(buf,"quit"))
                        {
                            FD_CLR(i, &rfds);
                            printf("客户端%d断开连接\n", i);
                            continue;
                        } 
                    }
                }
            }
        }
    }
}


http://www.kler.cn/a/325122.html

相关文章:

  • WPF的基础控件详解
  • 极速入门数模电路
  • 2024年了,TCP分析工具有哪些?
  • STM32单片机设计防儿童人员误锁/滞留车内警报系统
  • STM32保护内部FLASH
  • 树的直径计算:算法详解与实现
  • 使用Python创建EXE运行器和截图工具
  • 【数据结构和算法实践-排序-总结】
  • 9.24作业
  • Uniapp 打包后的横屏控制
  • 【JavaEE初阶】多线程7(面试要点)
  • MacOS安装MindSpore(2024年最新)
  • 创意实现!在uni-app小程序商品详情页轮播中嵌入视频播放功能
  • 成都睿明智科技有限公司可靠吗?
  • SpringBoot--为什么Controller是串行的?怎样才能并行?
  • uni-app之旅-day01-home页
  • Python 课程18-SQLAlchemy
  • Stable Diffusion绘画 | LCM模型:实现秒出图
  • 多旋翼无人机光伏发电站吊运技术详解
  • nodejs基于vue电子产品商城销售网站的设计与实现 _bugfu
  • 19 vue3之自定义指令Directive按钮鉴权
  • Qt --- 其他控件的介绍 --- 多元素控件
  • 【在Linux世界中追寻伟大的One Piece】验证TCP
  • 数据工程师岗位常见面试问题-1(附回答)
  • yolo自动化项目实例解析(七)自建UI--工具栏选项
  • 【JavaEE初阶】深入解析单例模式中的饿汉模式,懒汉模式的实现以及线程安全问题