socket编程之常用api介绍与socket、select、poll、epoll高并发服务器模型代码实现(3)

简介: 高并发服务器模型-pollpoll介绍  poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。

高并发服务器模型-poll

poll介绍

  poll跟select类似, 监控多路IO, 但poll不能跨平台。其实poll就是把select三个文件描述符集合变成一个集合了。

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:

  • fds: 传入传出参数, 实际上是一个结构体数组


fds.fd: 要监控的文件描述符
fds.events: 
  POLLIN---->读事件
  POLLOUT---->写事件
fds.revents: 返回的事件


  • nfds: 数组实际有效内容的个数
  • timeout: 超时时间, 单位是毫秒.


-1:永久阻塞, 直到监控的事件发生
0: 不管是否有事件发生, 立刻返回
>0: 直到监控的事件发生或者超时

返回值:


  • 成功:返回就绪事件的个数
  • 失败: 返回-1。若timeout=0, poll函数不阻塞,且没有事件发生, 此时返回-1, 并且errno=EAGAIN, 这种情况不应视为错误。


struct pollfd {
   int   fd;        /* file descriptor */   监控的文件描述符
   short events;     /* requested events */  要监控的事件---不会被修改
   short revents;    /* returned events */   返回发生变化的事件 ---由内核返回
};


说明:

  1. 当poll函数返回的时候, 结构体当中的fd和events没有发生变化, 究竟有没有事件发生由revents来判断, 所以poll是请求和返回分离
  2. struct pollfd结构体中的fd成员若赋值为-1, 则poll不会监控
  3. 相对于select, poll没有本质上的改变; 但是poll可以突破1024的限制.在/proc/sys/fs/file-max查看一个进程可以打开的socket描述符上限,如果需要可以修改配置文件: /etc/security/limits.conf,加入如下配置信息, 然后重启终端即可生效
* soft nofile 1024
* hard nofile 100000

soft和hard分别表示ulimit命令可以修改的最小限制和最大限制


poll代码实现

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <pthread.h>
#define MAX_LEN  4096
#define POLL_SIZE    1024
int main(int argc, char **argv) {
    int listenfd, connfd, n;
    struct sockaddr_in svr_addr;
    char buff[MAX_LEN];
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    memset(&svr_addr, 0, sizeof(svr_addr));
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    svr_addr.sin_port = htons(8081);
    if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    //poll
    struct pollfd fds[POLL_SIZE] = {0};
    fds[0].fd = listenfd;
    fds[0].events = POLLIN;
    int max_fd = listenfd;
    int i = 0;
    for (i = 1; i < POLL_SIZE; i++) {
        fds[i].fd = -1;
    }
    while (1) {
        int nready = poll(fds, max_fd + 1, -1);
        if (fds[0].revents & POLLIN) {
            struct sockaddr_in client = {};
            socklen_t len = sizeof(client);
            if ((connfd = accept(listenfd, (struct sockaddr *) &client, &len)) == -1) {
                printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                return 0;
            }
            printf("accept \n");
            fds[connfd].fd = connfd;
            fds[connfd].events = POLLIN;
            if (connfd > max_fd) max_fd = connfd;
            if (--nready == 0) continue;
        }
        //int i = 0;
        for (i = listenfd + 1; i <= max_fd; i++) {
            if (fds[i].revents & POLLIN) {
                n = recv(i, buff, MAX_LEN, 0);
                if (n > 0) {
                    buff[n] = '\0';
                    printf("recv msg from client: %s\n", buff);
                    send(i, buff, n, 0);
                }
                else if (n == 0) { //
                    fds[i].fd = -1;
                    close(i);
                }
                if (--nready == 0) break;
            }
        }
    }
}

高并发服务器模型-epoll (重点)

epoll介绍

  将检测文件描述符的变化委托给内核去处理, 然后内核将发生变化的文件描述符对应的事件返回给应用程序。

  记住,epoll是事件驱动的,其底层数据结构是红黑树,红黑树的key是fd,val是事件,返回的是事件。

epoll有两种工作模式,ET和LT模式。

水平触发LT:


  • 高电平代表1
  • 只要缓冲区中有数据, 就一直通知


边缘触发ET:

电平有变化就代表1

缓冲区中有数据只会通知一次, 之后再有新的数据到来才会通知(若是读数据的时候没有读完, 则剩余的数据不会再通知, 直到有新的数据到来)

 epoll默认是水平触发LT,在需要高性能的场景下,可以改成边缘ET非阻塞方式来提高效率。


 一般使用LT是一次性读数据读不完,数据较多的情况。而一次性能够读完,小数据量则用边缘ET。


 ET模式由于只通知一次, 所以在读的时候要循环读, 直到读完, 但是当读完之后read就会阻塞, 所以应该将该文件描述符设置为非阻塞模式(fcntl函数)


 read函数在非阻塞模式下读的时候, 若返回-1, 且errno为EAGAIN, 则表示当前资源不可用, 也就是说缓冲区无数据(缓冲区的数据已经读完了); 或者当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲区中已没有数据可读了,也就可以认为此时读事件已处理完成。

epoll反应堆

  反应堆: 一个小事件触发一系列反应

 epoll反应堆的思想: c++的封装思想(把数据和操作封装到一起)

  • 将描述符,事件,对应的处理方法封装在一起
  • 当描述符对应的事件发生了, 自动调用处理方法(其实原理就是回调函数)


epoll反应堆的核心思想是: 在调用epoll_ctl函数的时候, 将events上树的时候,利用epoll_data_t的ptr成员, 将一个文件描述符,事件和回调函数封装成一个结构体, 然后让ptr指向这个结构体。然后调用epoll_wait函数返回的时候, 可以得到具体的events, 然后获得events结构体中的events.data.ptr指针, ptr指针指向的结构体中有回调函数, 最终可以调用这个回调函数。

struct epoll_event {
  uint32_t     events;      /* Epoll events */
  epoll_data_t data;        /* User data variable */
};
typedef union epoll_data {
  void        *ptr;
  int          fd;
  uint32_t     u32;
  uint64_t     u64;
} epoll_data_t;

epoll-api

int epoll_create(int size);


函数说明: 创建一个树根

参数说明:


  • size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数,历史意义,用epoll_create1也行。
  • 返回值:
成功: 返回一个大于0的文件描述符, 代表整个树的树根.
失败: 返回-1, 并设置errno值.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);


函数说明: 将要监听的节点在epoll树上添加, 删除和修改

参数说明:


  • epfd: epoll树根
  • op:


EPOLL_CTL_ADD: 添加事件节点到树上
EPOLL_CTL_DEL: 从树上删除事件节点
EPOLL_CTL_MOD: 修改树上对应的事件节点


  • fd: 事件节点对应的文件描述符
  • event: 要操作的事件节点


struct epoll_event {
  uint32_t     events;      /* Epoll events */
  epoll_data_t data;        /* User data variable */
};
typedef union epoll_data {
  void        *ptr;
  int          fd;
  uint32_t     u32;
  uint64_t     u64;
} epoll_data_t;


  • event.fd: 要监控的事件对应的文件描述符


int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

函数说明:等待内核返回事件发生

参数说明:

  • epfd: epoll树根
  • events: 传出参数, 其实是一个事件结构体数组
  • maxevents: 数组大小
  • timeout:
  -1: 表示永久阻塞
  0: 立即返回
  >0: 表示超时等待事件
  • 成功: 返回发生事件的个数
  • 失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值

epoll_wait的events是一个传出参数, 调用epoll_ctl传递给内核什么值, 当epoll_wait返回的时候, 内核就传回什么值,不会对struct event的结构体变量的值做任何修改。

epoll优缺点

epoll优点:


  1. 性能高,百万并发不在话下,而select就不行


epoll缺点:


  1. 不能跨平台,linux下的


epoll代码实现

#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <pthread.h>
#define POLL_SIZE 1024
#define MAX_LEN  4096
int main(int argc, char **argv) {
    int listenfd, connfd, n;
    char buff[MAX_LEN];
    struct sockaddr_in svr_addr;
    memset(&svr_addr, 0, sizeof(svr_addr));
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    svr_addr.sin_port = htons(8081);
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    if (bind(listenfd, (struct sockaddr *) &svr_addr, sizeof(svr_addr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    int epfd = epoll_create(1); //int size
    struct epoll_event events[POLL_SIZE] = {0};
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
    while (1) {
        int nready = epoll_wait(epfd, events, POLL_SIZE, 5);
        if (nready == -1) {
            continue;
        }
        int i = 0;
        for (i = 0; i < nready; i++) {
            int actFd = events[i].data.fd;
            if (actFd == listenfd) {
                struct sockaddr_in cli_addr;
                socklen_t len = sizeof(cli_addr);
                if ((connfd = accept(listenfd, (struct sockaddr *) &cli_addr, &len)) == -1) {
                    printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
                    return 0;
                }
                printf("accept\n");
                ev.events = EPOLLIN;
                ev.data.fd = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            }
            else if (events[i].events & EPOLLIN) {
                n = recv(actFd, buff, MAX_LEN, 0);
                if (n > 0) {
                    buff[n] = '\0';
                    printf("recv msg from client: %s\n", buff);
                    send(actFd, buff, n, 0);
                }
                else if (n == 0) { //
                    epoll_ctl(epfd, EPOLL_CTL_DEL, actFd, NULL);
                    close(actFd);
                }
            }
        }
    }
    return 0;
}

目录
相关文章
|
9月前
|
JSON 监控 API
在线网络PING接口检测服务器连通状态免费API教程
接口盒子提供免费PING检测API,可测试域名或IP的连通性与响应速度,支持指定地域节点,适用于服务器运维和网络监控。
1340 0
|
12月前
|
存储 人工智能 边缘计算
当 AI 进入「算力密集时代」:你的服务器能跑通大模型吗?
本文深入探讨AI服务器在技术落地中的核心瓶颈问题,结合实战经验解析从模型训练到端侧部署的算力优化策略。内容涵盖三大典型场景的算力需求差异、GPU服务器选型的五大反直觉真相、实战优化方法(如混合精度训练与硬件资源监控),以及边缘AI部署挑战和解决方案。同时提供算力弹性扩展策略、模型生命周期管理及合规性建议,帮助读者构建可持续发展的算力体系。文末附有获取更多资源的指引。
999 17
|
9月前
|
数据采集 监控 网络协议
基于aiohttp的高并发爬虫实战:从原理到代码的完整指南
在数据驱动时代,传统同步爬虫效率低下,而基于Python的aiohttp库可构建高并发异步爬虫。本文通过实战案例解析aiohttp的核心组件与优化策略,包括信号量控制、连接池复用、异常处理等,并探讨代理集成、分布式架构及反爬应对方案,助你打造高性能、稳定可靠的网络爬虫系统。
700 0
|
前端开发 Cloud Native Java
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
Java||Springboot读取本地目录的文件和文件结构,读取服务器文档目录数据供前端渲染的API实现
|
Kubernetes API 网络安全
当node节点kubectl 命令无法连接到 Kubernetes API 服务器
当Node节点上的 `kubectl`无法连接到Kubernetes API服务器时,可以通过以上步骤逐步排查和解决问题。首先确保网络连接正常,验证 `kubeconfig`文件配置正确,检查API服务器和Node节点的状态,最后排除防火墙或网络策略的干扰,并通过重启服务恢复正常连接。通过这些措施,可以有效解决与Kubernetes API服务器通信的常见问题,从而保障集群的正常运行。
1013 17
|
存储 弹性计算 测试技术
10分钟私有部署QwQ-32B模型,像购买Ecs实例一样快捷
虽然阿里云提供了基于 IaaS 部署 QwQ-32B 模型的方式,但传统的基于IaaS的部署方式需要用户自行配置环境、安装依赖、优化硬件资源,并解决复杂的网络与存储问题,整个流程不仅耗时耗力,还容易因操作失误导致各种不可预见的问题。 因此,阿里云计算巢提供了基于ECS镜像与VLLM的大模型一键部署方案,通过ECS镜像打包标准环境,通过Ros模版实现云资源与大模型的一键部署,用户无需关心模型部署运行的标准环境与底层云资源编排,10分钟即可部署使用QwQ-32B模型,15分钟即可部署使用Deepseek-R1-70B模型。
|
人工智能 负载均衡 数据可视化
阿里云出手了,DeepSeek服务器拒绝繁忙,免费部署DeepSeek模型671B满血版
阿里云推出免费部署DeepSeek模型671B满血版服务,通过百炼大模型平台,用户无需编码,最快5分钟、最低0元即可完成部署。平台提供100万免费Token,支持DeepSeek-R1和DeepSeek-V3等多款模型调用,有效解决服务器繁忙问题。新手零基础也能轻松上手,享受高效稳定的API调用和自动弹性扩展功能。教程涵盖开通服务、获取API-KEY及配置Chatbox客户端等步骤,详细指引助您快速实现DeepSeek自由。
781 18
|
负载均衡 数据可视化 API
像素流送api ue多人访问需要什么显卡服务器
本文总结了关于像素流送技术的五大常见问题,包括是否支持Unity模型推流、UE多人访问的最大并发数、所需服务器配置、稳定性问题及API支持情况,旨在帮助开发者更好地理解和应用这一技术。
611 1
|
数据可视化 Linux 网络安全
如何使用服务器训练模型
本文介绍了如何使用服务器训练模型,包括获取服务器、访问服务器、上传文件、配置环境、训练模型和下载模型等步骤。适合没有GPU或不熟悉Linux服务器的用户。通过MobaXterm工具连接服务器,使用Conda管理环境,确保训练过程顺利进行。
2389 0
如何使用服务器训练模型
|
JSON JavaScript 前端开发
使用 Node.js 和 Express 构建 RESTful API 服务器
【10月更文挑战第3天】使用 Node.js 和 Express 构建 RESTful API 服务器

热门文章

最新文章