webserver类续
bool WebServer::dealclientdata(){LOG_TRACE << "新客户的链接请求到达";struct sockaddr_in client_address;socklen_t client_addrlength = sizeof(client_address);if (0 == m_LISTENTrigmode){LOG_TRACE << "0 == m_LISTENTrigmode: " << (0 == m_LISTENTrigmode);int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);LOG_INFO << "accept ret connfd: " << connfd;if (connfd < 0){LOG_ERROR << " accept error: " << strerror(errno);return false;}if (http_conn::m_user_count >= MAX_FD){utils.show_error(connfd, "Internal server busy");LOG_ERROR << "Internal server busy: m_user_count: " << http_conn::m_user_count;return false;}LOG_TRACE<<"call timer(connfd,client_address)";timer(connfd, client_address);}else{LOG_TRACE << "0 != m_LISTENTrigmode: " << (0 != m_LISTENTrigmode);while (1){int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);LOG_INFO << "accept ret connfd: " << connfd;if (connfd < 0){LOG_ERROR << "accept error : " << strerror(errno);break;}if (http_conn::m_user_count >= MAX_FD){utils.show_error(connfd, "Internal server busy");LOG_ERROR << "Internal server busy m_user_count: " << http_conn::m_user_count;break;}timer(connfd, client_address);}return false;}LOG_TRACE << " function end ";return true;}这段 WebServer::dealclientdata 函数是 Web 服务器处理新客户端连接请求的核心方法,根据监听套接字的触发模式(LT 或 ET),完成客户端连接的接收、资源检查及定时器初始化工作。以下是详细解析:
1. 函数功能概述
当服务器的监听套接字(m_listenfd)通过 epoll 检测到新连接事件(EPOLLIN)时,该函数被调用,主要职责包括:
- 接收客户端的连接请求,获取新连接的文件描述符(
connfd)。 - 检查服务器资源是否充足(如连接数是否达到上限
MAX_FD)。 - 为新连接初始化定时器(通过调用
timer函数),用于后续的超时管理。 - 根据监听套接字的触发模式(LT 或 ET),采用不同的连接处理逻辑。
2. 核心变量说明
m_LISTENTrigmode:监听套接字的触发模式(0 表示 LT 水平触发,1 表示 ET 边缘触发),由trig_mode函数初始化。client_address:存储客户端的网络地址信息(IP 和端口)。connfd:新建立的客户端连接的文件描述符,用于后续与客户端的通信。http_conn::m_user_count:当前服务器已建立的连接总数,用于判断是否超过最大连接限制MAX_FD。
3. 代码逻辑解析
(1)LT 触发模式(m_LISTENTrigmode == 0)
if (0 == m_LISTENTrigmode) {// 接收单个连接int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);// 错误处理:accept 失败if (connfd < 0) {LOG_ERROR << " accept error: " << strerror(errno);return false;}// 连接数达上限:返回错误并关闭连接if (http_conn::m_user_count >= MAX_FD) {utils.show_error(connfd, "Internal server busy");LOG_ERROR << "Internal server busy: m_user_count: " << http_conn::m_user_count;return false;}// 为新连接初始化定时器timer(connfd, client_address);
}
- 处理逻辑:LT 模式下,
epoll会持续通知未处理的连接事件,因此只需调用一次accept接收一个连接即可(后续未处理的连接会在下一次事件循环中被触发)。 - 资源检查:若连接数已达上限(
MAX_FD),通过utils.show_error向客户端返回 “服务器繁忙” 的错误,并关闭该连接。 - 定时器初始化:调用
timer函数为新连接创建定时器,管理其超时逻辑。
(2)ET 触发模式(m_LISTENTrigmode != 0)
else {while (1) {// 循环接收所有待处理的连接int connfd = accept(m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);// 错误处理:无更多连接时退出循环(ET 模式下 accept 返回 -1 且 errno 为 EAGAIN 或 EWOULDBLOCK)if (connfd < 0) {LOG_ERROR << "accept error : " << strerror(errno);break;}// 连接数达上限:返回错误并关闭连接,退出循环if (http_conn::m_user_count >= MAX_FD) {utils.show_error(connfd, "Internal server busy");LOG_ERROR << "Internal server busy m_user_count: " << http_conn::m_user_count;break;}// 为新连接初始化定时器timer(connfd, client_address);}return false;
}
- 处理逻辑:ET 模式下,
epoll仅在连接事件首次发生时通知一次,因此需要通过while(1)循环调用accept,直到返回错误(表示当前已无新连接待处理),避免遗漏连接。 - 退出条件:当
accept返回-1时(通常因errno为EAGAIN或EWOULDBLOCK,表示暂时无新连接),退出循环。 - 返回值:ET 模式下处理完所有连接后返回
false,不影响后续事件循环(因所有连接已一次性处理完毕)。
4. 关键作用总结
- 连接接收:根据监听模式(LT/ET)差异,分别采用单次或循环
accept方式,确保不遗漏客户端连接。 - 资源控制:通过检查
m_user_count限制最大连接数,防止服务器资源耗尽。 - 超时管理前置:为每个新连接绑定定时器(
timer函数),是后续超时连接自动清理的基础。
该函数是服务器处理并发连接的入口,通过适配不同的触发模式,平衡了连接处理的效率与可靠性。
bool WebServer::dealwithsignal(bool &timeout, bool &stop_server){LOG_INFO<<"call dealwithsignal timeout: "<<timeout<<" stop_server: "<<stop_server;int ret = 0;int sig;char signals[1024];ret = recv(m_pipefd[0], signals, sizeof(signals), 0);LOG_INFO<<"call recv:m_pipefd[0]:" <<m_pipefd[0]<<" ret: "<<ret;if (ret == -1){return false;}else if (ret == 0){return false;}else{for (int i = 0; i < ret; ++i){LOG_INFO<<"signals[i]: "<<static_cast<int>(signals[i]);LOG_INFO<<" SIGALRM: "<<14;LOG_INFO<<" SIGTERM: "<<15;switch (signals[i]){case SIGALRM: // 当使用alarm函数设置一个定时器后,当定时器时间到达,就会产生SIGALRM信号timeout = true;break;case SIGTERM: // SIGTERM是一种信号(Signal),用于在类 Unix 操作系统中请求一个进程终止stop_server = true;break;}}}LOG_TRACE<<" end ";return true;}这段 WebServer::dealwithsignal 函数是 Web 服务器中处理信号事件的核心方法,主要作用是解析通过管道传递的信号,并根据信号类型设置对应的标志位,以触发后续的超时处理或服务器停止逻辑。以下是详细解析:
1. 函数功能概述
在 Linux 系统中,信号是进程间通信的一种方式。服务器通过 socketpair 创建了一对管道(m_pipefd),当信号(如定时器超时信号 SIGALRM、终止信号 SIGTERM)产生时,信号处理函数会将信号写入管道的写端(m_pipefd[1]),而本函数则从管道的读端(m_pipefd[0])读取信号,并根据信号类型设置 timeout 或 stop_server 标志,供主事件循环(eventLoop)处理。
2. 代码逐行解析
LOG_INFO<<"call dealwithsignal timeout: "<<timeout<<" stop_server: "<<stop_server;
- 日志输出,记录函数调用时
timeout(超时标志)和stop_server(服务器停止标志)的初始状态,用于调试。
int ret = 0;
int sig;
char signals[1024];
ret = recv(m_pipefd[0], signals, sizeof(signals), 0);
- 从管道读端(
m_pipefd[0])读取信号数据:signals数组用于存储读取到的信号(每个信号以字符形式存储),ret接收实际读取的字节数。 - 管道的作用:将信号事件转换为 I/O 事件,使服务器能在
epoll主循环中统一处理信号(避免信号处理函数中直接操作复杂逻辑)。
LOG_INFO<<"call recv:m_pipefd[0]:" <<m_pipefd[0]<<" ret: "<<ret;
if (ret == -1)
{return false;
}
else if (ret == 0)
{return false;
}
- 错误处理:若
recv失败(ret == -1)或读取到 0 字节(管道关闭),返回false表示信号处理失败。
else
{for (int i = 0; i < ret; ++i){LOG_INFO<<"signals[i]: "<<static_cast<int>(signals[i]);LOG_INFO<<" SIGALRM: "<<14;LOG_INFO<<" SIGTERM: "<<15;switch (signals[i]){case SIGALRM: // 定时器超时信号timeout = true;break;case SIGTERM: // 进程终止请求信号stop_server = true;break;}}
}
- 解析信号:循环处理读取到的每个信号(
ret为实际读取的信号数量)。SIGALRM(值为 14):由alarm(TIMESLOT)定时触发,设置timeout = true,通知主循环执行定时器检查(清理超时连接)。SIGTERM(值为 15):通常由kill命令发送,请求进程终止,设置stop_server = true,通知主循环退出并关闭服务器。
- 日志输出信号值,便于调试时确认信号类型。
LOG_TRACE<<" end ";
return true;
- 日志标记函数结束,返回
true表示信号处理成功。
3. 调用场景与核心作用
- 调用时机:当
epoll检测到管道读端(m_pipefd[0])有可读事件(EPOLLIN)时,在主循环(eventLoop)中调用本函数。 - 核心作用:
- 统一处理信号:将信号事件转换为管道 I/O 事件,使服务器能在
epoll框架中统一处理所有事件(信号、连接、读写等),简化逻辑。 - 触发后续操作:通过设置
timeout和stop_server标志,让主循环决定是否执行定时器检查或停止服务器,避免在信号处理函数中直接操作共享资源(减少线程安全问题)。
- 统一处理信号:将信号事件转换为管道 I/O 事件,使服务器能在
该函数是服务器信号处理机制的关键,确保了信号能被安全、有序地处理,同时与主事件循环无缝衔接。
void WebServer::dealwithread(int sockfd){util_timer *timer = users_timer[sockfd].timer;LOG_TRACE<<"sockfd: "<<sockfd<<" timer: "<<timer;LOG_TRACE<<" m_actormode: "<<m_actormodel;// reactorif (1 == m_actormodel){LOG_INFO<<"1 == actormodel";if (timer){adjust_timer(timer);}// 若监测到读事件,将该事件放入请求队列LOG_TRACE<<"若监测到读事件,将该事件放入请求队列 call m_pool->append(users+sockfd)";m_pool->append(users + sockfd, 0);while (true){if (1 == users[sockfd].improv){if (1 == users[sockfd].timer_flag){deal_timer(timer, sockfd);users[sockfd].timer_flag = 0;}users[sockfd].improv = 0;break;}}}else{// proactorLOG_TRACE<<"(0 == m_actormodel) : proactor: 主动器";LOG_TRACE<<"sockfd: "<<sockfd<<" users[sockfd]: "<<&users[sockfd];if (users[sockfd].read_once()){LOG_INFO << "deal with the client ip: " << inet_ntoa(users[sockfd].get_address()->sin_addr);// 若监测到读事件,将该事件放入请求队列LOG_INFO<<"若监测到读事件,将该事件放入请求队列 m_pool->append_p(users+sockf)";m_pool->append_p(users + sockfd);if (timer){adjust_timer(timer);}}else{deal_timer(timer, sockfd);}}}
void WebServer::eventLoop(){bool timeout = false;bool stop_server = false;LOG_TRACE << "start runing";while (!stop_server){LOG_TRACE << "call epoll_wait: ";int number = epoll_wait(m_epollfd, events, MAX_EVENT_NUMBER, -1);LOG_TRACE << "epoll_wait ret number = " << number;if (number < 0 && errno != EINTR){LOG_ERROR << "epoll failure";break;}for (int i = 0; i < number; i++){int sockfd = events[i].data.fd;// 处理新到的客户连接if (sockfd == m_listenfd){LOG_TRACE << " 处理新到的客户连接: sockfd: " << sockfd;LOG_TRACE << "m_listenfd : " << m_listenfd;bool flag = dealclientdata();if (false == flag){continue;}}else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)){// 服务器端关闭连接,移除对应的定时器LOG_INFO<<"服务器端关闭连接,移除对应的定时器 ";util_timer *timer = users_timer[sockfd].timer;deal_timer(timer, sockfd);}else if ((sockfd == m_pipefd[0]) && (events[i].events & EPOLLIN)){// 处理信号LOG_INFO << " 处理信号";bool flag = dealwithsignal(timeout, stop_server);if (false == flag){LOG_ERROR << "dealclientdata failure";}}// 处理客户连接上接收到的数据else if (events[i].events & EPOLLIN){LOG_INFO<<"处理客户连接上接收到的数据";dealwithread(sockfd);}else if (events[i].events & EPOLLOUT){LOG_INFO<<"写数据 ";dealwithwrite(sockfd);}}if (timeout){LOG_INFO<<"timeout: "<<timeout;LOG_INFO<<"call utils.timer_handler(): ";utils.timer_handler();LOG_INFO << "timer tick"; // 定时器滴答timeout = false;}}LOG_TRACE << " running stop ";}
