高性能网络编程 - select、 poll 、epoll 、libevent

news2024/11/24 4:53:33

文章目录

  • 概述
  • 优缺点
    • Select
    • Poll
    • Epoll
    • LibEvent

在这里插入图片描述


概述

  1. Select(选择):
    • Select 是一种传统的 I/O 多路复用机制,用于在类 Unix 操作系统(如 Linux)中同时管理多个文件描述符(如网络套接字或文件)。
    • 它允许程序监视多个 I/O 源以检测可读性或可写性,并在数据可读或可写时触发事件。
    • Select 相对简单,但在处理大量文件描述符时性能和可扩展性有限。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>

int main()
{
    // 1.创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        exit(0);
    }
    // 2. 绑定 ip, port
    struct sockaddr_in addr;
    addr.sin_port = htons(10000);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
    // 3. 监听
    ret = listen(lfd, 100);
    if(ret == -1)
    {
        perror("listen");
        exit(0);
    }
    
    // 4. 等待连接 -> 循环
    // 检测 -> 读缓冲区, 委托内核去处理
    // 数据初始化, 创建自定义的文件描述符集
    fd_set rdset, tmp; 
    FD_ZERO(&rdset);
    FD_SET(lfd, &rdset);
    int maxfd = lfd;
    while(1)
    {
        // 委托内核检测
        tmp = rdset;
        ret = select(maxfd+1, &tmp, NULL, NULL, NULL);
        if(ret == -1)
        {
            perror("select");
            exit(0);
        }

        // 检测的度缓冲区有变化
        // 有新连接
        if(FD_ISSET(lfd, &tmp))
        {
            // 接收连接请求
            struct sockaddr_in sockcli;
            int len = sizeof(sockcli);
            // 这个accept是不会阻塞的
            int connfd = accept(lfd, (struct sockaddr*)&sockcli, &len);
            // 委托内核检测connfd的读缓冲区
            FD_SET(connfd, &rdset);
            maxfd = connfd > maxfd ? connfd : maxfd;
        }
        // 通信, 有客户端发送数据过来
        for(int i=lfd+1; i<=maxfd; ++i)
        {
            // 如果在集合中, 说明读缓冲区有数据
            if(FD_ISSET(i, &tmp))
            {
                char buf[128];
                int ret = read(i, buf, sizeof(buf));
                if(ret == -1)
                {
                    perror("read");
                    exit(0);
                }
                else if(ret == 0)
                {
                    printf("对方已经关闭了连接...\n");
                    FD_CLR(i, &rdset);
                    close(i);
                }
                else
                {
                    printf("客户端say: %s\n", buf);
                    write(i, buf, strlen(buf)+1);
                }
            }
        }
    }
    
    close(lfd);

    return 0;
}


  1. Poll(轮询):
    • Poll 是另一种在类 Unix 系统中可用的 I/O 多路复用机制。在性能和可扩展性方面优于 select。
    • 与 select 类似,poll 允许程序监视多个文件描述符,但它可以更高效地处理大量文件描述符。
    • Poll 仍然广泛使用,但像 epoll 这样的更现代替代方案因性能更好而备受青睐。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <poll.h>

int main()
{
    // 1.创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        exit(0);
    }
    // 2. 绑定 ip, port
    struct sockaddr_in addr;
    addr.sin_port = htons(10000);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
    // 3. 监听
    ret = listen(lfd, 100);
    if(ret == -1)
    {
        perror("listen");
        exit(0);
    }
    
    // 4. 等待连接 -> 循环
    // 检测 -> 读缓冲区, 委托内核去处理
    // 数据初始化, 创建自定义的文件描述符集
    struct pollfd fds[1024];
    // 初始化
    for(int i=0; i<1024; ++i)
    {
        fds[i].fd = -1;
        fds[i].events = POLLIN;
    }
    fds[0].fd = lfd;

    int maxfd = 0;
    while(1)
    {
        // 委托内核检测
        ret = poll(fds, maxfd+1, -1);
        if(ret == -1)
        {
            perror("select");
            exit(0);
        }

        // 检测的度缓冲区有变化
        // 有新连接
        if(fds[0].revents & POLLIN)
        {
            // 接收连接请求
            struct sockaddr_in sockcli;
            int len = sizeof(sockcli);
            // 这个accept是不会阻塞的
            int connfd = accept(lfd, (struct sockaddr*)&sockcli, &len);
            // 委托内核检测connfd的读缓冲区
            int i;
            for(i=0; i<1024; ++i)
            {
                if(fds[i].fd == -1)
                {
                    fds[i].fd = connfd;
                    break;
                }
            }
            maxfd = i > maxfd ? i : maxfd;
        }
        // 通信, 有客户端发送数据过来
        for(int i=1; i<=maxfd; ++i)
        {
            // 如果在集合中, 说明读缓冲区有数据
            if(fds[i].revents & POLLIN)
            {
                char buf[128];
                int ret = read(fds[i].fd, buf, sizeof(buf));
                if(ret == -1)
                {
                    perror("read");
                    exit(0);
                }
                else if(ret == 0)
                {
                    printf("对方已经关闭了连接...\n");
                    close(fds[i].fd);
                    fds[i].fd = -1;
                }
                else
                {
                    printf("客户端say: %s\n", buf);
                    write(fds[i].fd, buf, strlen(buf)+1);
                }
            }
        }
    }
    
    close(lfd);

    return 0;
}


  1. Epoll(事件轮询):
    • Epoll(事件轮询)是一种较新且高效的 I/O 事件通知机制,主要用于 Linux
    • 与 select 和 poll 不同,epoll 专为高性能 I/O 事件处理而设计。它可以有效地管理大量文件描述符而不会显著降低性能。
    • Epoll 特别适用于构建可扩展和高性能的网络服务器和应用程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/epoll.h>

int main()
{
    // 1.创建套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        exit(0);
    }
    // 2. 绑定 ip, port
    struct sockaddr_in addr;
    addr.sin_port = htons(10000);
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
    // 3. 监听
    ret = listen(lfd, 100);
    if(ret == -1)
    {
        perror("listen");
        exit(0);
    }

    // 创建epoll树
    int epfd = epoll_create(1000);
    if(epfd == -1)
    {
        perror("epoll_create");
        exit(0);
    }

    // 将监听lfd添加到树上
    struct epoll_event ev;
    // 检测事件的初始化
    ev.events = EPOLLIN ;
    ev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    struct epoll_event events[1024];
    // 开始检测
    while(1)
    {
        int nums = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);
        printf("numbers = %d\n", nums);
        
        // 遍历状态变化的文件描述符集合
        for(int i=0; i<nums; ++i)
        {
            int curfd = events[i].data.fd;
            // 有新连接
            if(curfd == lfd)
            {
                struct sockaddr_in clisock;
                int len = sizeof(clisock);
                int connfd = accept(lfd, (struct sockaddr*)&clisock, &len);
                if(connfd == -1)
                {
                    perror("accept");
                    exit(0);
                }
                // 将通信的fd挂到树上
                //ev.events = EPOLLIN | EPOLLOUT;
                ev.events = EPOLLIN;
                ev.data.fd  = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }
            // 通信
            else
            {
                // 读事件触发, 写事件触发
                if(events[i].events & EPOLLOUT) 
                {
                    continue;
                }
                char buf[128];
                int count = read(curfd, buf, sizeof(buf));
                if(count == 0)
                {
                    printf("client disconnect ...\n");
                    close(curfd);
                    // 从树上删除该节点
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                }
                else if(count == -1)
                {
                    perror("read");
                    exit(0);
                }
                else
                {
                    // 正常情况
                    printf("client say: %s\n", buf);
                    write(curfd, buf, strlen(buf)+1);
                }
            }
        }
    }
    
    close(lfd);

    return 0;
}


  1. Libevent(事件库):
    • Libevent 是一个提供了简单和一致的事件通知机制 API 的 C 库,包括 select、poll、epoll 等多种机制。
    • 它允许开发人员编写可移植且高效的网络和事件驱动软件。
    • Libevent 抽象了不同平台和事件通知机制之间的差异,使开发人员能够更容易地编写能够在不同系统上运行而无需担心底层细节的代码。

sever

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>

int main()
{
    // 1. 创建事件处理框架
    struct event_base* base = event_base_new();
    
    // 打印支持的IO转接函数
    const char** method = event_get_supported_methods();
    for(int i=0; method[i] != NULL; ++i)
    {
        printf("%s\n", method[i]);
    }
    printf("current method: %s\n", event_base_get_method(base));

    // 创建子进程
    pid_t pid = fork();
    if(pid == 0)
    {
        // 子进程中event_base也会被复制,在使用这个base时候要重新初始化
        event_reinit(base); 
    }

    // 2. 释放资源
    event_base_free(base);
    return 0;
}


client

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>

// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{
    printf("arg value: %s\n", (char*)arg);
    // 读缓冲区的数据
    char buf[128];
    int len = bufferevent_read(bev, buf, sizeof(buf));
    printf("read data: len = %d, str = %s\n", len, buf);

    // 回复数据
    bufferevent_write(bev, buf, len);
    printf("数据发送完毕...\n");
}

// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{

    printf("arg value: %s\n", (char*)arg);
    printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}

// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{
    if(event & BEV_EVENT_ERROR)
    {
        printf("some error happened ...\n");
    }
    else if(event & BEV_EVENT_EOF)
    {
        printf("server disconnect ...\n");
    }
    // 终止连接
    bufferevent_free(bev);
}

void send_msg(evutil_socket_t fd, short ev, void * arg)
{
    // 将写入到终端的数据读出
    char buf[128];
    int len = read(fd, buf, sizeof(buf));
    // 发送给服务器
    struct bufferevent* bev = (struct bufferevent*)arg;
    bufferevent_write(bev, buf, len);
}

int main()
{
    struct event_base * base = event_base_new();
    // 1. 创建通信的套接字
    struct bufferevent* bufev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
    // 2. 连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);    // 服务器监听的端口
    inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
    // 这个函数调用成功, == 服务器已经成功连接
    bufferevent_socket_connect(bufev, (struct sockaddr*)&addr, sizeof(addr));
    // 3. 通信
    // 给bufferevent的缓冲区设置回调
    bufferevent_setcb(bufev, read_cb, write_cb, events_cb, (void*)"hello, world");
    bufferevent_enable(bufev, EV_READ);

    // 创建一个普通的输入事件
    struct event* myev = event_new(base, STDIN_FILENO, EV_READ|EV_PERSIST, send_msg, bufev);
    event_add(myev, NULL);
    
    
    event_base_dispatch(base);
    event_free(myev);
    event_base_free(base);


    return 0;
}


总之,这些是用于编程的工具和库,用于高效地处理多个 I/O 操作,特别是在网络通信的背景下。Select 和 poll 是较旧、性能较低的选项,而 epoll 是一种高性能的替代方案。Libevent 是一个库,简化了使用这些机制的工作,同时提供了跨不同平台的可移植性。


优缺点

以下是每种方案的优点和缺点:

Select

优点:

  • 简单易用,易于理解和实现。
  • 在小规模连接数的情况下,性能通常足够。
  • 跨平台兼容性较好。

缺点:

  • 性能不够高,随着连接数的增加,性能会下降。
  • 需要维护大量文件描述符集合,开销较大。
  • 对于大规模并发连接,存在效率问题。

Poll

优点:

  • 性能相对于Select有所提升,可以处理更多文件描述符。
  • 在某些场景下,仍然是一个可行的选择。

缺点:

  • 仍然存在性能问题,特别是在大规模并发连接的情况下。
  • 对于每个事件的轮询会导致不必要的开销。

Epoll

优点:

  • 高性能:Epoll 针对大规模并发连接进行了优化,性能较高。
  • 有效地管理大量文件描述符,不会随连接数增加而降低性能。
  • 支持边缘触发模式,只在事件发生时通知应用程序,减少了不必要的处理开销。
  • 仅在Linux系统上可用。

缺点:

  • 不具备跨平台兼容性,只能在Linux上使用。
  • 相对于Select和Poll,编写代码可能稍微复杂一些。

LibEvent

优点:

  • 提供统一的事件通知 API,能够适应不同操作系统和事件通知机制。
  • 简化了跨平台开发,使代码更具可移植性。
  • 在性能方面,可以利用底层高性能机制,如Epoll,以提高性能。

缺点:

  • 与直接使用底层机制相比,可能引入轻微的性能开销。
  • 需要学习Libevent的API和概念。

总的来说,选择哪种方案取决于你的应用需求。如果需要处理大规模并发连接,特别是在Linux上,Epoll通常是最佳选择。对于跨平台开发,Libevent可以提供便利。如果只需处理少量连接,Select和Poll也可以工作,但性能可能不如Epoll。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1185815.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chatGLM中GLM设计思路

GLM是结合了MLM和CLM的一种预训练方式&#xff0c;其中G为general&#xff1b;在GLM中&#xff0c;它不在以某个token为粒度&#xff0c;而是一个span&#xff08;多个token&#xff09;&#xff0c;这些span之间使用自编码方式&#xff0c;而在span内部的token使用自回归的方式…

基础课25——业务流程分析

1.流程的定义&作用 业务流程是企业中一系列创造价值的活动的组合&#xff0c;它是企业运营的基础&#xff0c;也是企业提高效率、优化资源配置的重要手段。通过优化业务流程&#xff0c;企业可以更好地满足客户需求&#xff0c;提高客户满意度&#xff0c;同时也可以提高自…

Django框架简介

文章目录 Django框架介绍MVC与MVT模型MVCMTV 版本问题运行django注意事项 Django的下载与基本命令下载Django方式一&#xff1a;在命令界面使用pip安装方式二&#xff1a;使用pycharm安装 Django的基础命令命令行操作pycharm操作 Django项目命令行操作与Pycharm操作的区别应用D…

【Qt之QAssociativeIterable】使用

介绍 QAssociativeIterable类是QVariant中一个关联式容器的可迭代接口。这个类允许多种访问在QVariant中保存的关联式容器元素的方法。如果一个QVariant可以转换为QVariantHash或QVariantMap&#xff0c;那么QAssociativeIterable的实例可以从中提取出来。 QHash<int, QSt…

【工具】旋转图片-数据集制作工具, 开源!

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] Github&#xff1a;https://github.com/1061700625/small_tools_v2 之前做了一个下载百度的旋转图片验证码的工具(多进程下载百度旋转验证码图片-制作数据集)&#xff0c;那么拿到了图片数据&#xff0c;就需要手…

还不知道IP地址不够用是怎么被大牛们解决的?(NAT/NAPT, IPv6, DHCP)

文章目录 前言1. DHCP网络管理协议什么是 DHCPDHCP 两种分配机制 2. NAT网络地址转换协议什么是 NATNAT 技术使用NAT网络设备间如何通信两个内网设备相互通信不同内网中的设备相互通信NAT IP转换过程 NAPT 技术NAT 技术的缺陷 3. IPv6 协议什么是 IPv6 总结 前言 在之前的文章…

【教3妹学编程-算法题】2915. 和为目标值的最长子序列的长度

3妹&#xff1a;2哥&#xff0c;今日都立冬了&#xff0c; 可是天气一点都不冷。 2哥 : 立冬了&#xff0c;晚上要不要一起出去吃饺子&#xff1f;&#x1f95f; 3妹&#xff1a;好呀好呀&#xff0c;2哥请吃饺子喽 2哥 : 歪歪&#xff0c;我说的是一起出去吃&#xff0c;没说我…

[Java/力扣160]相交链表

这道题的关键是&#xff0c;使两个链表上的指针同时到达相交点处 方法一&#xff1a;分别遍历两个链表&#xff0c;得到其长度。然后先让较长的链表上的指针走“两链表长度之差”。然后两指针分别一步一步走&#xff0c;就能同时到达相交点处。 方法二&#xff1a;让 p1 遍历…

SHCTF-校外赛道

SHCTF-校外赛道 [WEEK1]babyRCE 1 (1)more:一页一页的显示档案内容2 (2)less:与 more 类似&#xff0c;但是比 more 更好的是&#xff0c;他可以[pg dn][pg up]翻页3 (3)head:查看头几行4 (4)tac:从最后一行开始显示&#xff0c;可以看出 tac 是 cat 的反向显示5 (5)tail:查看…

Android内存回收机制、GC算法及内存问题分析解决

Android内存回收机制、GC算法及内存问题分析解决 在Android开发中&#xff0c;Java内存回收和垃圾收集&#xff08;GC&#xff09;机制是确保应用程序高效运行的关键部分。针对不同对象存活率&#xff0c;Android平台采用了引用计数算法和可达性分析法来判定对象的可回收性&am…

命名管道原理(和匿名管道的对比),mkfifo(命令行,函数),命名管道模拟实现代码+与多个子进程通信代码

目录 命名管道 引入 原理 和匿名管道的对比 使用 -- mkfifo 命令行指令 创建 文件类型p 使用 函数 函数原型 模拟实现 头文件 客户端代码 服务端代码 运行情况 模拟实现 -- 与多个子进程 介绍 服务端代码: 运行情况 命名管道 引入 匿名管道只能用于父子进程…

一篇文章带你搞懂DNS全流程

1.DNS与CDN DNS是域名系统的缩写&#xff0c;它是一种将域名和IP地址相互映射的分布式数据库&#xff0c;能够使人更方便地访问互联网。 DNS的主要功能是将域名解析为IP地址。当你在浏览器中输入一个网址时&#xff0c;浏览器会向DNS服务器发送一个请求&#xff0c;以获取该网…

5G-A 商用加速,赋能工业互联网

2019 年 6 月&#xff0c;中国工业和信息化部发放 5G 商用牌照。同年 10 月&#xff0c;三大运营商公布 5G 商用套餐&#xff0c;11 月 1 日正式上线 5G 商用套餐&#xff0c;标志中国正式进入 5G 商用新纪元。今年是 5G 商用的第五年&#xff0c;在当前数字经济蓬勃发展的催化…

什么是屏蔽机房?

屏蔽机房是一种用于保护数据中心设备的安全和可靠的措施。通过屏蔽机房&#xff0c;可以防止电磁干扰、防止物理入侵以及提供更好的隔离和安全性。下面是一些关于屏蔽机房的常见做法&#xff1a; 电磁屏蔽&#xff1a;为了防止电磁干扰对数据中心设备的影响&#xff0c;可以在屏…

代码随想录算法训练营第15天|102. 二叉树的层序遍历226. 翻转二叉树101. 对称二叉树

JAVA代码编写 102. 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9…

11.8旧有报错与修改

我将uart_done&#xff08;出问题的信号&#xff09;的变量类型设为reg了&#xff0c;也就是我是reg uart_done这个信号的&#xff0c;这样做是错误的&#xff0c;哪怕你在接收模块确实定义的是reg类型&#xff0c;但是在顶层模块的时候&#xff0c;它可以视为是一条单纯的线而…

PPO算法是什么?

ppo称作近邻策略优化算法&#xff0c;是典型的Actor- critic算法&#xff0c;即以两个网络为输入&#xff0c;并可以同时更新两者参数&#xff1b;在RLHF中我们更关注actor网络的更新方式&#xff0c;其损失函数由三部分构成&#xff0c;分别是&#xff1a;1&#xff0c;新旧状…

二进制搭建及高可用 Kubernetes v1.20

目录 一、实验规划&#xff1a; 二、操作系统初始化配置&#xff1a; 1. 关闭防火墙 selinux&#xff1a; 2. 关闭swap分区&#xff1a; 3. 根据规划设置主机名&#xff1a; 4. 所有主机添加hosts&#xff1a; 5. 调整内核参数: 6. 时间同步: 三、部署 etcd 集群&#xff1a…

netty (二) netty原理详解

netty高性能架构设计 netty 写一个简单的demo 服务器端 package com.atguigu.netty.simple;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import …

GIS开发入门,TopoJSON格式是什么?TopoJSON格式与GeoJSON格式有什么不同?

TopoJSON介绍 TopoJSON是一种几何拓扑结构的地理数据格式,它使用拓扑结构来表示地理对象,可以更有效地压缩和转移数据,从而加快数据加载速度。 TopoJSON格式构成 TopoJSON文件由三部分组成,transform、objects和arcs组成。transform描述了变换参数; objects描述地理实体…