【TinyWebServer源码解析】(七)webserver

本文是对github上万star项目源码解析,供大家学习交流
项目地址:
https://github.com/qinguoyi/TinyWebServer

七、webserver

头文件

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <cassert>
#include <sys/epoll.h>

#include "./threadpool/threadpool.h"
#include "./http/http_conn.h"
1、类的定义

Web服务器的类定义,包含了很多成员函数和变量来实现服务器的各种功能。

成员函数包括了一些初始化函数,线程池、数据库连接池、定时器等的相关操作函数。同时还包含了事件监听函数,读写事件处理函数,信号处理函数等,来实现服务器的功能。

const int MAX_FD = 65536;           //最大文件描述符
const int MAX_EVENT_NUMBER = 10000; //最大事件数
const int TIMESLOT = 5;             //最小超时单位

class WebServer
{
public:
    WebServer();
    ~WebServer();

    void init(int port , string user, string passWord, string databaseName,
              int log_write , int opt_linger, int trigmode, int sql_num,
              int thread_num, int close_log, int actor_model);

    void thread_pool();
    void sql_pool();
    void log_write();
    void trig_mode();
    void eventListen();
    void eventLoop();
    void timer(int connfd, struct sockaddr_in client_address);
    void adjust_timer(util_timer *timer);
    void deal_timer(util_timer *timer, int sockfd);
    bool dealclinetdata();
    bool dealwithsignal(bool& timeout, bool& stop_server);
    void dealwithread(int sockfd);
    void dealwithwrite(int sockfd);

public:
    //基础
    int m_port;				// 服务器监听的端口号
    char *m_root;			// 网站根目录
    int m_log_write;		// 日志写入方式
    int m_close_log;		// 是否关闭日志
    int m_actormodel;		// 使用的反应堆模型类型

    int m_pipefd[2];		// 父进程和子进程间通信的管道描述符
    int m_epollfd;			// epoll句柄
    http_conn *users;		// http连接的数组

    //数据库相关
    connection_pool *m_connPool;		// 连接池
    string m_user;         				//登陆数据库用户名
    string m_passWord;     				//登陆数据库密码
    string m_databaseName; 				//使用数据库名
    int m_sql_num;						// 每个线程执行的最大SQL语句数

    //线程池相关
    threadpool<http_conn> *m_pool;		// 线程池
    int m_thread_num;					// 线程池中线程数量

    //epoll_event相关
    epoll_event events[MAX_EVENT_NUMBER];	// 用于存储epoll_wait返回的事件的数组

    int m_listenfd;			// 监听socket描述符
    int m_OPT_LINGER;		// 优雅关闭连接选项
    int m_TRIGMode;			// 触发模式
    int m_LISTENTrigmode;	// 监听socket的触发模式
    int m_CONNTrigmode;		// 连接socket的触发模式

    //定时器相关
    client_data *users_timer;	// 客户连接的定时器信息
    Utils utils;				// 工具集
};
2、构造函数和析构函数

构造函数

WebServer::WebServer(){
    // 在堆中初始化http_conn类对象数组,该数组最大长度为MAX_FD
    users = new http_conn[MAX_FD];

    // 获取当前工作目录,并将其存储在server_path字符数组中
    char server_path[200];
    getcwd(server_path, 200);
    // 将根目录的路径存储在root字符数组中
    char root[6] = "/root";
    // 使用malloc函数在堆上分配一块内存,大小为server_path和root长度之和加1
    m_root = (char *)malloc(strlen(server_path) + strlen(root) + 1);
    // 将server_path和root连接成一个字符串并存储在m_root指针中
    strcpy(m_root, server_path);
    strcat(m_root, root);

    // 这个数组将被用于实现定时器功能
    users_timer = new client_data[MAX_FD];
}

析构函数,调用close()函数关闭了WebServer类对象中成员变量m_epollfd、m_listenfd、m_pipefd[1]和m_pipefd[0]所对应的文件描述符,使用delete[]释放了WebServer类对象中的成员变量users和users_timer所分配的内存空间

WebServer::~WebServer(){
    close(m_epollfd);		// 使用epoll_create1()系统调用创建的epoll实例的文件描述符
    close(m_listenfd);		// 监听套接字的文件描述符
    close(m_pipefd[1]);		// 父子进程之间通信的管道写端的文件描述符
    close(m_pipefd[0]);		// 父子进程之间通信的管道读端的文件描述符
    delete[] users;			// 管理连接到服务器的客户端连接
    delete[] users_timer;	// 每个元素都对应一个客户端连接,并存储了该连接的信息,如socket文件描述符、最后一次活跃时间等
    delete m_pool;			// 线程池对象,用于处理客户端请求
}
3、init()方法

init函数主要用于初始化WebServer类对象的成员变量。其中,该函数的参数包括了需要配置的服务器参数,在函数内部,使用赋值语句为WebServer类对象的成员变量赋值,将传入的参数保存到类中

void WebServer::init(int port, string user, string passWord, string databaseName, int log_write, 
                     int opt_linger, int trigmode, int sql_num, int thread_num, int close_log, int actor_model){
    m_port = port;					// 服务器监听的端口号
    m_user = user;					// 登录数据库所需的用户名
    m_passWord = passWord;			// 登录数据库所需的密码
    m_databaseName = databaseName;	// 需要连接的数据库名称
    m_sql_num = sql_num;			// 每个线程处理的最大数据库任务数
    m_thread_num = thread_num;		// 线程池中的线程数
    m_log_write = log_write;		// 日志写入方式,0表示同步写入,1表示异步写入
    m_OPT_LINGER = opt_linger;		// 优雅关闭连接的方式,0表示不使用,1表示使用
    m_TRIGMode = trigmode;			// 触发模式,0表示LT(水平触发),1表示ET(边缘触发)
    m_close_log = close_log;		// 是否关闭日志,0表示不关闭,1表示关闭
    m_actormodel = actor_model;		// 并发模型选择,0表示Proactor,1表示Reactor
}
4、thread_pool()方法

thread_pool函数用于创建线程池并启动线程池中的线程。该函数使用"threadpool"第三方模板库,通过调用其构造函数new threadpool<http_conn>(m_actormodel, m_connPool, m_thread_num),创建了一个线程池,其中:

void WebServer::thread_pool(){
    /*
    	线程池
    	m_actormodel:并发模型选择,0表示Proactor,1表示Reactor。
		m_connPool:连接池对象,用于管理和复用客户端与服务器之间的连接。
		m_thread_num:线程池中的线程数。
    */
    m_pool = new threadpool<http_conn>(m_actormodel, m_connPool, m_thread_num);
}
5、sql_pool()方法

sql_pool函数用于初始化数据库连接池和读取表

  1. 初始化数据库连接池:调用connection_pool类的GetInstance静态方法获得唯一的连接池实例,并使用该实例的init方法对连接池进行初始化。通过参数指定需要连接的数据库的相关信息,包括地址(“localhost”)、用户名(m_user)、密码(m_passWord)、数据库名称(m_databaseName)、端口号(3306)等。同时,也可以通过设置m_sql_num控制每个线程处理的最大数据库任务数,以及m_close_log控制是否关闭日志输出。
  2. 初始化数据库读取表:使用users数组中的每个http_conn对象的initmysql_result方法,将数据库连接池对象m_connPool作为参数传入。这样,在每个http_conn对象被创建时,都会自动获取一个可用的数据库连接,并用该连接初始化mysql_result对象,以便后续进行数据库查询操作。
void WebServer::sql_pool(){
    // 初始化数据库连接池
    m_connPool = connection_pool::GetInstance();
    m_connPool->init("localhost", m_user, m_passWord, m_databaseName, 3306, m_sql_num, m_close_log);

    // 初始化数据库读取表
    users->initmysql_result(m_connPool);
}
6、log_write()方法

log_write函数主要用于初始化日志输出。具体来说,该函数通过检测成员变量m_close_log的值,判断是否需要关闭日志输出

void WebServer::log_write(){
    // 如果m_close_log为0(即不需要关闭),则调用Log类的get_instance静态方法获得唯一的Log实例,并使用其init方法初始化日志输出
    if (0 == m_close_log){
        // 初始化日志,如果m_log_write为1,则采用异步写入方式
        if (1 == m_log_write)
            Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 800);	
        // 否则采用同步写入方式
        else
            Log::get_instance()->init("./ServerLog", m_close_log, 2000, 800000, 0);
    }
}
7、trig_mode()方法

trig_mode函数主要用于根据参数m_TRIGMode设置监听套接字和连接套接字的触发模式。其中,m_TRIGMode表示触发模式选择,其值0、1、2、3分别代表LT + LT、LT + ET、ET + LT、ET + ET四种组合方式。

在LT模式下,当一个描述符上有可读事件时,每次epoll_wait都能把这个事件通知给用户处理;而在ET模式下,当一个描述符从不可读变为可读时,才将这个事件通知给用户处理一次。通过设置不同的触发模式,可以影响服务器在处理客户端请求时的运行效率和响应速度。

void WebServer::trig_mode(){
    /*
    	m_TRIGMode == 0,则使用LT + LT。
		m_TRIGMode == 1,则使用LT + ET。
		m_TRIGMode == 2,则使用ET + LT。
		m_TRIGMode == 3,则使用ET + ET。
		
    	m_LISTENTrigmode表示监听套接字的触发模式
    	m_CONNTrigmode表示连接套接字的触发模式
    */
    // LT + LT
    if (0 == m_TRIGMode){
        m_LISTENTrigmode = 0;
        m_CONNTrigmode = 0;
    }
    // LT + ET
    else if (1 == m_TRIGMode){
        m_LISTENTrigmode = 0;
        m_CONNTrigmode = 1;
    }
    // ET + LT
    else if (2 == m_TRIGMode){
        m_LISTENTrigmode = 1;
        m_CONNTrigmode = 0;
    }
    // ET + ET
    else if (3 == m_TRIGMode){
        m_LISTENTrigmode = 1;
        m_CONNTrigmode = 1;
    }
}
8、eventListen()方法

eventListen函数主要用于创建套接字、绑定端口、监听连接请求,并初始化epoll内核事件表

通过调用eventListen函数,WebServer可以正常地监听客户端连接请求、初始化epoll内核事件表并进行相关操作,为后续的客户端请求处理提供了必要的基础设施

void WebServer::eventListen(){
    // 网络编程基础步骤,使用socket函数创建TCP套接字
    m_listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(m_listenfd >= 0);

    // 通过setsockopt函数设置SO_LINGER选项为0或1,从而实现优雅关闭连接
    if (0 == m_OPT_LINGER){
        struct linger tmp = {0, 1};
        setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
    }
    else if (1 == m_OPT_LINGER){
        struct linger tmp = {1, 1};
        setsockopt(m_listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof(tmp));
    }

    int ret = 0;
    // 初始化一个IPv4地址结构体sockaddr_in address
    struct sockaddr_in address;
    bzero(&address, sizeof(address));	// 用bzero()函数将address结构体清零,确保其中的所有成员都被初始化为0
    address.sin_family = AF_INET;		// 设置address结构体中的sin_family成员为AF_INET,表示使用IPv4地址族
    address.sin_addr.s_addr = htonl(INADDR_ANY);	// 将address结构体中的sin_addr成员设置为任意IP地址,即0.0.0.0,表示该服务器可以接收来自任何网络接口的连接请求
    address.sin_port = htons(m_port);	// 将address结构体中的sin_port成员设置为指定端口号m_port的网络字节序,以便后续调用bind()函数进行端口绑定

    int flag = 1;
    setsockopt(m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    // 绑定端口和IP地址,使用bind函数将套接字绑定到指定的本地IP地址和端口号上
    ret = bind(m_listenfd, (struct sockaddr *)&address, sizeof(address));
    assert(ret >= 0);
    // 监听连接请求,使用listen函数开始监听客户端连接请求
    ret = listen(m_listenfd, 5);
    assert(ret >= 0);
	// 初始化utils工具类,通过调用utils工具类的init方法,对定时器相关参数进行初始化
    utils.init(TIMESLOT);

    // 创建epoll内核事件表
    epoll_event events[MAX_EVENT_NUMBER];
    // 使用epoll_create函数创建一个epoll内核事件表,返回一个文件描述符
    m_epollfd = epoll_create(5);
    // 若成功则将该文件描述符存储在m_epollfd变量中
    assert(m_epollfd != -1);
	
    // 向epoll内核事件表中添加监听套接字,使用utils工具类的addfd方法,将监听套接字添加到epoll内核事件表中,并设置是否采用LT模式等触发方式,以及是否启用ET模式等触发方式
    utils.addfd(m_epollfd, m_listenfd, false, m_LISTENTrigmode);
    // 设置http_conn类的静态成员变量m_epollfd,将创建好的epoll内核事件表的文件描述符赋值给http_conn类的静态成员变量m_epollfd,以便后续http_conn对象的创建和管理
    http_conn::m_epollfd = m_epollfd;
	
    // 创建双向管道,使用socketpair函数创建一个双向管道,并将文件描述符存储在m_pipefd数组中
    ret = socketpair(PF_UNIX, SOCK_STREAM, 0, m_pipefd);
    assert(ret != -1);
    utils.setnonblocking(m_pipefd[1]);

    // 向epoll内核事件表中添加管道读端,使用utils工具类的addfd方法,将管道读端添加到epoll内核事件表中,并设置是否采用LT模式等触发方式,以及是否启用ET模式等触发方式
    utils.addfd(m_epollfd, m_pipefd[0], false, 0);

    // 设置信号处理函数,使用utils工具类的addsig方法,将SIGPIPE、SIGALRM和SIGTERM三个信号的处理函数设置为SIG_IGN或utils的sig_handler函数
    utils.addsig(SIGPIPE, SIG_IGN);
    utils.addsig(SIGALRM, utils.sig_handler, false);
    utils.addsig(SIGTERM, utils.sig_handler, false);
	// 定时器初始化,使用alarm函数定时器,定时时间为TIMESLOT秒,默认为5秒
    alarm(TIMESLOT);

    // 将双向管道写端、epoll内核事件表文件描述符等变量存储在Utils的静态成员变量u_pipefd和u_epollfd中,以便后续工具类的操作
    Utils::u_pipefd = m_pipefd;
    Utils::u_epollfd = m_epollfd;
}
9、eventLoop()方法

事件循环主函数,主要作用是接收连接请求、处理数据请求和定时器事件

void WebServer::eventLoop(){
    bool timeout = false; // 用于表示是否超时
    bool stop_server = false; // 用于表示是否停止服务器
	
    // 当stop_server变量被设置为true时,表示服务器需要停止运行,则跳出循环,结束主函数的执行
    while (!stop_server){
        // 调用 epoll_wait 函数等待事件的发生,在有事件发生时返回相应的事件类型和文件描述符
        int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);
        if (number < 0 && errno != EINTR){
            LOG_ERROR("%s", "epoll failure");
            break;
        }

        for (int i = 0; i < number; i++){
            int sockfd = events[i].data.fd;

            // 如果事件类型是新连接请求,则调用 dealclinetdata 函数进行处理
            if (sockfd == m_listenfd){
                bool flag = dealclinetdata();
                if (false == flag)
                    continue;
            } 
            // 如果事件类型是客户端断开、服务端关闭连接或出现错误,则移除对应的定时器
            else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){
                util_timer *timer = users_timer[sockfd].timer;
                deal_timer(timer, sockfd);
            }
            // 如果事件类型是信号,则调用 dealwithsignal 函数进行处理
            else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)){
                bool flag = dealwithsignal(timeout, stop_server);
                if (false == flag)
                    LOG_ERROR("%s", "dealclientdata failure");
            }
            // 如果事件类型是客户端发送数据,则调用 dealwithread 函数进行处理
            else if (events[i].events & EPOLLIN){
                dealwithread(sockfd);
            }
            // 如果事件类型是可写,说明之前发起的写操作已完成,则调用 dealwithwrite 函数进行处理
            else if (events[i].events & EPOLLOUT){
                dealwithwrite(sockfd);
            }
        }
        // 如果timeout变量为 true,则表明当前轮询已经超时,需要处理定时器事件
        if (timeout){
            // 提供timer_handler函数来处理链表中已经到期的定时器事件
            utils.timer_handler();
            LOG_INFO("%s", "timer tick");
            timeout = false;
        }
    }
}
10、timer()方法

在每次有新连接时被调用,用于初始化用户数据和创建定时器

该函数的主要目的是为每个连接套接字创建一个定时器,以便在一定时间内未收到客户端请求时能够自动关闭连接,防止服务器资源浪费

void WebServer::timer(int connfd, struct sockaddr_in client_address){
    // 初始化用户数据,使用连接套接字connfd和客户端地址client_address初始化一个User对象,并将其保存在全局数组users中,以便后续使用
    users[connfd].init(connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);

    // 初始化client_data数据
    // 创建定时器,设置回调函数和超时时间,绑定用户数据,将定时器添加到链表中
    users_timer[connfd].address = client_address;
    users_timer[connfd].sockfd = connfd;
    util_timer *timer = new util_timer;
    // 绑定用户数据,将该连接套接字对应的User对象的指针保存在定时器的user_data成员变量中,以便在定时器回调函数中使用
    timer->user_data = &users_timer[connfd];
    // 将定时器的回调函数设置为cb_func
    timer->cb_func = cb_func;
    time_t cur = time(NULL);
    // 将定时器的超时时间设置为当前时间加上3倍的TIMESLOT值(TIMESLOT是一个常量,表示时间片的长度)
    timer->expire = cur + 3 * TIMESLOT;
    users_timer[connfd].timer = timer;
    // 将定时器添加到链表中
    utils.m_timer_lst.add_timer(timer);
}
11、adjust_timer()方法

用于调整定时器的函数。在传输数据时,该函数被调用以延迟定时器并对其在链表上的位置进行调整

//若有数据传输,则将定时器往后延迟3个单位
//并对新的定时器在链表上的位置进行调整
void WebServer::adjust_timer(util_timer *timer){
    time_t cur = time(NULL); // 获取当前时间
    // 将定时器的超时时间设置为当前时间+3个单位的时间片(TIMESLOT),这意味着定时器将在当前时间之后的3个时间片后过期,即等待数据传输完成后再执行回调函数
    timer->expire = cur + 3 * TIMESLOT;
    // 对定时器在链表上的位置进行调整
    utils.m_timer_lst.adjust_timer(timer);

    LOG_INFO("%s", "adjust timer once");
}
12、deal_timer()方法

用于处理定时器的函数。当定时器超时时,该函数被调用以执行定时器回调函数

void WebServer::deal_timer(util_timer *timer, int sockfd){
    // 用定时器的回调函数
    timer->cb_func(&users_timer[sockfd]);
    // 如果定时器存在
    if (timer){
        // 删除定时器
        utils.m_timer_lst.del_timer(timer);
    }

    LOG_INFO("close fd %d", users_timer[sockfd].sockfd);
}
13、dealclinetdata()方法

用于处理客户端数据的函数。在没有数据到达时,该函数会一直阻塞等待客户端连接

bool WebServer::dealclinetdata(){
    struct sockaddr_in client_address;
    socklen_t client_addrlength = sizeof(client_address);
    // 根据服务器的触发模式(m_LISTENTrigmode),执行不同的操作
    // 如果触发模式为0,即LT模式
    if (0 == m_LISTENTrigmode){
        // 则调用accept()函数接受客户端连接请求,并传入参数m_listenfd作为监听套接字
        int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
        // 如果连接失败,则记录错误信息并返回false
        if (connfd < 0){
            LOG_ERROR("%s:errno is:%d", "accept error", errno);
            return false;
        }
        // 如果当前连接数超出最大限制(MAX_FD)
        if (http_conn::m_user_count >= MAX_FD){
            // 则向客户端发送“Internal server busy”消息,并记录错误信息后返回false
            utils.show_error(connfd, "Internal server busy");
            LOG_ERROR("%s", "Internal server busy");
            return false;
        }
        // 否则就创建一个定时器,并将套接字和客户端地址作为参数传入
        timer(connfd, client_address);
    }
	// 如果触发模式为1,即ET模式
    else{
        // 使用while循环接受所有连接请求
        while (1){
            int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
            // 如果连接失败,则记录错误信息并退出循环
            if (connfd < 0){
                LOG_ERROR("%s:errno is:%d", "accept error", errno);
                break;
            }
            // 如果当前连接数超出最大限制(MAX_FD)
            if (http_conn::m_user_count >= MAX_FD){
                // 则向客户端发送“Internal server busy”消息,并记录错误信息后退出循环
                utils.show_error(connfd, "Internal server busy");
                LOG_ERROR("%s", "Internal server busy");
                break;
            }
            // 否则就创建一个定时器,并将套接字和客户端地址作为参数传入
            timer(connfd, client_address);
        }
        return false;
    }
    return true;
}
14、dealwithsignal()方法

用于处理信号的函数。它通过管道接收信号,并根据不同的信号类型执行相应的操作

bool WebServer::dealwithsignal(bool &timeout, bool &stop_server){
    int ret = 0;
    int sig;
    char signals[1024];
    // 使用recv()函数从管道读取信号,并将信号存储在名为“signals”的字符数组中
    ret = recv(m_pipefd[0], signals, sizeof(signals), 0);
    // 如果读取失败,则返回false
    if (ret == -1){
        return false;
    }
    // 如果没有收到任何信号,则返回false
    else if (ret == 0){
        return false;
    }
    // 否则,遍历字符数组并根据不同的信号类型执行相应的操作
    else{
        for (int i = 0; i < ret; ++i){
            switch (signals[i]){
            // 如果收到SIGALRM信号,则将timeout设置为true,表示定时器超时。这通常是由定时器到期引起的,可以让主函数检查此标志并执行相应的操作
            case SIGALRM:{
                timeout = true;
                break;
            }
            // 如果收到SIGTERM信号,则将stop_server设置为true,表示需要停止服务器。这通常是由管理员或操作系统发出的信号,用于安全关闭服务器
            case SIGTERM:{
                stop_server = true;
                break;
            }
            }
        }
    }
    return true;
}
15、dealwithread()方法

用于处理读事件的函数。当服务器监测到套接字上有可读事件时,该函数将被调用以处理数据

void WebServer::dealwithread(int sockfd){
    // 获取与套接字相关联的定时器
    util_timer *timer = users_timer[sockfd].timer;
	// 根据服务器模型(m_actormodel)执行不同的操作
    // 如果服务器模型为1,即采用Reactor模型
    if (1 == m_actormodel){
        if (timer){
            // 将定时器延迟3个单位,并将读事件放入请求队列
            adjust_timer(timer);
        }

        //若监测到读事件,将该事件放入请求队列
        m_pool->append(users + sockfd, 0);
		// 使用while循环等待其他线程处理请求并更新用户状态,直到发现用户状态已经被改变为“improv=1”才退出循环
        while (true){
            if (1 == users[sockfd].improv){
                // 如果定时器标志为1
                if (1 == users[sockfd].timer_flag){
                    // 处理定时器事件
                    deal_timer(timer, sockfd);
                    users[sockfd].timer_flag = 0;
                }
                users[sockfd].improv = 0;
                break;
            }
        }
    }
	// 如果服务器模型为0,即采用Proactor模型
    else{
        //读取套接字上的数据
        if (users[sockfd].read_once()){
            LOG_INFO("deal with the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));
            // 如果读取成功,则将读事件放入请求队列
            m_pool->append_p(users + sockfd);
			// 如果定时器存在
            if (timer){
                // 将其延迟3个单位
                adjust_timer(timer);
            }
        }
        // 如果读取失败,则调用“deal_timer(timer,sockfd)”函数处理定时器事件
        else{
            deal_timer(timer, sockfd);
        }
    }
}
16、dealwithwrite()方法

用于处理写事件的函数。当服务器检测到套接字上有可写事件时,该函数将被调用以向客户端发送数据

void WebServer::dealwithwrite(int sockfd){
    // 获取与套接字相关联的定时器
    util_timer *timer = users_timer[sockfd].timer;
    // 据服务器模型(m_actormodel)执行不同的操作
    // 如果服务器模型为1,即采用Reactor模型
    if (1 == m_actormodel){
        if (timer){
            // 将定时器延迟3个单位
            adjust_timer(timer);
        }
		// 将写事件放入请求队列
        m_pool->append(users + sockfd, 1);
		// 使用while循环等待其他线程处理请求并更新用户状态,直到发现用户状态已经被改变为“improv=1”才退出循环
        while (true){
            if (1 == users[sockfd].improv){
                // 如果定时器标志为1
                if (1 == users[sockfd].timer_flag){
                    // 调用“deal_timer(timer,sockfd)”函数处理定时器事件
                    deal_timer(timer, sockfd);
                    users[sockfd].timer_flag = 0;
                }
                users[sockfd].improv = 0;
                break;
            }
        }
    }
    // 如果服务器模型为0,即采用Proactor模型
    else{
        // 使用“users[sockfd].write()”函数向客户端发送数据
        if (users[sockfd].write()){
            LOG_INFO("send data to the client(%s)", inet_ntoa(users[sockfd].get_address()->sin_addr));
            if (timer){
                // 将定时器延迟3个单位
                adjust_timer(timer);
            }
        }
        else{
            // 果发送失败,则调用“deal_timer(timer,sockfd)”函数处理定时器事件
            deal_timer(timer, sockfd);
        }
    }
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值