Linux_网络项目_WEB服务器 处理服务器写入失败后sigpipe信号导致服务器崩溃退出问题,引入线程池缓解大量请求,服务器组件化重构,在线计算机业务测试

1. 处理服务器写入管道出错

经过测试,服务器在读取报文时如果出错可以选择直接关闭这个TCP里链接来节省资源。这个问题好解决不在赘述

此外如果服务器使用CGI时,向管道写入数据失败时,服务器进程会收到sigpipe信号直接崩溃。所以这里选择直接忽视这个信号。

class HttpSever
{
private:
    int port;
    TcpSever *tcp_sever;
    bool states; // 标记服务器运行状态
    void InitSever()
    {
        // 信号SIGPIPE需要进行忽略,防止服务器写入管道时失败服务器崩溃
        signal(SIGPIPE, SIG_IGN);
        tcp_sever = TcpSever::GetInstance(port);
    }

public:
    HttpSever(int _port = PORT)
    {
        port = _port;
        tcp_sever = nullptr;
        states = true;
        InitSever();
    }
    ~HttpSever() {}
    void Loop()
    {
        LOG(INFO, "---http sever loop begin---");
        int listen_socket = tcp_sever->GetLinstenSocket();
        while (states != false)
        {
            struct sockaddr_in client; // 客户端信息
            socklen_t len = sizeof(client);
            int sock = accept(listen_socket, (struct sockaddr *)&client, &len);
            if (sock < 0)
            {
                // 套接字监听失败
                continue;
            }
            LOG(INFO, "get a new link");
            int *_sock = new int(sock);
            pthread_t tid = 0;
            pthread_create(&tid, nullptr, Entrance::HanderReq, _sock);
            pthread_detach(tid); // 线程分离
        }
    }
};

2. 引入线程池缓解大量请求导致服务器崩溃

设计线程任务类

#pragma once

#include <iostream>
#include "../Protocol.hpp"

// 设计线程任务队列
class Task
{
private:
    int sock;
    CallBack callback; // 设置回调
public:
    Task() {}
    Task(int _sock)
    {
        sock = _sock;
    }
    void ProcessOn()
    {
        callback(sock);
    }
};

服务器在收到链接时不创建线程,而是构建线程任务,然后将线程任务放入到线程任务队列上,最后线程池从任务队列上拿去任务处理即可,线程拿到任务后,通过任务里的CallBack回调函数执行不同的任务

线程任务CallBack设计紧贴前面重构前的代码,将Entrance类更改成CallBack即可,这里设计仿函数,线程回调函数通过()直接访问解析请求函数

// 线程工作入口
class CallBack
{
public:
    void operator()(int sock)//仿函数
    {
        HanderReq(sock);
    }
    // 处理HTTP请求
    static void HanderReq(int _sock)
    {
        LOG(INFO, "http request hander begin");
        EndPoint *endpoint = new EndPoint(_sock);
        endpoint->ReadRequest();
        if (endpoint->Stop() != true)
        {
            LOG(INFO, "recv success! build request begin");
            endpoint->BuildResponse();
            endpoint->SendResponse();
        }
        else
        {
            LOG(WARNING, "recv error! please try again");
        }
        delete endpoint;
        LOG(INFO, "http request hander end");
    }
};

单例线程池组件设计

ThreadPool

#pragma once
#include <iostream>
#include "task.hpp"
#include <queue>
#include <pthread.h>
#include "../log/log.hpp"
#define THREAD_NUM 6
// 单例模式
class ThreadPool
{
private:
    // 生产者消费者模型
    std::queue<Task> task_queue; // 临界资源
    int thread_count;            // 线程数
    bool stop;                   // 线程池是否停止
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    static ThreadPool *instance;
    bool init_threadPool() // 初始化线程池
    {
        for (int i = 0; i < thread_count; i++)
        {
            pthread_t thread_id;
            if (0 != pthread_create(&thread_id, NULL, thread_routine, this))
            {
                // 创建线程失败
                LOG(FATAL, "init threadpool error!");
                return false;
            }
        }
        LOG(INFO, "http sever init threadpool success");
        return true;
    }
    static void *thread_routine(void *args) // 线程执行函数 static 删除类的this指针,对象通过线程的参数传递
    {
        ThreadPool *pool = (ThreadPool *)args;
        while (!pool->stop)
        {
            Task task;
            pthread_mutex_lock(&pool->mutex);
            while (pool->task_queue.empty()) // while 不能换成if
            {
                pool->thread_wait(); // 线程唤醒后一定占有互斥锁
            }
            task = pool->task_queue.front();
            pool->task_queue.pop();
            pthread_mutex_unlock(&pool->mutex);
            task.ProcessOn();
        }
        return NULL;
    }
    void thread_wait() // 任务队列无任务,线程休眠
    {
        pthread_cond_wait(&cond, &mutex);
    }
    void thread_wakeup() // 任务队列有任务,线程唤醒
    {
        pthread_cond_signal(&cond); // 唤醒一个线程即可
    }
    ThreadPool(int thread_num = THREAD_NUM)
    {
        thread_count = thread_num;
        stop = false;
        pthread_mutex_init(&mutex, nullptr);
        pthread_cond_init(&cond, nullptr);
        init_threadPool();
    }
    ThreadPool(const ThreadPool &) = delete;

public:
    static ThreadPool *GetInstance()
    {
        static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 静态锁直接初始化,不需要释放
        if (instance == nullptr)
        {
            pthread_mutex_lock(&lock);
            if (instance == nullptr)
            {
                instance = new ThreadPool();
            }
            pthread_mutex_unlock(&lock);
        }
        return instance;
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&cond);
    }
    void push_task(const Task &task)
    {
        pthread_mutex_lock(&mutex);
        task_queue.push(task);
        pthread_mutex_unlock(&mutex);
        thread_wakeup();
    }
    bool is_stop()
    {
        return stop;
    }
};

ThreadPool *ThreadPool::instance = nullptr;

HttpSever函数更换为线程池同时组件化

#pragma once
#include "./TcpSever/TcpSever.hpp"
#include "Protocol.hpp"
#include "./log/log.hpp"
#include <signal.h>
#include <pthread.h>
#include "./ThreadPool/task.hpp"
#include "./ThreadPool/threadpool.hpp"
#define PORT 8080

class HttpSever
{
private:
    int port;
    TcpSever *tcp_sever;
    bool states; // 标记服务器运行状态
    ThreadPool threadpool;
    void InitSever()
    {
        // 信号SIGPIPE需要进行忽略,防止服务器写入管道时失败服务器崩溃
        signal(SIGPIPE, SIG_IGN);
        tcp_sever = TcpSever::GetInstance(port);
    }

public:
    HttpSever(int _port = PORT)
    {
        port = _port;
        tcp_sever = nullptr;
        states = true;
        InitSever();
    }
    ~HttpSever() {}
    void Loop()
    {
        LOG(INFO, "---http sever loop begin---");
        int listen_socket = tcp_sever->GetLinstenSocket();
        while (states != false)
        {
            struct sockaddr_in client; // 客户端信息
            socklen_t len = sizeof(client);
            int sock = accept(listen_socket, (struct sockaddr *)&client, &len);
            if (sock < 0)
            {
                // 套接字监听失败
                continue;
            }
            LOG(INFO, "get a new link");
            // 构建任务
            Task task(sock);
            threadpool.push_task(task);
        }
    }
};

测试运行结果如下:线程池创建6个线程,初始化线程池正确。同时用户报文发送错误,日志级别WARING级别
在这里插入图片描述

3.代码位置

Github
Gitee

4. 在线计算机业务运行截图

在这里插入图片描述
在这里插入图片描述
除0错误等其他错误会导致子进程错误退出,服务器捕捉到后跳转到错误页面即404.html
在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值