【网络编程】Web服务器shttpd源码剖析——线程池调度

 

hello !大家好呀! 欢迎大家来到我的网络编程系列之web服务器shttpd源码剖析——线程池调度,在这篇文章中,你将会学习到在Linux内核中如何创建一个自己的并发服务器shttpd,并且我会给出源码进行剖析,以及手绘UML图来帮助大家来理解,希望能让大家更能了解网络编程技术!!!

希望这篇文章能对你有所帮助,大家要是觉得我写的不错的话,那就点点免费的小爱心吧!

               

目录

​一.多线程池作用

​1.1 SHTTPD的多客户支持需求

​1.2 SHTTPD多线程池的实现

​二.多线程状态及其函数

 ​2.1 多线程状态

 ​2.2 多线程创建以及销毁


一.多线程池作用

1.1 SHTTPD的多客户支持需求

SHTTPD支持多个客户端的并发连接,在同一时刻允许多个客户同时成功获得服务器上的网络资源,这是现代服务器的基本属性,SHTTPD启动的时候处理单元初始化了多个线程,当客户增加超过上限时候,会根据现场情况增加处理单元。

 如图:

 当超过四个客户端并发访问时,SHTTPD会将后来的请求放在队列排序中,当处理单元空闲时再响应其他请求。

1.2 SHTTPD多线程池的实现

服务器SHTTPD的多客户端支持模块为此程序的主处理模块。在此模块中进行客户端连接的处理、请求数据的接收、响应数据的发送和服务线程的调度。模块的核心部分采用线程池的服务器模型,如图:

 模块初始化的时候,建立线程池,其中的线程负责接收客户端的请求、解析数据并响应数据。当客户端请求到来的时候,主线程查看当前线程池中是否有空闲的工作线程,当没有工作线程的时候会建立新的工作线程,然后分配任务给空闲的线程。

工作线程轮询接收客户端的请求数据,进行请求数据分析并响应请求,处理完毕后,关闭客户端的连接,等待主线程分发下一个请求。

 多客户端模块的线程处理框架如图所示:

主要分为两个部分:线程调度部分和线程退出部分。线程调度部分负责线程初始化、线程的增减、线程的销毁及线程互斥区的保护。线程退出部分则发送信号给工作线程,使得工作线程能够及时地释放资源。这主要应用于接收到用户的信号 SIGINT 时调用。
 

二.多线程状态及其函数

 2.1 多线程状态

工作线程分为多个状态:初始化状态 , 线程空闲状态, 线程运行状态 , 线程退出中状态和线程退出完毕状态:

线程建立的时候状态为线程初始化状态,此时工作线程不可以接受主线程的任务,刚刚进入线程函数。

线程建立完毕的时候进入线程空闲状态,此时可以接受主线程分配的任务,处理客户端的请求,并进行响应。

线程运行状态为线程正在处理客户端请求的时机,可以由空闲状态转入,转入的条件为主线程分配给此线程一个任务。

在处理完客户端请求时,线程可以转入空闲状态,等待下一次客户端请求任务的分配。

线程退出中的状态主要由接收到SIGINT 信号后调用线程退出函数时引起,此时各个线程在非阻塞的时候会及时响应此状态,释放资源、关闭连接。进入线程退出。

进入退出状态后各个线程都释放完申请的动态资源。

 2.2 多线程创建以及销毁

线程控制管理结构:

struct worker_opts{
    pthread_t th;            //线程的ID号
    int flags;                //线程状态
    pthread_mutex_t mutex;//线程任务互斥
    struct worker_ctl *work;//本线程的总控结构
};

之后使用线程初始化函数,将所有线程初始化:

void Worker_Init()
{
    DBGPRINT("LCW==>Worker_Init");
    int i = 0;
    //初始化总控参数
    wctls = (struct worker_ctl*)malloc( sizeof(struct worker_ctl)*conf_para.MaxClient);//开辟空间
    memset(wctls,0, sizeof(*wctls)*conf_para.MaxClient);//清零
    //初始化一些参数
    for(i=0;i<conf_para.MaxClient;i++)
    {
        //opt&connn结构和worker_ctl结构形成回指针
        wctls[i].opts.work = &wctls[i];
        wctls[i].conn.work = &wctls[i];
        //opts结构部分的初始化
        wctls[i].opts.flags = WORKER_DETACHED;
        //wctls[i].opts.mutex = PTHREAD_MUTEX_INITIALIZER;
        pthread_mutex_init(&wctls[i].opts.mutex,NULL);//初始化互斥锁
        pthread_mutex_lock(&wctls[i].opts.mutex);
        //conn部分的初始化
        //con_req&con_res与conn结构形成回指
        wctls[i].conn.con_req.conn = &wctls[i].conn;
        wctls[i].conn.con_res.conn = &wctls[i].conn;
        wctls[i].conn.cs = -1;//客户端socket连接为空
        //conn.con_req部分初始化:请求结构
        wctls[i].conn.con_req.req.ptr = wctls[i].conn.dreq;
        wctls[i].conn.con_req.head = wctls[i].conn.dreq;
        wctls[i].conn.con_req.uri = wctls[i].conn.dreq;
        //conn.con_res部分初始化:响应结构
        wctls[i].conn.con_res.fd = -1; 
        wctls[i].conn.con_res.res.ptr = wctls[i].conn.dres;

    }    
    for (i = 0; i < conf_para.InitClient;i++)
    {
        //增加规定个数工作线程
        Worker_Add(i);
    }
    DBGPRINT("LCW<==Worker_Init\n");
}

 然后使用调度函数对空闲线程进行调度使用:

******************************************************
函数名:worker(void *arg)
参数:worker_ctl *wctls
功能:线程处理函数
*******************************************************/
static void* worker(void *arg)
{
    DBGPRINT("LCW==>worker\n");
    struct worker_ctl *ctl = (struct worker_ctl *)arg;//为何不直接传这个类型过来?
    struct worker_opts *self_opts = &ctl->opts;//定义一个选项结构
    pthread_mutex_unlock(&thread_init);//解锁互斥
    self_opts->flags = WORKER_IDEL;//初始化线程为空闲,等待任务
    //如果主控线程没有让此线程退出,则循环处理任务
    for(;self_opts->flags != WORKER_DETACHING;)//while(self_opts->flags != WORKER_DETACHING)
    {
        //DBGPRINT("work:%d,status:%d\n",(int)self_opts->th,self_opts->flags );
        //查看是否有任务分配
        int err = pthread_mutex_trylock(&self_opts->mutex);//互斥预锁定
        //pthread_mutex_trylock()是pthread_mutex_lock() 的非阻塞版本
        if(err)
        {
            //DBGPRINT("NOT LOCK\n");
            sleep(1);
            continue;
        }
        else
        {
            //有任务,do it
            DBGPRINT("Do task\n");
            self_opts->flags = WORKER_RUNNING;//执行标志
            do_work(ctl);
            close(ctl->conn.cs);//关闭套接字
            ctl->conn.cs = -1;
            if(self_opts->flags == WORKER_DETACHING)
                break;
            else
                self_opts->flags = WORKER_IDEL;
        }
    }
    //主控发送退出命令
    //设置状态为已卸载
    self_opts->flags = WORKER_DETACHED;
    workersnum--;//工作线程-1

    DBGPRINT("LCW<==worker\n");
    return NULL;
}
/******************************************************
函数名:WORKER_ISSTATUS(int status)
参数:欲查询的线程状态
功能:查询线程状态
*******************************************************/
int WORKER_ISSTATUS(int status)
{
    int i = 0;
    for(i = 0; i<conf_para.MaxClient;i++)
    {
        if(wctls[i].opts.flags == status)
            return i;//返回符合的线程
    }
    return -1;//没有符合的线程状态
}

 最后使用线程销毁函数,收回资源:

/******************************************************
函数名:Worker_Destory()
参数:
功能:销毁线程
*******************************************************/
void Worker_Destory()
{
    DBGPRINT("LCW==>Worker_Destory\n");
    int i = 0;
    int clean = 0;

    for(i=0;i<conf_para.MaxClient;i++)
    {
        DBGPRINT("thread %d,status %d\n",i,wctls[i].opts.flags );
        if(wctls[i].opts.flags != WORKER_DETACHED)//如果状态不是已经卸载
            Worker_Delete(i);
    }

    while(!clean)
    {
        clean = 1;
        for(i = 0; i<conf_para.MaxClient;i++)
        {
            DBGPRINT("thread %d,status %d\n",i,wctls[i].opts.flags );
            if(wctls[i].opts.flags == WORKER_RUNNING || wctls[i].opts.flags == WORKER_DETACHING)
                clean = 0;
        }
        if(!clean)
            sleep(1);
    }
    DBGPRINT("LCW<==Worker_Destory\n");
}

当然,这只是一部分源码,但这些是最主要的线程池框架,许多小的细节函数大家可以自己实现或者查资料,找我也是可以的哦!(欢迎私信!!!) 

   好啦!到这里这篇文章就结束啦,关于实例代码中我写了很多注释,如果大家还有不懂得,可以评论区或者私信我都可以哦!! 感谢大家的阅读,我还会持续创造网络编程相关内容的,记得点点小爱心和关注哟!   

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

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

相关文章

allure2教程-3-测试报告定制

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节&#xff0c;我们学习一下pytestallure2生成html测试报告的方法&#xff0c;本小节我们学习一下allure2测试报告的定制。 allure2报告预览 预览网址&#xff1a;https://demo.qameta.io/allure/# allur…

[leetcode] minimum-falling-path-sum

. - 力扣&#xff08;LeetCode&#xff09; 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始&#xff0c;并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多…

归并排序了解吗?手撕一个我看看?

目录 1- 归并排序原理1-1 主要思想1-2 实现步骤 2- 归并排序代码实现(双指针)⭐ 归并排序 ——实现思路 3- ACM模式实现 1- 归并排序原理 1-1 主要思想 归并排序基于分治 将序列中待排序的数数字分为若干组&#xff0c;每个数字分为一组 将若干组两两合并&#xff0c;保证合…

3D模型处理的多进程并行【Python】

今天我们将讨论如何使用 Python 多进程来处理大量3D数据。 我将讲述一些可能在手册中找到的一般信息&#xff0c;并分享我发现的一些小技巧&#xff0c;例如将 tqdm 与多处理 imap 结合使用以及并行处理存档。 NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生…

【蓝桥杯2025备赛】素数判断:从O(n^2)到O(n)学习之路

素数判断:从O( n 2 n^2 n2)到O(n)学习之路 背景:每一个初学计算机的人肯定避免不了碰到素数&#xff0c;素数是什么&#xff0c;怎么判断&#xff1f; 素数的概念不难理解:素数即质数&#xff0c;指的是在大于1的自然数中&#xff0c;除了1和它本身不再有其他因数的自然数。 …

4.18作业

顺序栈&#xff1a; #include "seq_stack.h" seq_p creat_stack() //从堆区申请顺序栈的空间 {seq_p S(seq_p)malloc(sizeof(seq_stack));if(SNULL){printf("空间申请失败\n");return NULL;}bzero(S->data,sizeof(S->data));S->top-1;return S; …

OpenGL:图元

OpenGL的图元 点 GL_POINTS: 将顶点绘制成单个的点 线 GL_LINES:将顶点用于创建线段,2个点成为一条单独的线段。如果顶点个数是奇数,则忽略最后一个。 顶点:v0, v1, v2, v3, … , vn,线段:v0-v1, v2-v3, v4-v5, … , vn-1 - vn GL_LINE_STRIP:将顶点用于创建线段,…

在Linux系统中,禁止有线以太网使用NTP服务器进行时间校准的几种方法

目录标题 方法 1&#xff1a;修改NTP配置以禁止所有同步方法 2&#xff1a;通过网络配置禁用NTP同步方法 3&#xff1a;禁用NTP服务 在Linux系统中&#xff0c;如果想要禁止有线以太网使用NTP服务器进行时间校准&#xff0c;可以通过以下几种方法之一来实现&#xff1a; 方法 …

tcp网络编程——2

1.一个服务器只能有一个客户端连接&#xff08;下面代码&#xff09; ​​​​​​​tcp网络编程&#xff08;基础&#xff09;-CSDN博客 2.一个服务器可以有多个客户端连接&#xff08;多线程&#xff09; server端创建多个线程&#xff0c;每个线程与不同的client端建立连…

代码签名证书的作用及申请

代码签名证书新兴的数字证书的一种&#xff0c;应用范围相对于传统的数字证书而言要稍微少一些。用于验证软件代码的来源和完整性&#xff0c;并提供了一种防止代码被篡改或损坏的机制。常用于软件开发上&#xff0c;代码签名证书由签名证书公钥和私钥证书两部分组成&#xff0…

day05-Elasticsearch01

1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch 是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; 在 GitHub 搜索代码在电商网站搜索商品在百度搜索答案在打…

【工位ubuntu的配置】补充

软件 安装桌面图标的问题 登录密码 root的密码为&#xff1a;19980719 按照如下的链接进行配置&#xff1a; https://blog.csdn.net/zhangmingfie/article/details/131102331?spm1001.2101.3001.6650.3&utm_mediumdistribute.pc_relevant.none-task-blog-2%7Edefault%7E…

永久免费次数ChatGPT国内镜像网站【强烈建议收藏】

gctohttps://chat.tomyres.com/#/pages/web/index?n0 觉得分享的网站好用的话&#xff0c;记得点赞收藏哦。

lettcode179.最大数

问题描述&#xff1a; 给定一组非负整数 nums&#xff0c;重新排列每个数的顺序&#xff08;每个数不可拆分&#xff09;使之组成一个最大的整数。 注意&#xff1a;输出结果可能非常大&#xff0c;所以你需要返回一个字符串而不是整数。 示例一&#xff1a; 输入nums [10…

街景图片语义分割后像素类别提取,用于计算各种指标。

语义分割代码见之前博文&#xff08;免费&#xff09;&#xff1a;deeplabv3街景图片语义分割&#xff0c;无需训练模型&#xff0c;看不懂也没有影响&#xff0c;直接使用。cityscapes 语义分割之后&#xff0c;如下图&#xff0c;想要统计各类像素所占的比例&#xff0c;用于…

2024 MathorCup C 题 物流网络分拣中心货量预测及人员排班

一、问题重述 电商物流网络在订单履约中由多个环节组成&#xff0c;图1是一个简化的物流网络示意图。其中&#xff0c;分拣中心作为网络的中间环节&#xff0c;需要将包裹按照不同流向进行分拣并发往下一个场地&#xff0c;最终使包裹到达消费者手中。分拣中心管理效率的提升&…

初识 React:安装和初步使用指南

文章目录 前言一、React 是什么&#xff1f;1.组件化开发2.虚拟 DOM3.单向数据流4.生态系统丰富 二、安装1.准备工作2.下载react 三、探索 React 应用总结 前言 在当今的 Web 开发领域&#xff0c;React 已经成为了一个备受推崇的技术。它的组件化、灵活性和高效性使得它成为了…

MySQL中InnoDB的行级锁

InnoDB 实现了以下两种类型的行锁。 共享锁&#xff08;S&#xff09;&#xff1a;又称为读锁&#xff0c;简称S锁&#xff0c;共享锁就是多个事务对于同一数据可以共享一把锁&#xff0c;都能访问到数据&#xff0c;但是只能读不能修改。 排他锁&#xff08;X&#xff09;&am…

时间同步服务项目练习

一.配置server主机要求如下&#xff1a; 1.server主机的主机名称为 ntp_server.example.com 2.server主机的IP为&#xff1a; 172.25.254.100 3.server主机的时间为1984-11-11 11&#xff1a;11&#xff1a;11 4.配置server主机的时间同步服务要求可以被所有人使用 更改主机名…

Android开发基础:Activity之间的跳转 向下一个Activity传递数据 给上一个Activity返回数据

目录 一&#xff0c;使用Intent在Activity之间跳转 1.显示使用Intent 2.隐式使用Intent 二&#xff0c;携带数据的跳转 1.Bundle 三&#xff0c;返回数据给上一个Activity 1.registerForActivityResult 一&#xff0c;使用Intent在Activity之间跳转 一个Android应用中包…