• Linux网络编程 I/O模型和服务器模型


    在网络程序里面,通常都是一个服务器处理多个客户机。为了处理多个客户机的请求,服务器端的程序有不同的处理方式。

    IO模型

    • 阻塞I/O

      最常用、最简单、效率最低

    • 非阻塞I/O

      可防止进程阻塞在I/O操作上,需要轮询

    • I/O 多路复用

      允许同时对多个I/O进行控制

    • 信号驱动I/O

      一种异步通信模型


    阻塞I/O
    • 阻塞I/O模式是最普遍使用的I/O模式,大部分程序使用的都是阻塞模式的I/O

    • 缺省情况下,套接字建立后所处于的模式就是阻塞I/O模式

    • 例:

      read(); recv(); recvfrom(); write(); send(); accept(); connect();


    非阻塞I/O模式的实现
    • fcntl() 函数:操作文件描述符

    当一开始建立一个套接字描述符的时候,系统内核将其设置为阻塞IO模式

    使用函数fcntl() 设置一个套接字的标志为O_NONBLOCK来实现非阻塞

    #include 
    #include 
    int fcntl(int fd, int cmd, ... /* arg */ );
    参数:
        fd:文件描述符
        cmd:命令
    返回值:
        
    /* eg. */
    int flag = fcntl(fd, F_GETFL, 0);	//获得属性
    flag = flag | O_NONBLOCK;	//修改属性,添加非阻塞
    fcnl(fd, F_SETFL, flag);	//设置属性
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    多路复用I/O

    应用程序中同时处理多路输入输出流:

    • 若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,使程序变得更加复杂;
    • 若采用阻塞模式,将得不到预期目的;
    • 若采用非阻塞模式,对多个输入进行轮询,又太浪费CPU世间

    比较好的方法,使用 IO多路复用

    • 基本思想

      先构造一张有关描述符的表fd_set,

      然后函数select,让内核帮助上层用户循环检测是否有可操作的文件描述符,如果有则告诉应用程序去操作

    select()机制:

    用于探测多个文件句柄的状态变化.只适用于“短作业”处理

    #include 
    #include 
    #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:   在指定时间内,若没有事件发生,则超时返回
        
    返回值:
        成功返回准备好的文件描述符个数,失败返回 -1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    select函数调用完成后,fd_set变量将发生变化。除了发生了改变的文件描述符外,其他原来为 1 的变为 0

    相关辅助函数:

    void FD_CLR(int fd, fd_set *set);//删除表中一个fd
    int FD_ISSET(int fd, fd_set *set);//检测是否准备好,是 返回1 ,否返回 0
    void FD_SET(int fd, fd_set *set);//加入fd到表
    void FD_ZERO(fd_set *set);//清空表,将fd_set变量所指的位全部初始化为 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    select缺点:
    1. 内核使用轮询的方式来检查文件描述符集合中的描述符是否就绪,文件描述符越多,消耗的时间资源越多
    2. 文件描述符集合使用的数组,有大小限制 0-1024
    3. 每次文件描述符集合更新时,重新拷贝到内核中

    poll()机制:
    • poll的实现和select非常相似,只是文件描述符fd集合的方式不同;
    • poll使用struct pollfd结构而不是select的fd_set结构,其他都差不多
    struct pollfd {
    	int   fd;         /* file descriptor 文件描述符*/
    	short events;     /* requested events 请求事件(读、写、异常)*/
    	short revents;    /* returned events 对请求事件的反馈*/    	
    };
    
    /*eg.*/
    struct pollfd fds[];
    fds[0].fd;
    fds[0].events;
    	//POLLIN:	读事件
        //POLLOUT:	写事件
    fds[0].revents;
    
    #include 
    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    参数:
        fds:	文件描述符表
        nfds:	文件描述符表中文件描述符个数
        timeout:超时检测
        	-1	阻塞,直到文件描述符准备好
        
    返回值:
        成功返回准备好的文件描述符的个数,失败返回 -1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    epoll()机制:
    • epoll 使用红黑树来管理所有的文件描述符集合,不受大小限制,更新集合时,不需要重新拷贝整个集合,直接更新红黑树即可。
    • 利用call back(回调函数)来知道文件描述符是否就绪,只关心已就绪的文件描述符,不需要遍历所有的文件描述符。

    相关函数:epoll_create(); epoll_ctl(); epoll_wait();

    epoll_create()
    #include 
    int epoll_create(int size);
    功能:
        创建一个树
    参数:
        size:树的结点
    返回值:
        成功返回 红黑树描述符epfd ,失败返回 -1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    epoll_ctl()
    -------------相关数据结构--------------------------------------
    typedef union epoll_data {
    	void *ptr;
    	int fd;
    	uint32_t u32;
    	uint64_t u64;
    }epoll_data_t;
    
    struct epoll_event {
    	uint32_t events;      /* Epoll events */
    	epoll_data_t data;        /* User data variable */
    };
    --------------------------------------------------------------
    #include 
    int  epoll_ctl(int  epfd,  int  op,  int fd, struct epoll_event *event);
    功能:
        控制 epoll
    参数:
        epfd:	epoll_create()的返回值
        op:		指令
        	EPOLL_CTL_ADD:	添加文件描述符
            EPOLL_CTL_DEL:	删除文件描述符
    	fd:		准备好的文件描述符
        event:	结构体
          /* eg. */
             struct epoll_event ev;
    		 ev.events;	//事件	EPOLLIN 读事件;EPOLLOUT 写事件;
    		 ev.data.fd;//文件描述符
    
    返回值:
        成功返回 0,失败返回 -1
    • 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
    epoll_wait()
    #include 
    int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
    功能:等待文件描述符就绪
    参数:
        epfd:		epoll_create()返回值
        events:		结构体数组
        	struct epoll_event evs[SIZE];
    
    	maxevents:	SIZE 结构体数组最大值
        timeout:	超时检测
            -1:	阻塞,等待
    
    返回值:
        成功返回准备好的文件描述符的个数 ,失败返回 -1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    服务器模型

    在网络程序里面,通常都是一个服务器处理多个客户机。为了处理多个客户机的请求,服务器端的程序有不同的处理方式。

    循环服务器

    循环服务器在同一时刻只能相应一个客户端的请求。

    TCP服务器一般很少采用循环服务器模型。如果某个客户端一直占用服务器资源,那么其他的客户端都不能被处理。


    并发服务器模型

    除非UDP服务器在处理某个客户端的请求时所用时间比较长,UDP服务器实际上很少使用并发服务器模型。

    并发服务器在同一个时刻可以相应多个客户端的请求。

    多进程并发服务器:
    • 基本原理

    每连接一个客户端,创建一个子进程,子进程负责处理connfd(客户请求),父进程处理sockfd(连接请求)。

    • 细节处理

    回收子进程资源

    多线程并发服务器:
    • 基本原理

    每连接一个客户端,创建一个子线程,子线程负责处理connfd(客户请求),主线程处理sockfd(连接请求)。

    • 注意:子线程结束时,进行资源回收

    pthread_self 获取线程自身id


  • 相关阅读:
    Filter过滤器
    Open3D RANSAC拟合圆(随机采样一致性)
    Linux~一些基本开发工具的使用(yum,vim,gcc,gdb,makefile)
    后端服务架构的不同与区别
    Alien Skin Exposure2023调色滤镜插件RAW后期处理软件
    STM32平替GD32有多方便
    [可视化] rviz的可视化python
    An工具介绍之摄像头
    C++基础算法教程|图论入门(一)
    _cpp利用红黑树封装实现map和set
  • 原文地址:https://blog.csdn.net/weixin_45649201/article/details/126427241