IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> select、poll和epoll形象化理解与对比 -> 正文阅读

[系统运维]select、poll和epoll形象化理解与对比

一、引入问题

现在有一个服务器程序和一个客户端程序,客户端程序连接上服务器程序后向服务器发送“Hello server,I am client!”,服务器收到客户端的连接后给客户端发送“Hello client,I am server!”。如果是迭代服务器,那么它运行时一旦与一个客户端程序建立连接,只有处理完这个客户端的请求(上述程序中是给客户端发完“Hello client,I am server!”)后才能与下一个客户端建立连接。也就是说,迭代服务器一次只能处理一个客户端的请求。

与迭代服务器不同的是,并发服务器一次能处理多个客户端请求。在上述程序中是服务器可以在非常短的时间内读取到多个客户端给它发送的“Hello server,I am client!”,并给多个客户端发送“Hello client,I am server!”。

我们可以用多进程或多线程的方式来实现并发服务器。父进程或父线程负责接听客户端的连接,然后交给子进程或子线程来处理该客户端的请求,而父进程继续接听客户端连接。但是在非常多的客户端请求需要服务器处理的情况下,多进程和多线程并发服务器非常消耗资源,于是又引入了多路复用来解决这个问题。

二、形象化解释select、poll、epoll

多路复用有三种方式select、poll、epoll,下面用一个生活中的例子来形象化解释这三种多路复用的原理。

假如你是便利店老板,需要看顾客是否进店了,要给顾客结账,还要防止偷窃。这时你只能一直看着门等待顾客进店,然后一直监视顾客的行为,直到顾客结账出店,你又开始等待下一个顾客。

select相当于一个监控,会替你一直监视进来的每一个顾客(select可以替你监视1024个人),顾客进店、发生偷窃行为或者顾客要结账时它都会通知你有顾客发生了事件,但是它不会通知你是哪个顾客发生了哪种事件,在没接到select的通知期间,你就可以摸鱼做其他的事,接到select的通知后,你需要一个个地问select是不是XXX人发生了XXX事件,问到以后再处理事件。

poll也是一个监控,它的功能和select相同,但是性能更好,它替你监控的人数没有限制,但是当你接到poll的通知时仍然需要一个个地问poll是不是XXX人发生了XXX事件,问到以后再处理事件。

epoll也是一个监控,它的功能更强,它不仅监视的人数没有限制,而且一旦有人进店、结账或偷窃它就会告诉你是哪个人发生了哪种事件,你就可以直接去处理事件。

三、select、poll、epoll“监控”步骤

无论你是想要select、poll还是epoll帮你监视顾客,让你可以及时知道店里现在发生了什么事然后去处理,你都需要做三步:

  1. 一旦有顾客进店,你就要告诉它们要监视这个顾客,要不然它们可不会主动帮你监视
  2. 你要准备好接收发生事件的通知
  3. 问它们具体发生了什么事件

接下来就是你去处理对应的事件了,注意:select和poll只会通知你有事件发生,想知道是谁发生了什么事件你还得一个个地问,epoll会主动告诉你是谁发生了什么事件。
换句话说,select和poll会给你一份所有监视顾客的名单,你得对着这个名单问select或poll是不是XXX人发生了XXX事件,然后去处理对应事件;epoll也会给你一份顾客名单,不过这份顾客名单上全部是发生事件的顾客,你拿到名单以后直接按着名单去处理相应事件。

四、图解select、poll、epoll多路复用实现并发服务器

select、poll、epoll用它们的”监控“功能可以实现多路复用(只一个服务器程序就可以在极短时间内处理多个客户端请求)优化服务器性能,实现服务器高并发。那它们又是如何应用到服务器上的呢?我们用并发服务器的流程图和select、poll、epoll实现的迭代服务器流程图对比来说明这个问题。
在这里插入图片描述

并发服务器流程图

五、select、poll、epoll使用的头文件和函数

为了方便理解,这里我们仍用“便利店老板看店”的例子来解释select、poll、epoll相关函数的功能

1)select

#include <sys/select.h>
#include <sys/time.h>

FD_ZERO(fd_set* fds) //清空监视顾客的集合
FD_SET(int fd, fd_set* fds) //让select监视指定的顾客
FD_ISSET(int fd, fd_set* fds) //用这个函数询问是不是XXX顾客发生了XXX事件
FD_CLR(int fd, fd_set* fds) //用这个函数可以让select不再监视指定顾客
    
int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);//select的通知由这个函数发送

2)poll

#include <poll.h>

struct pollfd
{
int fd;//相当于顾客的身份证,用于标识顾客
short events;//你关心的事件(进店、结账、偷窃)
short revents;//poll监视到实际发生的事件
} ;

int poll(struct pollfd *fds, nfds_t nfds, int timeout);//poll的通知由这个函数发送

3)epoll

#include <sys/epoll.h>

struct epoll_event
{
uint32_t events; //你关心的事件(进店、结账、偷窃)
epoll_data_t data; //相当于顾客的身份证,用于标识顾客
};

int epoll_create(int size);//创建一个epoll对象来监视顾客
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);//用这个函数可以让epoll监视指定顾客,修改你关心的事件以及让epoll不再监视某个顾客
int epoll_wait(int epfd, struct epoll_event *evlist, int maxevents, int timeout);//epoll的通知由这个函数发送

六、用epoll写一个简单的并发服务器程序

现在我们用epoll多路复用来实现我们最开始提到的服务器程序:客户端程序连接上服务器程序后向服务器发送“Hello server,I am client!”,服务器收到客户端的消息后给客户端发送“Hello client,I am server!”

服务器端:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/epoll.h>

#define SERVER_PORT 9999
#define MAX_EVENTS  100//epoll返回发生事件的客户端的最大数量


int main(char argc, char *argv[])
{
    int                        serv_fd;
    int                        clie_fd;
    struct sockaddr_in        serv_addr;
    struct sockaddr_in        clie_addr;
    socklen_t                cliaddr_len = sizeof(struct sockaddr_in);
    int                        rv = -1;
    char                       buf[1024];
    int                        on = 1;
    int                        epollfd;//epoll对象,用来监视所有服务器指定的客户端
    struct epoll_event         event;//用来设置单个客户端的信息,包括fd的值和对这个客户端关心的事件
    struct epoll_event         event_array[MAX_EVENTS];//用来存放所有发生事件的客户端的信息
    int                        events;//发生事件的客户端的数目
    int                        i;//用来遍历event_array数组处理对应事件
    
    

    /*服务器的初始化*/
    serv_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(serv_fd < 0)
    {
        printf("accept socket failure: %s\n", strerror(errno));
        rv = -2;
        goto cleanup;
    }
    printf("create socket[%d] successfully!\n",serv_fd);
    
    setsockopt(serv_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
   
    serv_addr.sin_family = AF_INET; 
    serv_addr.sin_port = htons(SERVER_PORT);   
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    if(bind(serv_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("bind socket[%d] failure: %s\n", serv_fd, strerror(errno));
        rv = -3;
        goto cleanup;
    }
    
    listen(serv_fd, 13);

    /*创建epoll对象,来监视所有服务器指定的客户端*/
    epollfd = epoll_create(MAX_EVENTS);
    if(epollfd < 0)
    {
        printf("epoll_create() failure: %s\n", strerror(errno));
        rv = -4;
        goto cleanup;
    }

    /*将服务器用于和客户端建立连接的socket文件描述符加入epoll对象的监视中,如果它发生事件说明有新的客户端正在请求建立连接*/
    event.data.fd = serv_fd;
    event.events = EPOLLIN;//设置对serv_fd关心的事件为是否可读
    if(epoll_ctl(epollfd, EPOLL_CTL_ADD, serv_fd, &event) < 0)//将服务器用于和客户端建立连接的socket文件描述符加入epoll对象的监视中
    {
        printf("epoll add listen socket failure: %s\n", strerror(errno));
        rv = -5;
        goto cleanup;
    }
   
    while(1)
    {
        printf("\nStart waiting epoll inform...\n");
        events = epoll_wait(epollfd, event_array, MAX_EVENTS, -1);//接收epoll监视通知,如果监视的socket文件描述符有事件发生,函数就会返回,否则一直堵塞
        if(events < 0)
        {
            printf("epoll failure: %s\n", strerror(errno));
            break;
        }
        else if(events == 0)
        {
            printf("epoll get timeout\n");
            continue;
        }
        
        for(i=0; i<events; i++)
        {
            if((event_array[i].events&EPOLLERR) || (event_array[i].events&EPOLLHUP))
            {
                printf("epoll_wait get error on fd[%d]: %s\n", event_array[i].data.fd, strerror(errno));
                epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                close(event_array[i].data.fd);
            }

            if(event_array[i].data.fd == serv_fd)//有新客户端连接
            {
                clie_fd = accept(serv_fd, (struct sockaddr*)NULL, NULL);
                if(clie_fd < 0)
                {
                    printf("accept new client failure: %s\n", strerror(errno));
                    continue;
                }

                event.data.fd = clie_fd;
                event.events = EPOLLIN;
                if(epoll_ctl(epollfd, EPOLL_CTL_ADD, clie_fd, &event) < 0)
                {
                    printf("epoll add client socket failure: %s\n", strerror(errno));
                    close(event_array[i].data.fd);
                    continue;
                }
            }
            else
            {//处理已经建立连接的客户端的事件
                rv = read(event_array[i].data.fd, buf, sizeof(buf));
                if(rv <= 0)
                {
                    printf("socket[%d] read failure or get disconncet and will be removed.\n",event_array[i].data.fd);
                    epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);//从epoll对象的监视中移除
                    close(event_array[i].data.fd);
                    continue;
                }
                else
                {
                    printf("socket[%d] read get %d bytes data:%s\n", event_array[i].data.fd, rv, buf);

                    if(write(event_array[i].data.fd, "Hello client, I am cloud server!", sizeof("Hello client, I am cloud server!")) < 0)
                    {
                        printf("socket[%d] write failure: %s\n", event_array[i].data.fd, strerror(errno));
                        epoll_ctl(epollfd, EPOLL_CTL_DEL, event_array[i].data.fd, NULL);
                        close(event_array[i].data.fd);
                    }
                }
            }
        }
    }

cleanup:
    if(serv_fd > 0)
        close(serv_fd);
    if(clie_fd > 0)
        close(clie_fd);
    return rv;
}

客户端:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <getopt.h>

#define SERVER_PORT         9999
#define SERVER_IP           "127.0.0.1"
#define MSG_STR				"Hello server, I am client!"


int main(char argc, char *argv[])
{
    int 					conn_fd = -1;
    int 					rv = 0;
    char					buf[1024];
    struct sockaddr_in 		serv_addr;
    
    conn_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (conn_fd < 0)
    {
        printf("create socket failure: %s\n", strerror(errno));
        rv = -2;
        goto cleanup;
    }
    
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(SERVER_PORT);
    inet_aton( SERVER_IP, &serv_addr.sin_addr );

    if( connect(conn_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("connect to server[%s:%d] failure: %s\n", serv_ip, port, strerror(errno));
        rv = -3;
        goto cleanup;
    }

    rv = write(conn_fd, MSG_STR, sizeof(MSG_STR));
    if(rv < 0)
    {
        printf("Write data to server [%s:%d] failure: %s\n", serv_ip, port, strerror(errno));
        rv = -4;
        goto cleanup;
    }

    memset(buf, 0, sizeof(buf));
    rv = read(conn_fd, buf, sizeof(buf));
    if(rv < 0)
    {
        printf("Read data to server [%s:%d] failure: %s\n", serv_ip, port, strerror(errno));
        rv = -5;
        goto cleanup;
    }
    printf("Read %d bytes data from Server: %s\n", rv, buf);


cleanup:
    if(conn_fd > 0)
        close(conn_fd);

    return rv;

}

七、对比总结select、poll、epoll

首先说明,select和poll的用处越来越有限,epoll已经成为了目前实现高性能网络服务器的必备技术。

select的缺点:

  1. 最多只能监视1024个文件描述符,虽然可以更改,但select采用轮询的方式扫描文件描述符,文件描述符数量越多,select性能越差。
  2. 内核/用户空间内存拷贝问题,会产生巨大的开销。
  3. 需要遍历整个文件描述符数组才能发现哪些文件描述符发生了那些事件。
  4. 如果程序没有完成对一个就绪文件描述符事件的处理,那么每次调用select都还是会将那些正在处理的文件描述符通知给进程。

而poll除了没有最大文件描述符限制,以上的缺点都存在。

epoll与select、poll的实现机制完全不同,它没有以上的缺点,可以直接返回就绪文件描述符数组,能显著提高程序在大量并发连接中只有少量活跃情况下的系统CPU利用率。

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-10-22 11:20:10  更:2021-10-22 11:22:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 20:54:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码