159 Linux C++ 通讯架构实战14,epoll 函数代码实战

ngx_epoll_init函数的调用

    //(3.2)ngx_epoll_init函数的调用(要在子进程中执行)
    //四章,四节 project1.cpp:nginx中创建worker子进程;
    //nginx中创建worker子进程
    //官方nginx ,一个master进程,创建了多个worker子进程;
    // master process ./nginx 
    // worker process
    //(i)ngx_master_process_cycle()        //创建子进程等一系列动作
    //(i)    ngx_setproctitle()            //设置进程标题    
    //(i)    ngx_start_worker_processes()  //创建worker子进程   
    //(i)        for (i = 0; i < threadnums; i++)   //master进程在走这个循环,来创建若干个子进程
    //(i)            ngx_spawn_process(i,"worker process");
    //(i)                pid = fork(); //分叉,从原来的一个master进程(一个叉),分成两个叉(原有的master进程,以及一个新fork()出来的worker进程
    //(i)                //只有子进程这个分叉才会执行ngx_worker_process_cycle()
    //(i)                ngx_worker_process_cycle(inum,pprocname);  //子进程分叉
    //(i)                    ngx_worker_process_init();  //ngx_epoll_init函数的调用
    //(i)                        sigemptyset(&set);  
    //(i)                        sigprocmask(SIG_SETMASK, &set, NULL); //允许接收所有信号
    //(i)                        g_socket.ngx_epoll_init();  //初始化epoll相关内容,同时 往监听socket上增加监听事件,从而开始让监听端口履行其职责
    //(i)                            m_epollhandle = epoll_create(m_worker_connections); 
    //(i)                            ngx_epoll_add_event((*pos)->fd....);
    //(i)                                epoll_ctl(m_epollhandle,eventtype,fd,&ev);
    //(i)                    ngx_setproctitle(pprocname);          //重新为子进程设置标题为worker process
    //(i)                    for ( ;; ) {

                                        ngx_process_events_and_timers(); //处理网络事件和定时器事件

                              }. ....                   //子进程开始在这里不断的死循环

    //(i)    sigemptyset(&set); 
    //(i)    for ( ;; ) {}.                //父进程[master进程]会一直在这里循环

ngx_epoll_init 函数内容

该函数从完成的事情有:

1.epoll_create  创建红黑树

2.m_pconnections = new ngx_connection_t[m_connection_n];  创建连接池 并初始化连接池

3.遍历所有监听socket【监听端口】,我们为每个监听socket增加一个 连接池中的连接

for(pos = m_ListenSocketList.begin(); pos != m_ListenSocketList.end(); ++pos){

c = ngx_get_connection((*pos)->fd); //从连接池中获取一个空闲连接对象

c->listening = (*pos);   //连接对象 和监听对象关联,方便通过连接对象找监听对象

(*pos)->connection = c;  //监听对象 和连接对象关联,方便通过监听对象找连接对象

        //对监听端口的读事件设置处理方法,因为监听端口是用来等对方连接的发送三路握手的,所以监听端口关心的就是读事件

        c->rhandler = &CSocekt::ngx_event_accept;

        //往监听socket上增加监听事件,从而开始让监听端口履行其职责【如果不加这行,虽然端口能连上,但不会触发ngx_epoll_process_events()里边的epoll_wait()往下走】
        if(ngx_epoll_add_event((*pos)->fd,       //socekt句柄
                                1,0,             //读,写【只关心读事件,所以参数2:readevent=1,而参数3:writeevent=0】
                                0,               //其他补充标记
                                EPOLL_CTL_ADD,   //事件类型【增加,还有删除/修改】
                                c                //连接池中的连接 
                                ) == -1) 
        {
            exit(2); //有问题,直接退出,日志 已经写过了
        }

}

4.ngx_epoll_add_event 函数 本质上就是epoll_ctl。并设置了回调函数。

#和网络相关
[Net]
#监听的端口数量,一般都是1个,当然如果支持多于一个也是可以的
ListenPortCount = 2
#ListenPort+数字【数字从0开始】,这种ListenPort开头的项有几个,取决于ListenPortCount的数量,
ListenPort0 = 80
ListenPort1 = 443

#epoll连接的最大数【是每个worker进程允许连接的客户端数】,实际其中有一些连接要被监听socket使用,实际允许的客户端连接数会比这个数小一些
worker_connections = 1024

//初始化函数【fork()子进程之前干这个事】
//成功返回true,失败返回false
bool CSocekt::Initialize()
{
    ReadConf();  //读配置项
    bool reco = ngx_open_listening_sockets();  //打开监听端口
    return reco;
}

//专门用于读各种配置项
void CSocekt::ReadConf()
{
    CConfig *p_config = CConfig::GetInstance();
    m_worker_connections = p_config->GetIntDefault("worker_connections",m_worker_connections); //epoll连接的最大项数
    m_ListenPortCount    = p_config->GetIntDefault("ListenPortCount",m_ListenPortCount);       //取得要监听的端口数量
    return;
}

    //连接池: 数组,元素数量就是worker_connections【1024】,每个数组元素类型为 ngx_connection_t【结构】; ---结构数组;
     //为什么要引入这个数组:  2个监听套接字, 用户连入进来,每个用户多出来一个套接字;
       //把 套接字数字跟一块内存捆绑,达到的效果就是将来我通过这个套接字,就能够把这块内存拿出来;

初始化 连接池

当执行完成如下的代码后,参考图

    do 
    {
        i--;   //注意i是数字的末尾,从最后遍历,i递减至数组首个元素

        //好从屁股往前来---------
        c[i].data = next;         //设置连接对象的next指针,注意第一次循环时next = NULL;
        c[i].fd = -1;             //初始化连接,无socket和该连接池中的连接【对象】绑定
        c[i].instance = 1;        //失效标志位设置为1【失效】,此句抄自官方nginx,这句到底有啥用,后续再研究
        c[i].iCurrsequence = 0;   //当前序号统一从0开始
        //----------------------

        next = &c[i]; //next指针前移
    } while (i); //循环直至i为0,即数组首地址

    m_pfree_connections = next;            //设置空闲连接链表头指针,因为现在next指向c[0],注意现在整个链表都是空的
    m_free_connection_n = m_connection_n;  //空闲连接链表长度,因为现在整个链表都是空的,这两个长度相等;

ngx_get_connection()重要函数:从连接池中找空闲连接;注意在这段代码中 应该只是给 监听fd 一个空闲连接对象。

	for(pos = m_ListenSocketList.begin(); pos != m_ListenSocketList.end(); ++pos)
    {
        c = ngx_get_connection((*pos)->fd); //从连接池中获取一个空闲连接对象

理论上,后面应该要给每一个connectfd也要给一个 空闲连接对象。

    //c)ngx_epoll_add_event() ************
    //    epoll_ctl();
    //d)ev.data.ptr = (void *)( (uintptr_t)c | c->instance);    把一个指针和一个位 合二为一,塞到一个void *中去,
         //后续能够把这两个值全部取出来,如何取,取出来干嘛,后续再说;

    //ps -eo pid,ppid,sid,tty,pgrp,comm,stat,cmd | grep -E 'bash|PID|nginx'

    //如下命令用root权限执行
    //sudo su       获得root权限
    //lsof -i:80    列出哪些进程在监听80端口
    //netstat -tunlp | grep 80

    //总结:
    //a)epoll_create();epoll_ctl();
    //b)连接池技巧ngx_get_connection(),ngx_free_connection();学习这种编程方法;
    //c)同时传递 一个指针和一个二进制数字技巧;

    //(3.2)ngx_epoll_init函数的调用(要在子进程中执行)
    //四章,四节 project1.cpp:nginx中创建worker子进程;
    //nginx中创建worker子进程
    //官方nginx ,一个master进程,创建了多个worker子进程;
    // master process ./nginx 
    // worker process
    //(i)ngx_master_process_cycle()        //创建子进程等一系列动作
    //(i)    ngx_setproctitle()            //设置进程标题    
    //(i)    ngx_start_worker_processes()  //创建worker子进程   
    //(i)        for (i = 0; i < threadnums; i++)   //master进程在走这个循环,来创建若干个子进程
    //(i)            ngx_spawn_process(i,"worker process");
    //(i)                pid = fork(); //分叉,从原来的一个master进程(一个叉),分成两个叉(原有的master进程,以及一个新fork()出来的worker进程
    //(i)                //只有子进程这个分叉才会执行ngx_worker_process_cycle()
    //(i)                ngx_worker_process_cycle(inum,pprocname);  //子进程分叉
    //(i)                    ngx_worker_process_init();
    //(i)                        sigemptyset(&set);  
    //(i)                        sigprocmask(SIG_SETMASK, &set, NULL); //允许接收所有信号
    //(i)                        g_socket.ngx_epoll_init();  //初始化epoll相关内容,同时 往监听socket上增加监听事件,从而开始让监听端口履行其职责
    //(i)                            m_epollhandle = epoll_create(m_worker_connections); 
    //(i)                            ngx_epoll_add_event((*pos)->fd....);
    //(i)                                epoll_ctl(m_epollhandle,eventtype,fd,&ev);
    //(i)                    ngx_setproctitle(pprocname);          //重新为子进程设置标题为worker process
    //(i)                    for ( ;; ) {}. ....                   //子进程开始在这里不断的死循环

    //(i)    sigemptyset(&set); 
    //(i)    for ( ;; ) {}.                //父进程[master进程]会一直在这里循环

上述,我们已经完成了epoll_init,对于 listenfd的 epoll_ctl

那么epoll_wait是什么时候呢?以及对于connectfd的 epoll_ctl以及对于 connectfd的epoll_wait在哪里呢?

ngx_process_events_and_timers(); 这个函数就是做epoll_wait的 //处理网络事件和定时器事件

    g_socket.ngx_epoll_process_events(-1); //-1表示卡着等待把。 -1表示阻塞等待,为什么要阻塞等待,这是 epoll模型决定的,多路IO复用,listenfd wait的时候是阻塞等待。

    //等待事件,事件会返回到m_events里,最多返回NGX_MAX_EVENTS个事件【因为我只提供了这些内存】;
    //阻塞timer这么长时间除非:a)阻塞时间到达 b)阻塞期间收到事件会立刻返回c)调用时有事件也会立刻返回d)如果来个信号,比如你用kill -1 pid测试
    //如果timer为-1则一直阻塞,如果timer为0则立即返回,即便没有任何事件
    //返回值:有错误发生返回-1,错误在errno中,比如你发个信号过来,就返回-1,错误信息是(4: Interrupted system call)
    //       如果你等待的是一段时间,并且超时了,则返回0;
    //       如果返回>0则表示成功捕获到这么多个事件【返回值里】
    int events = epoll_wait(m_epollhandle,m_events,NGX_MAX_EVENTS,timer);

如果是读事件,注意这里是的读事件,是listenfd的读事件,对应的是 客户端有连接过来了。

对应的回调函数是:   ngx_event_accept; 该设定在前面

        if(revents & EPOLLIN)  //如果是读事件
        {
            //一个客户端新连入,这个会成立
            //c->r_ready = 1;               //标记可以读;【从连接池拿出一个连接时这个连接的所有成员都是0】            
            (this->* (c->rhandler) )(c);    //注意括号的运用来正确设置优先级,防止编译出错;【如果是个新客户连入
                                              //如果新连接进入,这里执行的应该是CSocekt::ngx_event_accept(c)】            
                                              //如果是已经连入,发送数据到这里,则这里执行的应该是 CSocekt::ngx_wait_request_handler
        }

        c->rhandler = &CSocekt::ngx_event_accept;

void CSocekt::ngx_event_accept(lpngx_connection_t oldc) 函数调用:

通过accept4函数得到 connectfd。注意这里都是NON_BLOCK,取到值

        if(use_accept4)
        {
            s = accept4(oldc->fd, &mysockaddr, &socklen, SOCK_NONBLOCK); //从内核获取一个用户端连接,最后一个参数SOCK_NONBLOCK表示返回一个非阻塞的socket,节省一次ioctl【设置为非阻塞】调用
        }
        else
        {
            s = accept(oldc->fd, &mysockaddr, &socklen);
        }

设置copnnectfd的 回调函数为 ngx_wait_request_handler函数

通过ngx_epoll_add_event,将 connectfd 加入红黑树。 这时候如果客户端有数据发送过来,就会调用 ngx_wait_request_handler 函数

        newc->rhandler = &CSocekt::ngx_wait_request_handler;  //设置数据来时的读处理函数,其实官方nginx中是ngx_http_wait_request_handler()
        //客户端应该主动发送第一次的数据,这里将读事件加入epoll监控
        if(ngx_epoll_add_event(s,                 //socket句柄
                                1,0,              //读,写
                                EPOLLET,          //其他补充标记【EPOLLET(高速模式,边缘触发ET)】
                                EPOLL_CTL_ADD,    //事件类型【增加,还有删除/修改】                                    
                                newc              //连接池中的连接
                                ) == -1)

也就是说,当客户端有数据发送过来的时候,会调用 ngx_wait_Request_handler函数。在这个函数中

1. 收包,这里就要涉及到一个问题,当我们

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

卫星遥感影像统计农业产量、作物分类及面积

卫星遥感技术的广泛应用为农业领域带来了巨大的变革&#xff0c;其中&#xff0c;卫星遥感影像在农业产量估算方面的应用正成为一项关键技术。通过高分辨率的遥感数据&#xff0c;农业生产者可以更准确、及时地了解农田状况&#xff0c;实现精准农业管理&#xff0c;提高产量和…

零基础如何闯入IT的神秘大门?

前言 随着信息技术的飞速发展&#xff0c;IT行业成为了许多有志之士梦寐以求的职业领域。但对于零基础的人来说&#xff0c;如何成功进入这个行业却是一个不小的挑战。下面&#xff0c;我将结合自身的C语言专业知识&#xff0c;为大家详细阐述一条可行的学习路径&#xff0c;并…

canvas画图,画矩形、圆形、直线可拖拽移动,可拖拽更改尺寸大小

提示&#xff1a;canvas画图&#xff0c;画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动 文章目录 前言一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖拽移动总结 前言 一、画矩形&#xff0c;圆形&#xff0c;直线&#xff0c;曲线可拖…

LLM大语言模型(九):LangChain封装自定义的LLM

背景 想基于ChatGLM3-6B用LangChain做LLM应用&#xff0c;需要先了解下LangChain中对LLM的封装。本文以一个hello world的封装来示例。 LangChain中对LLM的封装 继承关系&#xff1a;BaseLanguageModel——》BaseLLM——》LLM LLM类 简化和LLM的交互 _call抽象方法定义 ab…

教你快速认识Java中的抽象类和接口

目录 引言 抽象类&#xff08;Abstract Class&#xff09; 抽象类的概念 抽象类的图标 抽象类的语法 抽象类的特点 接口&#xff08;Interface&#xff09; 接口的概念 接口的图标 接口的语法 接口的特点 接口的使用 接口的意义 抽象类与接口的区别 Object类 结…

idea开发 java web 高校学籍管理系统bootstrap框架web结构java编程计算机网页

一、源码特点 java 高校学籍管理系统是一套完善的完整信息系统&#xff0c;结合java web开发和bootstrap UI框架完成本系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 前段主要技术 css jq…

最优算法100例之33-数据流的中位数

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。 例如, [2,3,4] 的中位数是 3 [2,3] 的中位…

对PAC的侧信道攻击

PAC存不存在安全性问题&#xff1f;侧信道攻击&#xff1f;本博客探讨这些问题。

达梦DMHS-Manager工具安装部署

目录 1、前言 1.1、平台架构 1.2、平台原理 2、环境准备 2.1、硬件环境 2.2、软件环境 2.3、安装DMHS 2.3.1、源端DMHS前期准备 2.3.2、源端DMHS安装 2.3.3、目的端DMHS安装 3、DMHS-Manager客户端部署 3.1、启动dmhs web服务 3.2、登录web管理平台 4、添加DMHS实…

手搓Docker-Image-Creator(DIC)工具(03):实现alpine+jre的镜像

此篇博客将介绍如何使用 Docker 创建一个alpine3.10-jre1.8.0_401 的 Docker 镜像&#xff0c;并使用 Docker 运行起来。将用到 Dockerfile 的 COPY 命令、RUN 命令、ENV 命令&#xff0c;最终实现基于单一应用的 Dockerfile 构建镜像和运行。 紧急修改&#xff1a;代码我是在m…

python笔记(9)Dictionary(字典)

目录 创建字典 取值 修改字典 删除 内置函数和方法 创建字典 字典键值和value用&#xff1a;隔开&#xff0c;键值是不可变的&#xff0c;而且必须是唯一的&#xff0c;值可以变&#xff0c;可以是任意类型 dict {key1 : value1, key2 : value2 } 1&#xff09;不允许同…

【C语言】“vid”Microsoft Visual Studio安装及应用(检验内存泄露)

文章目录 前言安装包获取配置VLD完成 前言 我们在写代码时往往容易存在内存泄漏的情况&#xff0c;所以存在这样一个名为VLD的工具用来检验内存泄漏&#xff0c;现在我来教大家安装一下 安装包获取 vld下载网址&#xff1a;https://github.com/KindDragon/vld/releases/tag/…

mysql中主键索引和联合索引的原理解析

mysql中主键索引和联合索引的原理解析 一、主键索引二、什么是联合索引? 对应的B树是如何生成的?1、建立索引方式2、什么是最左前缀原则?3、回表4、为什么要遵守最左前缀原则才能利用到索引?5、什么是覆盖索引?6、索引扫描底层原理7、order by为什么会导致索引消失&#x…

vue3和vue2项目中如何根据不同的环境配置基地址?

在不同环境下取出的变量的值是不同的, 像这样的变量称为环境变量 为什么要使用环境变量呢? 开发环境生产环境下的接口地址有可能是不一样的&#xff0c;所以我们需要根据环境去配置不同的接口基地址 1、vue2环境变量配置 在根目录创建&#xff1a;.env.development和.env.p…

RTPS协议概述

一.RTPS协议概述 RTPS协议主要由四个部分组成&#xff1a; 1.发现模块&#xff08;Discovery&#xff09; ​ 发现模块是定义了RTPS的参与者&#xff08;Participant&#xff09;获取其他RTPS的参与者&#xff08;Participant&#xff09;&#xff0c;端点&#xff08;Endpo…

解决沁恒ch592单片机在tmos中使用USB总线时,接入USB Hub无法枚举频繁Reset的问题

开发产品时采用了沁恒ch592&#xff0c;做USB开发时遇到了一个奇葩的无法枚举问题。 典型症状 使用USB线直连电脑时没有问题&#xff0c;可以正常使用。 如果接入某些特定方案的USB Hub&#xff08;例如GL3510、GL3520&#xff09;&#xff0c;可能会出现以下2种情况&#xf…

C# MES通信从入门到精通(9)——c#使用visual studio2019社区版部署webservice服务

前言 在上位机软件开发领域,我们经常使用调用webservice接口来和mes系统进行交互,我们在开发Mes程序之前也可以自己先模拟发布客户的webservice接口进行调用从而加快软件开发进度,本文就是详细介绍如何自己发布webservice接口。 1、创建 参考这篇博文:webservice服务创建…

【MYSQL进阶篇】详细版MYSQL优化

Mysql优化&#xff0c;让你彻底知道优化的各种情况以及解决方案 有一系列的代码和图片让你一步一步走&#xff0c;成为索引的真正实践者 坚持看完这篇文章&#xff0c;会让你功力瞬间提升一个档次 制作不易&#xff0c;觉得不错请点赞收藏 &#xff01…

java——文件上传

一、文件上传——简介 文件上传的简介&#xff1a;文件上传是指将本地计算机中的文件传输到网络上的服务器或另一台计算机上的过程。在 Web 开发中&#xff0c;文件上传通常指的是将用户通过 Web 页面提交的文件&#xff08;如图像、文档、音频、视频等&#xff09;传输到服务器…

包子凑数【蓝桥杯】/完全背包

包子凑数 完全背包 完全背包问题和01背包的区别就是&#xff0c;完全背包问题每一个物品能取无限次。 思路&#xff1a;当n个数的最大公约数不为1&#xff0c;即不互质时&#xff0c;有无限多个凑不出来的&#xff0c;即n个数都可以表示成kn&#xff0c;k为常数且不为1。当n个…