c++高级篇(二) ——Linux下IO多路复用之select模型

简介: c++高级篇(二) ——Linux下IO多路复用之select模型

什么是IO多路复用

前言

我们在Linux上服务端一般是要同时连接多个客户端进行通信,但是为每一个客户端连接创建一个进/线程,会消耗很多资源,一个1核2GB的虚拟机,大概只能创建100多个线程,但是我们经常使用网络知道,这样是远远不能满足我们日常的使用需求的,所以为了解决这一问题,就需要我们去使用IO多路复用。

IO多路复用

IO多路复用指的是我们可以使用一个进/线程去处理多个TCP链接,减少系统开销,而我们常见的IO多路复用主要用三种:

  • select(1024)
  • poll(几千)
  • epoll(百万)

网络通讯中的读与写事件

读事件

  • 已连接队列中有已经准备好的socket(有新的客户端连接上来)
  • 接收缓存有数据可以读(对端发送的报文已经送达)
  • tcp连接断开(对端使用close()函数断开了连接)

写事件

  • 发送端缓冲区没有满,可以写入数据(向对端发送报文)

select模型

位图

  • 什么是位图
    select实现IO多路复用是基于位图来实现的,位图的本质是一个32位整型数组(int[32]),一个32位整型有4个字节,每个字节有8个位:
    32 ∗ 8 ∗ 4 = 1024 32*8*4=10243284=1024
    每一个位可以监听一个socket这也是select模型课件监听1024个socket的原因所在。
  • 位图的相关操作
    Linux内核中为我们提供相关的宏让我们操作位图:
void D_CLR(int fd,fd_set* set);//将socket从位图中删除
int FD_ISSET(int fd,fd_set *set);//判断socket是否在位图中
void FD_SET(int fd,fd_set* set);//将socket加入到位图中
void FD_ZERO(fd_set* set); //将位图全部初始化为0

select模型的细节

写事件

  • 如果tcp的发送缓冲区没有满,那么此时socket连接是可写的
  • 一般来说发送缓冲区不容易填满,但是如果发送数据量过大或者网络带宽不够,发送缓冲区有填满的可能。

水平触发

  • select()监视的socket如果发生了事件,select()会返回(通知应用程序处理事件),如果事件没有被处理,再次调用select()的时候会立即再通知
  • 存在的问题
  • 这里操作位图的方法是轮询,它的性能会随着socket的增多而增多
  • 每次调用select,需要拷贝位图,而且select属于用户态,网络通信属于内核态,需要拷贝两次,会影响select的性能
  • 受位图大小的限制,每个进/线程selectt所能处理的socket数量默认是1024个,性能不够高,无法处理网络通信频繁的实际场景

select模型监控socket通讯流程图

代码示例

#include "data-sharing-center/public/_cmpublic.h"
#include <string.h>
using namespace std;
int inintserver(int port); //初始化监听端口
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        cout<<"using example:./server [port]"<<endl;
        return -1;
    }
    //初始化服务端用来监听的socket
    int listensock=inintsocket(atoi(argv[1]));
    if(listensock<0)
    {
        perror("inintsocket() error");
        return -1;
    }
    cout<<"listensock="<<listensock<<endl;
    //初始化select
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(listensock,&readfds);
    int maxfd=listensock;//记录当前监听socket的数量
    while(true)  //使用select循环监听
    {
        //定义超时结构体
        struct timeval timeout;  //定义超时结构体
        timeout.tv_sec=10; //秒
        timeout.tv_usec=0; //微妙
        fd_set tmps=readfds;  //select操作中会对位图进行修改,创建一个临时位图
        int infds=select(maxfd+1,&tmps,NULL,NULL,&timeout);  //开启监听
        if(infds<0)   //连接失败
        {
            perror("select() error");
            break;
        }
        else if(infds==0)  //超时(此时间段没有事件发生)
        {
            cout<<"select timeout"<<endl;
            continue;
        }
        else    //有事件发生
        {
            //遍历位图,查看是哪一个socket发生事件
            for(int eventfd=0;eventfd<=maxfd;eventfd++)
            {
                if(FD_ISSET(eventfd,&tmps))    //查看是否是该socket
                {
                    if(eventfd=listensock)   //如果是监听,说明发生的事件是有客户顿socket发送了连接请求
                    {
                        //接收客户端连接
                        struct sockaddr_in clientaddr;
                        socklen_t addrlen=sizeof(clientaddr);
                        int clientsock=accept(listensock,(struct sockaddr*)&clientaddr,&addrlen);
                        if(clientsock<0)
                        {
                            perror("accept() error");
                            continue;
                        }
                        cout<<"client connected,clientsock:"<<clientsock<<endl;
                        //将新的客户端socket加入位图
                        FD_SET(clientsock,&readfds);
                        if(clientsock>maxfd)
                        {
                            maxfd=clientsock;
                        }
                    }
                    else   //否则就是客户端向服务端发送了数据,或者有客户端断开了连接
                    {
                        char buffer[1024]; //用来接收数据
                        memset(buffer,0,sizeof(buffer));
                        if(recv(eventfd,buffer,sizeof(buffer),0)<0)   //说明是有客户端断开了
                        {
                            cout<<"client disconnected,clientfd="<<eventfd<<endl;
                            close(eventfd);
                            FD_CLR(eventfd,&tmps);
                            if(maxfd==eventfd);  //重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
                            {
                                for(int ii=maxfd;ii>0;ii--)
                                {
                                    if(FD_ISSET(ii,&readfds))
                                    {
                                        maxfd=ii;
                                        break;
                                    }
                                }
                            }
                        }
                        else   //说明是有客户端发送了数据
                        {
                            cout<<"client data:"<<buffer<<endl;
                            send(eventfd,buffer,strlen(buffer),0);  //把数据发送回去说明已经收到了
                        }
                    }   
                }
            }
        }
    }
    return 0;
}
int inintsocket(int port)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket() error");
        return -1;
    }
    //设置端口复用
    int opt=1;
    unsigned int len=sizeof(opt);
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
    //绑定端口
    struct sockaddr_in serveraddr;
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_port=htons(port);
    serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    if(bind(sock,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("bind() error");
        close(sock);
        return -1;
    }
    //监听
    if(listen(sock,5)<0)
    {
        perror("listen() error");
        close(sock);
        return -1;
    }
    return sock;
}

注意: 这里的头文件是博主自己封装的,大家可以使用’man+函数名的方式查看相关函数所需的头文件。

相关文章
|
9月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
218 0
|
1月前
|
Linux C语言 网络架构
Linux的基础IO内容补充-FILE
而当我们将运行结果重定向到log.txt文件时,数据的刷新策略就变为了全缓冲,此时我们使用printf和fwrite函数打印的数据都打印到了C语言自带的缓冲区当中,之后当我们使用fork函数创建子进程时,由于进程间具有独立性,而之后当父进程或是子进程对要刷新缓冲区内容时,本质就是对父子进程共享的数据进行了修改,此时就需要对数据进行写时拷贝,至此缓冲区当中的数据就变成了两份,一份父进程的,一份子进程的,所以重定向到log.txt文件当中printf和fwrite函数打印的数据就有两份。此时我们就可以知道,
36 0
|
1月前
|
存储 Linux Shell
Linux的基础IO
那么,这里我们温习一下操作系统的概念我们在Linux平台下运行C代码时,C库函数就是对Linux系统调用接口进行的封装,在Windows平台下运行C代码时,C库函数就是对Windows系统调用接口进行的封装,这样做使得语言有了跨平台性,也方便进行二次开发。这就是因为在根本上操作系统确实像银行一样,并不完全信任用户程序,因为直接开放底层资源(如内存、磁盘、硬件访问权限)给用户程序会带来巨大的风险。所以就向银行一样他的服务是由工作人员隔着一层玻璃,然后对顾客进行服务的。
36 0
|
9月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
364 1
Linux C/C++之IO多路复用(aio)
|
7月前
|
Ubuntu Linux Shell
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
(已成功解决)Linux环境报错—bash: wget: command not found;常见Linux发行版本,Linux中yum、rpm、apt-get、wget的区别;Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
2745 68
(已解决)Linux环境—bash: wget: command not found; Docker pull报错Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled
|
5月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
223 34
|
7月前
|
Linux API C语言
Linux基础IO
Linux基础IO操作是系统管理和开发的基本技能。通过掌握文件描述符、重定向与管道、性能分析工具、文件系统操作以及网络IO命令等内容,可以更高效地进行系统操作和脚本编写。希望本文提供的知识和示例能帮助读者更深入地理解和运用Linux IO操作。
143 14
|
7月前
|
机器学习/深度学习 人工智能 自然语言处理
C++构建 GAN 模型:生成器与判别器平衡训练的关键秘籍
生成对抗网络(GAN)是AI领域的明星,尤其在C++中构建时,平衡生成器与判别器的训练尤为关键。本文探讨了GAN的基本架构、训练原理及平衡训练的重要性,提出了包括合理初始化、精心设计损失函数、动态调整学习率、引入正则化技术和监测训练过程在内的五大策略,旨在确保GAN模型在C++环境下的高效、稳定训练,以生成高质量的结果,推动AI技术的发展。
235 10
|
8月前
|
存储 JSON Java
细谈 Linux 中的多路复用epoll
大家好,我是 V 哥。`epoll` 是 Linux 中的一种高效多路复用机制,用于处理大量文件描述符(FD)事件。相比 `select` 和 `poll`,`epoll` 具有更高的性能和可扩展性,特别适用于高并发服务器。`epoll` 通过红黑树管理和就绪队列分离事件,实现高效的事件处理。本文介绍了 `epoll` 的核心数据结构、操作接口、触发模式以及优缺点,并通过 Java NIO 的 `Selector` 类展示了如何在高并发场景中使用多路复用。希望对大家有所帮助,欢迎关注威哥爱编程,一起学习进步。
119 6
|
9月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
233 0
Linux C/C++之IO多路复用(poll,epoll)