跳到主要内容
深入解析 C++ 轻量级 WebServer 实现 | 极客日志
C++ 算法
深入解析 C++ 轻量级 WebServer 实现 综述由AI生成 基于 BS 模型的高并发 C++ WebServer 实现解析。涵盖 Epoll 触发模式(LT/ET)、线程池管理、数据库连接池复用、异步日志系统及定时器机制。详细阐述了 WebServer 类初始化、事件循环、HTTP 请求解析及内存映射文件读取流程。通过 Reactor 与 Proactor 模型对比,展示了高并发场景下的资源调度策略与优雅关闭设计。
竹影清风 发布于 2026/3/16 更新于 2026/5/3 21 浏览概述
这是基于 BS 模型并且使用 epoll 实现的高并发服务器,使用线程池 + 连接池 + 使用同步或异步的日志系统 + 定时器等。
核心组件说明
线程池 :线程池实现主要是为了高效处理大量短期任务,特别是在高并发服务器中管理连接和请求。
数据库连接池 :是一个负责分配、管理和释放数据库连接的技术组件。它充当了应用程序和数据库之间的一个'缓冲层',通过复用已经建立的数据库连接,来避免频繁创建和关闭连接所带来的巨大性能开销。
日志系统 :是指一套用于生成、收集、存储、分析和展示应用程序运行时产生的事件、状态和错误信息的软件组件、库和工具的集合。它的核心目的是:为开发、测试和运维人员提供洞察应用内部行为的能力,以便于调试、监控和审计。
定时器 :是一种编程组件或系统服务,它允许你调度一个任务(一段代码/函数)在未来的某个特定时间点执行一次,或者以固定的时间间隔重复执行。它的核心思想是:将任务的执行与时间的流逝解耦,由系统来负责时间管理,并在恰当时机触发回调。
Main.cpp
#include "config.h"
int main (int argc, char *argv[]) {
string user = "root" ;
string passwd = "123456" ;
string databasename = "yourdb" ;
Config config;
config.parse_arg (argc, argv);
WebServer server;
server.init (config.PORT, user, passwd, databasename, config.LOGWrite, config.OPT_LINGER, config.TRIGMode, config.sql_num, config.thread_num, config.close_log, config.actor_model);
server.log_write ();
server.sql_pool ();
server.thread_pool ();
server.trig_mode ();
server.eventListen ();
server.eventLoop ();
return 0 ;
}
Webserver.h+Webserver.cpp
WebServer 类封装了服务器主要逻辑包括初始化、事件循环和事件处理,比较核心,所以选择看完 main.cpp 后看 WebServer 类。
Webserver 类的 init 的函数参数讲解 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) ;
其中 log_write 参数是日志写入方式:分同步写入和异步写入
同步写入 :在应用程序执行日志记录语句时,必须等待日志数据被完全、安全地写入到持久化存储(如硬盘)后,才能执行后续代码
异步写入 :应用程序执行日志记录语句时,将日志消息放入到一个内存缓冲区后便立即返回,不等待数据落盘。由一个后台线程负责将缓冲区中的日志批量、定期地写入磁盘
opt_linger 参数是优雅关闭选项:
优雅关闭 指的是当一个应用程序需要关闭时,它不是强制立即终止,而是通过一个预设的流程,有序的完成正在进行的任务、释放资源、并通知上下游组件,然后才安全退出的过程。
相反的是暴力关闭 :即立即向进程发送 SIGKILL(-9)信号,进程会立即停止,无论它在做什么。
trigmode 参数指的是触发模式:
LT(水平触发):只要条件满足,就会一直通知你
ET(边缘触发):只有当条件发生变化时,它才会通知你一次。
假设有一个 Socket,其接收缓冲区里收到了 2KB 的数据。
1. LT(水平触发)的工作方式 epoll_wait 函数返回,通知你这个 socket 有数据可读(读就绪)。你决定只从缓冲区读取 1KB 的数据。然后你再次调用 epoll_wait。关键点 :因为缓冲区里还有 1KB 数据(仍然处于'可读'状态),所以 epoll_wait 会立即再次返回 ,通知你这个 socket 仍然可读。你再去读取剩下的 1KB 数据。此时缓冲区为空,epoll_wait 才会休眠,直到下次有新数据到来。
LT 的优点 :编程更安全。即使你一次没有处理完所有数据,下次还会得到通知,不会丢失数据。
LT 的缺点 :可能会导致效率略低。因为如果对方发送数据很快,你每次只读一部分,会导致 epoll_wait 被频繁唤醒,即使还有旧数据没读完。
2. ET(边缘触发)的工作方式 当数据包到达,缓冲区从空变为非空 时,epoll_wait 返回,通知你这个 socket 有数据可读。你必须用一个循环,一直读 这个 socket,直到 read 系统调用返回 -1,并且错误码 errno 为 EAGAIN 或 EWOULDBLOCK(表示本次通知下的所有数据已读完)。如果你只读了 1KB 就停止了,然后再次调用 epoll_wait。关键点 :epoll_wait 会阻塞 ,即使缓冲区里还有 1KB 数据!因为它只在乎变化的那一瞬间。只要缓冲区里还有数据,状态就没有从'空'变为'非空'的变化(空到非空的意思是没有新数据的加入) ,所以它不会通知你。只有当对端再次发送数据,导致缓冲区从空(或未满)变为有数据(或更多数据) 时,epoll_wait 才会再次返回。
ET 的优点 :减少了事件被触发的次数,理论上性能更高,尤其是在高并发、大流量的场景下。
ET 的缺点 :必须使用非阻塞 I/O :因为你必须一次性读完所有数据,如果使用阻塞 I/O,在读完所有数据后,最后一次 read 会阻塞住线程,导致程序卡死。必须保证一次性处理完 :如果一次没有读完所有数据,剩下的数据将永远无法被读取,直到下一个数据包到来触发新的通知。
actor_model 参数指的是并发模型:
Reactor 模式(非阻塞 I/O+ 就绪通知):应用程序向内核询问'I/O 是否就绪'当就绪时,由应用程序自己执行实际的 I/O 操作(将数据从内核缓冲区读到用户空间)。
Proactor 模式(异步 I/O):应用程序发起一个异步 I/O 操作,内核(或底层框架)负责完成整个 I/O 操作(包括将数据读到用户空间),操作完成后,再通知应用程序。
#ifndef WEBSERVER_H
#define WEBSERVER_H
#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"
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 () ;
bool dealclientdata () ;
bool dealwithsignal (bool & timeout, bool & stop_server) ;
void dealwithread (int sockfd) ;
void dealwithwrite (int sockfd) ;
void timer (int connfd, struct sockaddr_in client_address) ;
void adjust_timer (util_timer* timer) ;
void deal_timer (util_timer *timer, 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;
http_conn* users;
connection_pool *m_connPool;
string m_user;
string m_passWord;
string m_databaseName;
int m_sql_num;
threadpool<http_conn>* m_pool;
int m_thread_num;
epoll_event events[MAX_EVENT_NUMBER];
int m_listenfd;
int m_OPT_LINGER;
int m_TRIGMode;
int m_LISTENTrigmode;
int m_CONNTrigmode;
client_data *users_timer;
Utils utils;
};
#endif
Webserver.cpp
WebServer 类的 WebServer 实现:
WebServer::WebServer () {
users = new http_conn[MAX_FD];
char server_path[200 ];
getcwd (server_path, 200 );
char root[6 ] = "/root" ;
m_root = (char *)malloc (strlen (server_path) + strlen (root) + 1 );
strcpy (m_root, server_path);
strcat (m_root, root);
users_timer = new client_data[MAX_FD];
}
char *getcwd (char *buf, size_t size) ;
char *strcpy (char *dest, const char *src) ;
char *strcat (char *dest, const char *src) ;
WebServer::~WebServer() 的实现
WebServer::~WebServer () {
close (m_epollfd);
close (m_listenfd);
close (m_pipefd[1 ]);
close (m_pipefd[0 ]);
delete [] users;
delete [] users_timer;
delete m_pool;
}
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;
m_OPT_LINGER = opt_linger;
m_TRIGMode = trigmode;
m_close_log = close_log;
m_actormodel = actor_model;
}
WebServer::trig_mode() 的实现:
void WebServer::trig_mode () {
if (0 == m_TRIGMode) {
m_LISTENTrigmode = 0 ;
m_CONNTrigmode = 0 ;
}
else if (1 == m_TRIGMode) {
m_LISTENTrigmode = 0 ;
m_CONNTrigmode = 1 ;
}
m_ else if (2 == m_TRIGMode) {
m_LISTENTrigmode = 1 ;
m_CONNTrigmode = 0 ;
}
else if (3 == m_TRIGMode) {
m_LISTENTrigmode = 1 ;
m_CONNTrigmode = 1 ;
}
}
void WebServer::log_write () {
if (0 == m_close_log) {
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 );
}
}
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);
}
void WebServer::thread_pool () {
m_pool = new threadpool <http_conn>(m_actormodel, m_connPool, m_thread_num);
}
#include <assert.h>
assert(expression);如果 expression 为真(非零),程序继续正常执行如果 expression 为假(零),assert 会:输出错误信息到 stderr 调用 abort() 终止程序
struct linger {
int l_onoff; // 是否启用 linger 选项 (0=禁用,非 0=启用)
int l_linger; // 等待时间(秒)
};
l_onoff 决定 close() 函数的行为模式:**l_onoff = 0**:close() 立即返回,系统后台处理 关闭 **l_onoff = 1**:close() 可能阻塞 ,程序亲自等待 关闭完成
l_linger只在 l_onoff = 1 时起作用 !当 l_onoff = 0 时:l_linger 被完全忽略当 l_onoff = 1 时:l_linger 决定等待行为
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
void WebServer::eventListen () {
m_listenfd = socket (PF_INET, SOCK_STREAM, 0 );
assert (m_listenfd >= 0 );
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 ;
struct sockaddr_in address;
bzero (&address, sizeof (address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl (INADDR_ANY);
address.sin_port = htons (m_port);
int flag = 1 ;
setsockopt (m_listenfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof (flag));
ret = bind (m_listenfd, (struct sockaddr *)&address, sizeof (address));
assert (ret >= 0 );
ret = listen (m_listenfd, 5 );
assert (ret >= 0 );
utils.init (TIMESLOT);
epoll_event events[MAX_EVENT_NUMBER];
m_epollfd = epoll_create (5 );
assert (m_epollfd != -1 );
utils.addfd (m_epollfd, m_listenfd, false , m_LISTENTrigmode);
http_conn::m_epollfd = m_epollfd;
ret = socketpair (PF_UNIX, SOCK_STREAM, 0 , m_pipefd);
assert (ret != -1 );
utils.setnonblocking (m_pipefd[1 ]);
utils.addfd (m_epollfd, m_pipefd[0 ], false , 0 );
utils.addsig (SIGPIPE, SIG_IGN);
utils.addsig (SIGALRM, utils.sig_handler, false );
utils.addsig (SIGTERM, utils.sig_handler, false );
alarm (TIMESLOT);
Utils::u_pipefd = m_pipefd;
Utils::u_epollfd = m_epollfd;
}
void WebServer::timer (int connfd, struct sockaddr_in client_address) {
users[connfd].init (connfd, client_address, m_root, m_CONNTrigmode, m_close_log, m_user, m_passWord, m_databaseName);
users_timer[connfd].address = client_address;
users_timer[connfd].sockfd = connfd;
util_timer *timer = new util_timer;
timer->user_data = &users_timer[connfd];
timer->cb_func = cb_func;
time_t cur = time (NULL );
timer->expire = cur + 3 * TIMESLOT;
users_timer[connfd].timer = timer;
utils.m_timer_lst.add_timer (timer);
}
void WebServer::adjust_timesr (util_timer *timer) {
time_t cur = time (NULL );
timer->expire = cur + 3 * TIMESLOT;
utils.m_timer_lst.adjust_timer (timer);
LOG_INFO ("%s" , "adjust timer once" );
}
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);
}
bool WebServer::dealclientdata () {
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof (client_address);
if (0 == m_LISTENTrigmode)
{
int connfd = accept (m_listenfd, (struct sockaddr *)&client_address, &client_addrlength);
if (connfd < 0 )
{
LOG_ERROR ("%s:errno is:%d" , "accept error" , errno);
return false ;
}
if (http_conn::m_user_count >= MAX_FD)
{
utils.show_error (connfd, "Internal server busy" );
LOG_ERROR ("%s" , "Internal server busy" );
return false ;
}
timer (connfd, client_address);
}
else
{
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 ;
}
if (http_conn::m_user_count >= MAX_FD)
{
utils.show_error (connfd, "Internal server busy" );
LOG_ERROR ("%s" , "Internal server busy" );
break ;
}
timer (connfd, client_address);
}
return false ;
}
return true ;
}
函数原型
recv 函数在 <sys/socket.h> 头文件中声明,其标准原型如下:
参数详解 **int sockfd含义 :一个已连接的套接字描述符。这个套接字必须是:面向连接的(如 SOCK_STREAM,即 TCP 套接字)。或者是一个已经通过 connect 函数连接到对等方的无连接套接字(如 SOCK_DGRAM,即 UDP 套接字)。对于普通的无连接 UDP 套接字,通常使用 recvfrom 来同时获取数据来源的地址。void *buf含义 :指向一个内存缓冲区的指针,用于存放接收到的数据。size_t len含义 :缓冲区 buf 的最大长度(以字节为单位)。recv 函数最多会向缓冲区写入 len 个字节的数据。int flags含义 :用于修改函数行为的标志位。可以单独使用,也可以通过按位或 | 组合使用。常见的标志有:0: 默认行为,阻塞等待,直到有数据到达。 MSG_PEEK: '窥探'数据。将数据从网络缓冲区复制到应用缓冲区 buf 中,但不从内核的网络接收缓冲区中移除这些数据 。下一次调用 recv(不带 MSG_PEEK)时,还会收到同样的数据。 MSG_WAITALL: 等待所有数据到达。函数会阻塞,直到接收到了 恰好 len 个字节的数据,或者连接被关闭/发生错误。注意 :即使设置了此标志,如果连接被对端正常关闭(收到 FIN),它也可能返回少于 len 字节的数据。 MSG_DONTWAIT**: 以非阻塞方式操作。如果没有数据立即可用,函数会立即返回失败,并设置错误码为 EAGAIN 或 EWOULDBLOCK。
返回值****成功 :返回实际接收到的字节数。返回值可能小于参数 len,这很常见。对于 TCP ,返回值 0 有特殊含义:表示对端已经关闭了连接 (发送了 FIN 包)。这是判断连接是否关闭的重要依据。对于 UDP ,返回值 0 是合法的,表示收到了一个长度为 0 的数据报。失败 :返回 -1,并设置全局变量 errno 以指示具体的错误。常见的错误有:EAGAIN / EWOULDBLOCK: 套接字被设置为非阻塞,并且没有数据可读。ECONNRESET: 连接被对端强制重置(如对端进程崩溃)。EINTR: 系统调用被信号中断。
bool WebServer::dealwithsignal (bool &timeout, bool &stop_server) {
int ret = 0 ;
int sig;
char signals[1024 ];
ret = recv (m_pipefd[0 ], signals, sizeof (signals), 0 );
if (ret == -1 ) {
return false ;
}
else if (ret == 0 ) {
return false ;
}
else {
for (int i = 0 ; i < ret; ++i) {
switch (signals[i]) {
case SIGALRM: {
timeout = true ;
break ;
}
case SIGTERM: {
stop_server = true ;
break ;
}
}
}
}
return true ;
}
void WebServer::dealwithread (int sockfd) {
util_timer *timer = users_timer[sockfd].timer;
if (1 == m_actormodel) {
if (timer)
{
adjust_timer (timer);
}
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 {
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) {
adjust_timer (timer);
}
}
else {
deal_timer (timer, sockfd);
}
}
}
void WebServer::dealwithwrite (int sockfd) {
util_timer *timer = users_timer[sockfd].timer;
if (1 == m_actormodel) {
if (timer) {
adjust_timer (timer);
}
m_pool->append (users + sockfd, 1 );
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 {
if (users[sockfd].write ())
{
LOG_INFO ("send data to the client(%s)" , inet_ntoa (users[sockfd].get_address ()->sin_addr));
if (timer) {
adjust_timer (timer);
}
}
else {
deal_timer (timer, sockfd);
}
}
}
函数原型
参数详解
epfd作用 :epoll 实例的文件描述符来源 :由 epoll_create() 或 epoll_create1() 创建
events作用 :输出参数,用于接收就绪事件的数组类型 :struct epoll_event*重要 :这是就绪事件的集合 ,不是所有监控的事件
maxevents作用 :指定 events 数组的大小要求 :必须大于 0建议 :通常设置为 events 数组的长度
timeout作用 :指定超时时间(毫秒)取值 :-1:阻塞等待,直到有事件发生 0:立即返回,即使没有事件(非阻塞轮询)>0:最多等待指定的毫秒数
返回值成功 :返回就绪的文件描述符数量超时 :返回 0(没有事件发生)错误 :返回 -1,并设置 errno
void WebServer::eventLoop () {
bool timeout = false ;
bool stop_server = false ;
while (!stop_server)
{
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;
if (sockfd == m_listenfd) {
bool flag = dealclientdata ();
if (false == flag) continue ;
}
else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
util_timer *timer = users_timer[sockfd].timer;
deal_timer (timer, sockfd);
}
else if ((sockfd == m_pipefd[0 ]) && (events[i].events & EPOLLIN)) {
bool flag = dealwithsignal (timeout, stop_server);
if (false == flag) LOG_ERROR ("%s" , "dealclientdata failure" );
}
else if (events[i].events & EPOLLIN) {
dealwithread (sockfd);
}
else if (events[i].events & EPOLLOUT) {
dealwithwrite (sockfd);
}
}
if (timeout) {
utils.timer_handler ();
LOG_INFO ("%s" , "timer tick" );
timeout = false ;
}
}
}
Timer
lst_timer.h
**函数指针:*是 C 语言中的一个高级特性,它允许将函数作为参数传递给其他函数,或者保存函数的地址以便后续调用。函数指针的定义格式通常为:类型名 ( 函数名) (函数参数列表);
void func1 (void ) {
printf ("test for function pointer.\n" );
}
void (*pFunc)(void );
pFunc = func1;
(*pFunc)();
函数指针的作用:
函数指针的主要作用是两个方面:一是作为回调函数,允许在运行时决定调用哪个函数;二是作为函数的参数,使得函数可以接受不同行为的函数作为参数,提高代码的复用性和灵活性。
#ifndef LST_TIMER
#define LST_TIMER
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <time.h>
#include "../log/log.h"
class util_timer ;
struct client_data {
sockaddr_in address;
int sockfd;
util_timer *timer;
};
class util_timer {
public :
util_timer () : prev (NULL ), next (NULL ) {}
public :
time_t expire;
void (*cb_func)(client_data*);
client_data* user_data;
util_timer* prev;
util_timer* next;
};
class sort_timer_lst {
public :
sort_timer_lst ();
~sort_timer_lst ();
void add_timer (util_timer* timer) ;
void adjust_timer (util_timer* timer) ;
void del_timer (util_timer* timer) ;
void tick () ;
private :
void add_timer (util_timer *timer, util_timer *lst_head) ;
util_timer *head;
util_timer *tail;
};
class Utils
{
public :
Utils () {}
~Utils () {}
void init (int timeslot) ;
int setnonblocking (int fd) ;
void addfd (int epollfd, int fd, bool one_shot, int TRIGMode) ;
static void sig_handler (int sig) ;
void addsig (int sig, void (handler)(int ), bool restart = true ) ;
void timer_handler () ;
void show_error (int connfd, const char *info) ;
public :
static int * u_pipefd;
sort_timer_lst m_timer_lst;
static int u_epollfd;
int m_TIMESLOT;
};
void cb_func (client_data *user_data) ;
#endif
lst_timer.cpp #include "lst_timer.h"
#include "../http/http_conn.h"
sort_timer_lst::sort_timer_lst () {
head = NULL ;
tail = NULL ;
}
sort_timer_lst::~sort_timer_lst () {
util_timer *tmp = head;
while (tmp)
{
head = tmp->next;
delete tmp;
tmp = head;
}
}
void sort_timer_lst::add_timer (util_timer *timer) {
if (!timer) {
return ;
}
if (!head)
{
head = tail = timer;
return ;
}
if (timer->expire < head->expire) {
timer->next = head;
head->prev = timer;
head = timer;
return ;
}
add_timer (timer, head);
}
void sort_timer_lst::adjust_timer (util_timer *timer)
{
if (!timer) {
return ;
}
util_timer *tmp = timer->next;
if (!tmp || (timer->expire < tmp->expire)) {
return ;
}
if (timer == head) {
head = head->next;
head->prev = NULL ;
timer->next = NULL ;
add_timer (timer, head);
}
else {
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
add_timer (timer, timer->next);
}
}
void sort_timer_lst::del_timer (util_timer *timer) {
if (!timer) {
return ;
}
if ((timer == head) && (timer == tail))
{
delete timer;
head = NULL ;
tail = NULL ;
return ;
}
if (timer == head) {
head = head->next;
head->prev = NULL ;
delete timer;
return ;
}
if (timer == tail) {
tail = tail->prev;
tail->next = NULL ;
delete timer;
return ;
}
timer->prev->next = timer->next;
timer->next->prev = timer->prev;
delete timer;
}
void sort_timer_lst::tick ()
{
if (!head) {
return ;
}
time_t cur = time (NULL );
util_timer *tmp = head;
while (tmp)
{
if (cur < tmp->expire)
{
break ;
}
tmp->cb_func (tmp->user_data);
head = tmp->next;
if (head) {
head->prev = NULL ;
}
delete tmp;
tmp = head;
}
}
void sort_timer_lst::add_timer (util_timer *timer, util_timer *lst_head)
{
util_timer *prev = lst_head;
util_timer *tmp = prev->next;
while (tmp)
{
if (timer->expire < tmp->expire)
{
prev->next = timer;
timer->next = tmp;
tmp->prev = timer;
timer->prev = prev;
break ;
}
prev = tmp;
tmp = tmp->next;
}
if (!tmp)
{
prev->next = timer;
timer->prev = prev;
timer->next = NULL ;
tail = timer;
}
}
void Utils::init (int timeslot) {
m_TIMESLOT = timeslot;
}
int Utils::setnonblocking (int fd) {
int old_option = fcntl (fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl (fd, F_SETFL, new_option);
return old_option;
}
void Utils::addfd (int epollfd, int fd, bool one_shot, int TRIGMode) {
epoll_event event;
event.data.fd = fd;
if (1 == TRIGMode)
{
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
}
else
{
event.events = EPOLLIN | EPOLLRDHUP;
}
if (one_shot)
{
event.events |= EPOLLONESHOT;
}
epoll_ctl (epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking (fd);
}
void Utils::sig_handler (int sig) {
int save_errno = errno;
int msg = sig;
send (u_pipefd[1 ], (char *)&msg, 1 , 0 );
errno = save_errno;
}
void Utils::addsig (int sig, void (handler)(int ), bool restart) {
struct sigaction sa;
memset (&sa, '\0' , sizeof (sa));
sa.sa_handler = handler;
if (restart)
{
sa.sa_flags |= SA_RESTART;
}
sigfillset (&sa.sa_mask);
assert (sigaction (sig, &sa, NULL ) != -1 );
}
void Utils::timer_handler () {
m_timer_lst.tick ();
alarm (m_TIMESLOT);
}
void Utils::show_error (int connfd, const char *info) {
send (connfd, info, strlen (info), 0 );
close (connfd);
}
int *Utils::u_pipefd = 0 ;
int Utils::u_epollfd = 0 ;
class Utils ;
void cb_func (client_data *user_data)
{
assert (user_data);
epoll_ctl (Utils::u_epollfd, EPOLL_CTL_DEL, user_data->sockfd, 0 );
close (user_data->sockfd);
http_conn::m_user_count--;
}
Threadpool #ifndef THREADPOOL_H
#define THREADPOOL_H
#include <list>
#include <cstdio>
#include <exception>
#include <pthread.h>
#include "../lock/locker.h"
#include "../CGImysql/sql_connection_pool.h"
template <typename T>
class threadpool
{
public :
threadpool (int actor_model, connection_pool *connPool, int thread_number = 8 , int max_request = 10000 );
~threadpool ();
bool append (T *request, int state) ;
bool append_p (T *request) ;
private :
static void *worker (void *arg) ;
void run () ;
private :
int m_thread_number;
pthread_t *m_threads;
std::list<T *> m_workqueue;
int m_max_requests;
locker m_queuelocker;
sem m_queuestat;
connection_pool *m_connPool;
int m_actor_model;
};
template <typename T>
threadpool<T>::threadpool (
int actor_model, connection_pool *connPool, int thread_number, int max_requests)
: m_actor_model (actor_model), m_thread_number (thread_number), m_max_requests (max_requests), m_threads (NULL ), m_connPool (connPool)
{
if (thread_number <= 0 || max_requests <= 0 )
{
throw std::exception ();
}
m_threads = new pthread_t [m_thread_number];
if (!m_threads)
{
throw std::exception ();
}
for (int i = 0 ; i < thread_number; ++i) {
if (pthread_create (m_threads + i, NULL , worker, this ) != 0 ) {
delete [] m_threads;
throw std::exception ();
}
if (pthread_detach (m_threads[i]))
{
delete [] m_threads;
throw std::exception ();
}
}
}
template <typename T>
threadpool<T>::~threadpool () {
delete [] m_threads;
}
template <typename T>
bool threadpool<T>::append (T *request, int state) {
m_queuelocker.lock ();
if (m_workqueue.size () >= m_max_requests) {
m_queuelocker.unlock ();
return false ;
}
request->m_state = state;
m_workqueue.push_back (request);
m_queuelocker.unlock ();
m_queuestat.post ();
return true ;
}
template <typename T>
bool threadpool<T>::append_p (T *request) {
m_queuelocker.lock ();
if (m_workqueue.size () >= m_max_requests) {
m_queuelocker.unlock ();
return false ;
}
m_workqueue.push_back (request);
m_queuelocker.unlock ();
m_queuestat.post ();
return true ;
}
template <typename T>
void *threadpool<T>::worker (void *arg) {
threadpool* pool = (threadpool*)arg;
pool->run ();
return pool;
}
template <typename T>
void threadpool<T>::run () {
while (true )
{
m_queuestat.wait ();
m_queuelocker.lock ();
if (m_workqueue.empty ()) {
m_queuelocker.unlock ();
continue ;
}
T* request = m_workqueue.front ();
m_workqueue.pop_front ();
m_queuelocker.unlock ();
if (!request)
continue ;
if (1 == m_actor_model)
{
if (0 == request->m_state)
{
if (request->read_once ())
{
request->improv = 1 ;
connectionRAII mysqlcon (&request->mysql, m_connPool) ;
request->process ();
}
else
{
request->improv = 1 ;
request->timer_flag = 1 ;
}
}
else
{
if (request->write ())
{
request->improv = 1 ;
}
else
{
request->improv = 1 ;
request->timer_flag = 1 ;
}
}
}
else
{
connectionRAII mysqlcon (&request->mysql, m_connPool);
request->process ();
}
}
}
#endif
CGImysql
sql_connection_pool.h #ifndef _CONNECTION_POOL_
#define _CONNECTION_POOL_
#include <stdio.h>
#include <list>
#include <mysql/mysql.h>
#include <error.h>
#include <string.h>
#include <iostream>
#include <string>
#include "../lock/locker.h"
#include "../log/log.h"
class connection_pool {
public :
MYSQL *GetConnection () ;
bool ReleaseConnection (MYSQL *conn) ;
int GetFreeConn () ;
void DestroyPool () ;
static connection_pool *GetInstance () ;
void init (string url, string User, string PassWord, string DataBaseName, int Port, int MaxConn, int close_log) ;
private :
connection_pool ();
~connection_pool ();
int m_MaxConn;
int m_CurConn;
int m_FreeConn;
locker lock;
list<MYSQL *> connList;
sem reserve;
public :
string m_url
string m_Port;
string m_User;
string m_PassWord;
string m_DatabaseName;
int m_close_log;
};
class connectionRAII {
public :
connectionRAII (MYSQL **con, connection_pool *connPool);
~connectionRAII ();
private :
MYSQL *conRAII;
connection_pool *poolRAII;
};
#endif
Sql_connection_pool.cpp #include <mysql/mysql.h>
#include <stdio.h>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <list>
#include <pthread.h>
#include <iostream>
#include "sql_connection_pool.h"
connection_pool::connection_pool () {
m_CurConn = 0 ;
m_FreeConn = 0 ;
}
connection_pool *connection_pool::GetInstance () {
static connection_pool connPool;
return &connPool;
}
void connection_pool::init (string url, string User, string PassWord, string DBName, int Port, int MaxConn, int close_log) {
m_url = url;
m_Port = Port;
m_User = User;
m_PassWord = PassWord;
m_DatabaseName = DBName;
m_close_log = close_log;
for (int i = 0 ; i < MaxConn; i++) {
MYSQL *con = NULL ;
con = mysql_init (con);
if (con == NULL )
{
LOG_ERROR ("MySQL Error" );
exit (1 );
}
con = mysql_real_connect (con, url.c_str (), User.c_str (), PassWord.c_str (), DBName.c_str (), Port, NULL , 0 );
if (con == NULL ) {
LOG_ERROR ("MySQL Error" );
exit (1 );
}
connList.push_back (con);
++m_FreeConn;
}
reserve = sem (m_FreeConn);
m_MaxConn = m_FreeConn;
}
MYSQL *connection_pool::GetConnection () {
MYSQL *con = NULL ;
if (0 == connList.size ())
return NULL ;
reserve.wait ();
lock.lock ();
con = connList.front ();
connList.pop_front ();
--m_FreeConn;
++m_CurConn;
lock.unlock ();
return con;
}
bool connection_pool::ReleaseConnection (MYSQL *con) {
if (NULL == con)
return false ;
lock.lock ();
connList.push_back (con);
++m_FreeConn;
--m_CurConn;
lock.unlock ();
reserve.post ();
return true ;
}
void connection_pool::DestroyPool () {
lock.lock ();
if (connList.size () > 0 ) {
list<MYSQL *>::iterator it;
for (it = connList.begin (); it != connList.end (); ++it) {
MYSQL *con = *it;
mysql_close (con);
}
m_CurConn = 0 ;
m_FreeConn = 0 ;
connList.clear ();
}
lock.unlock ();
}
int connection_pool::GetFreeConn () {
return this ->m_FreeConn;
}
connection_pool::~connection_pool () {
DestroyPool ();
}
connectionRAII::connectionRAII (MYSQL **SQL, connection_pool *connPool){
connection_pool *connPool:连接池对象指针
*SQL = connPool->GetConnection ();
conRAII = *SQL;
poolRAII = connPool;
}
connectionRAII::~connectionRAII (){
poolRAII->ReleaseConnection (conRAII);
}
Log
block_queue.h #ifndef BLOCK_QUEUE_H
#define BLOCK_QUEUE_H
#include <iostream>
#include <stdlib.h>
#include <pthread.h>
#include <sys/time.h>
#include "../lock/locker.h"
template <class T >
class block_queue {
private :
locker m_mutex;
cond m_cond;
T* m_array;
int m_size;
int m_max_size;
int m_front;
int m_back;
public :
block_queue (int max_size = 1000 ) {
if (max_size <= 0 )
{
exit (-1 );
}
m_max_size = max_size;
m_array = new T[max_size];
m_size = 0 ;
m_front = -1 ;
m_back = -1 ;
}
void clear () {
m_mutex.lock ();
m_size = 0 ;
m_front = -1 ;
m_back = -1 ;
m_mutex.unlock ();
}
~block_queue ()
{
m_mutex.lock ();
if (m_array != NULL )
delete [] m_array;
m_mutex.unlock ();
}
bool full () {
m_mutex.lock ();
if (m_size >= m_max_size) {
m_mutex.unlock ();
return true ;
}
m_mutex.unlock ();
return false ;
}
bool empty () {
m_mutex.lock ();
if (0 == m_size) {
m_mutex.unlock ();
return true ;
}
m_mutex.unlock ();
return false ;
}
bool front (T &value) {
m_mutex.lock ();
if (0 == m_size) {
m_mutex.unlock ();
return false ;
}
value = m_array[m_front];
m_mutex.unlock ();
return true ;
}
bool back (T &value) {
m_mutex.lock ();
if (0 == m_size) {
m_mutex.unlock ();
return false ;
}
value = m_array[m_back];
m_mutex.unlock ();
return true ;
}
int size () {
int tmp = 0 ;
m_mutex.lock ();
tmp = m_size;
m_mutex.unlock ();
return tmp;
}
int max_size () {
int tmp = 0 ;
m_mutex.lock ();
tmp = m_max_size;
m_mutex.unlock ();
return tmp;
}
bool push (const T &item) {
m_mutex.lock ();
if (m_size >= m_max_size)
{
m_cond.broadcast ();
m_mutex.unlock ();
return false ;
}
m_back = (m_back + 1 ) % m_max_size;
m_array[m_back] = item;
m_size++;
m_cond.broadcast ();
m_mutex.unlock ();
return true ;
}
bool pop (T &item)
{
m_mutex.lock ();
while (m_size <= 0 )
{
if (!m_cond.wait (m_mutex.get ()))
{
m_mutex.unlock ();
return false ;
}
}
m_front = (m_front + 1 ) % m_max_size;
item = m_array[m_front];
m_size--;
m_mutex.unlock ();
return true ;
}
bool pop (T &item, int ms_timeout)
int ms_timeout:超时时间(毫秒)
{
struct timespec t = {0 , 0 };
struct timeval now = {0 , 0 };
gettimeofday (&now, NULL );
m_mutex.lock ();
if (m_size <= 0 )
{
t.tv_sec = now.tv_sec + ms_timeout / 1000 ;
t.tv_nsec = (ms_timeout % 1000 ) * 1000 ;
if (!m_cond.timewait (m_mutex.get (), t))
{
m_mutex.unlock ();
return false ;
}
}
if (m_size <= 0 )
{
m_mutex.unlock ();
return false ;
}
m_front = (m_front + 1 ) % m_max_size;
item = m_array[m_front];
m_size--;
m_mutex.unlock ();
return true ;
}
};
#endif
log.h #ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <iostream>
#include <string>
#include <stdarg.h>
#include <pthread.h>
#include "block_queue.h"
class Log {
public :
static Log *get_instance ()
{
static Log instance;
return &instance;
}
static void *flush_log_thread (void *args)
{
Log::get_instance ()->async_write_log ();
}
bool init (const char *file_name, int close_log, int log_buf_size = 8192 , int split_lines = 5000000 , int max_queue_size = 0 ) ;
void write_log (int level, const char *format, ...) ;
void flush (void ) ;
private :
Log ();
virtual ~Log ();
void *async_write_log () {
string single_log;
while (m_log_queue->pop (single_log))
{
m_mutex.lock ();
fputs (single_log.c_str (), m_fp);
m_mutex.unlock ();
}
}
private :
char dir_name[128 ];
char log_name[128 ];
int m_split_lines;
int m_log_buf_size;
long long m_count;
int m_today;
locker m_mutex;
FILE *m_fp;
char *m_buf;
block_queue<string> *m_log_queue;
bool m_is_async;
int m_close_log;
};
#define LOG_DEBUG(format, ...) if (0 == m_close_log) {Log::get_instance()->write_log(0, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_INFO(format, ...) if (0 == m_close_log) {Log::get_instance()->write_log(1, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_WARN(format, ...) if (0 == m_close_log) {Log::get_instance()->write_log(2, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#define LOG_ERROR(format, ...) if (0 == m_close_log) {Log::get_instance()->write_log(3, format, ##__VA_ARGS__); Log::get_instance()->flush();}
#endif
log.cpp
参数说明 **thread** (输出参数)类型 : pthread_t *作用 : 指向 pthread_t 类型变量的指针。函数成功返回后,该变量会被填充为新创建的线程标识符(Thread ID) 。这个 ID 在后面其他线程函数(如 pthread_join, pthread_cancel)中用于引用这个特定的线程。attr (输入参数)类型 : pthread_attr_t *作用 : 用于设置新线程的属性 ,如栈大小、调度策略、分离状态等。特殊值 :如果传入 NULL,线程将使用默认属性 创建。在大多数情况下,我们使用默认属性就足够了。如果需要自定义属性,必须先使用 pthread_attr_init 初始化一个属性对象,设置好所需属性后传入。start_routine (输入参数)类型 : void *(*)(void *)作用 : 这是一个函数指针 ,指向新线程将要执行的函数 。这个函数必须具有特定的签名:返回类型是 void * 接收一个 void * 类型的参数**arg** (输入参数)类型 : void *作用 : 传递给 start_routine 函数的参数 。它是一个泛型指针,你可以传递任何数据的地址(如一个变量的地址、一个结构体的地址等)。如果不需要传递参数,可以传入 NULL。
返回值****成功 : 返回 0。失败 : 返回一个错误代码 (非零值),而不是设置 errno 。常见的错误代码有:EAGAIN: 系统资源不足,无法创建另一个线程,或线程数量超过了系统的限制。EINVAL: 参数 attr 无效。EPERM: 没有权限设置调度策略和参数。
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <stdarg.h>
#include "log.h"
#include <pthread.h>
Log::Log ()
{
m_count = 0 ;
m_is_async = false ;
}
Log::~Log () {
if (m_fp != NULL ) {
fclose (m_fp);
}
}
bool Log::init (const char *file_name, int close_log, int log_buf_size, int split_lines, int max_queue_size) {
if (max_queue_size >= 1 ) {
m_is_async = true ;
m_log_queue = new block_queue <string>(max_queue_size);
pthread_t tid;
pthread_create (&tid, NULL , flush_log_thread, NULL );
}
m_close_log = close_log;
m_log_buf_size = log_buf_size;
m_buf = new char [m_log_buf_size];
memset (m_buf, '\0' , m_log_buf_size);
m_split_lines = split_lines;
time_t t = time (NULL );
struct tm *sys_tm = localtime (&t);
struct tm my_tm = *sys_tm;
char log_full_name[256 ] = {0 };
const char *p = strrchr (file_name, '/' );
if (p == NULL )
{
snprintf (log_full_name, 255 , "%d_%02d_%02d_%s" , my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday, file_name);
}
else
{
strcpy (log_name, p + 1 );
strncpy (dir_name, file_name, p - file_name + 1 );
snprintf (log_full_name, 255 , "%s%d_%02d_%02d_%s" , dir_name, my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday, log_name);
}
m_today = my_tm.tm_mday;
m_fp = fopen (log_full_name, "a" );
if (m_fp == NULL )
{
return false ;
}
return true ;
}
void Log::write_log (int level, const char *format, ...) {
struct timeval now = {0 , 0 };
timeval (time_t 、suseconds_t )
gettimeofday (&now, NULL );
time_t t = now.tv_sec;
struct tm *sys_tm = localtime (&t);
struct tm my_tm = *sys_tm;
char s[16 ] = {0 };
switch (level) {
case 0 : strcpy (s, "[debug]:" );
break ;
case 1 : strcpy (s, "[info]:" );
break ;
case 2 : strcpy (s, "[warn]:" );
break ;
case 3 : strcpy (s, "[erro]:" );
break ;
default : strcpy (s, "[info]:" );
break ;
}
m_mutex.lock ();
m_count++;
if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0 ) {
char new_log[256 ] = {0 };
fflush (m_fp);
fclose (m_fp);
char tail[16 ] = {0 };
snprintf (tail, 16 , "%d_%02d_%02d_" , my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday);
if (m_today != my_tm.tm_mday)
{
snprintf (new_log, 255 , "%s%s%s" , dir_name, tail, log_name);
m_today = my_tm.tm_mday;
m_count = 0 ;
}
else
{
snprintf (new_log, 255 , "%s%s%s.%lld" , dir_name, tail, log_name, m_count / m_split_lines);
}
m_fp = fopen (new_log, "a" );
}
m_mutex.unlock ();
va_list valst;
va_start (valst, format);
string log_str;
m_mutex.lock ();
int n = snprintf (m_buf, 48 , "%d-%02d-%02d %02d:%02d:%02d.%06ld %s " , my_tm.tm_year + 1900 , my_tm.tm_mon + 1 , my_tm.tm_mday, my_tm.tm_hour, my_tm.tm_min, my_tm.tm_sec, now.tv_usec, s);
int m = vsnprintf (m_buf + n, m_log_buf_size - n - 1 , format, valst);
m_buf[n + m] = '\n' ;
m_buf[n + m + 1 ] = '\0' ;
log_str = m_buf;
m_mutex.unlock ();
if (m_is_async && !m_log_queue->full ())
{
m_log_queue->push (log_str);
}
else
{
m_mutex.lock ();
fputs (log_str.c_str (), m_fp);
m_mutex.unlock ();
}
va_end (valst);
}
void Log::flush (void ) {
m_mutex.lock ();
fflush (m_fp);
m_mutex.unlock ();
}
Lock #ifndef LOCKER_H
#define LOCKER_H
#include <exception>
#include <pthread.h>
#include <semaphore.h>
class sem {
public :
sem () {
if (sem_init (&m_sem, 0 , 0 ) != 0 ) {
throw std::exception ();
}
}
sem (int num) {
if (sem_init (&m_sem, 0 , num) != 0 )
{
throw std::exception ();
}
}
~sem () {
sem_destroy (&m_sem);
}
bool wait ()
{
return sem_wait (&m_sem) == 0 ;
}
bool post () {
return sem_post (&m_sem) == 0 ;
}
private :
sem_t m_sem;
};
class locker {
public :
locker () {
if (pthread_mutex_init (&m_mutex, NULL ) != 0 ) {
throw std::exception ();
}
}
~locker () {
pthread_mutex_destroy (&m_mutex);
}
bool lock () {
return pthread_mutex_lock (&m_mutex) == 0 ;
}
bool unlock () {
return pthread_mutex_unlock (&m_mutex) == 0 ;
}
pthread_mutex_t *get () {
return &m_mutex;
}
private :
pthread_mutex_t m_mutex;
};
class cond {
public :
cond () {
if (pthread_cond_init (&m_cond, NULL ) != 0 ) {
throw std::exception ();
}
}
~cond () {
pthread_cond_destroy (&m_cond);
}
bool wait (pthread_mutex_t *m_mutex)
{
int ret = 0 ;
ret = pthread_cond_wait (&m_cond, m_mutex);
return ret == 0 ;
}
bool timewait (pthread_mutex_t *m_mutex, struct timespec t) {
int ret = 0 ;
ret = pthread_cond_timedwait (&m_cond, m_mutex, &t);
return ret == 0 ;
}
bool signal () {
return pthread_cond_signal (&m_cond) == 0 ;
}
bool broadcast () {
return pthread_cond_broadcast (&m_cond) == 0 ;
}
private :
pthread_cond_t m_cond;
};
#endif
Http
http_conn.h #ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <sys/stat.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <map>
#include "../lock/locker.h"
#include "../CGImysql/sql_connection_pool.h"
#include "../timer/lst_timer.h"
#include "../log/log.h"
class http_conn
{
public :
static const int FILENAME_LEN = 200 ;
static const int READ_BUFFER_SIZE = 2048 ;
static const int WRITE_BUFFER_SIZE = 1024 ;
enum METHOD {
GET = 0 ,
POST,
HEAD,
PUT,
DELETE,
TRACE,
OPTIONS,
CONNECT,
PATH
};
enum CHECK_STATE {
CHECK_STATE_REQUESTLINE = 0 ,
CHECK_STATE_HEADER,
CHECK_STATE_CONTENT
};
enum HTTP_CODE {
NO_REQUEST,
GET_REQUEST,
BAD_REQUEST,
NO_RESOURCE,
FORBIDDEN_REQUEST,
FILE_REQUEST,
INTERNAL_ERROR,
CLOSED_CONNECTION
};
enum LINE_STATUS {
LINE_OK = 0 ,
LINE_BAD,
LINE_OPEN
};
public :
http_conn () {}
~http_conn () {}
public :
void init (int sockfd, const sockaddr_in& addr, char *, int , int , string user, string passwd, string sqlname) ;
void close_conn (bool real_close = true ) ;
void process () ;
bool read_once () ;
bool write () ;
sockaddr_in *get_address () {
return &m_address;
}
void initmysql_result (connection_pool *connPool) ;
int timer_flag;
int improv;
private :
void init () ;
HTTP_CODE process_read () ;
bool process_write (HTTP_CODE ret) ;
HTTP_CODE parse_request_line (char *text) ;
HTTP_CODE parse_headers (char *text) ;
HTTP_CODE parse_content (char *text) ;
HTTP_CODE do_request () ;
char *get_line () {
return m_read_buf + m_start_line;
};
LINE_STATUS parse_line () ;
void unmap () ;
bool add_response (const char *format, ...) ;
bool add_status_line (int status, const char *title) ;
bool add_headers (int content_length) ;
bool add_content (const char *content) ;
bool add_content_type () ;
bool add_content_length (int content_length) ;
bool add_linger () ;
bool add_blank_line () ;
public :
static int m_epollfd;
static int m_user_count;
MYSQL* mysql;
int m_state;
private :
int m_sockfd;
sockaddr_in m_address;
char m_read_buf[READ_BUFFER_SIZE];
long m_read_idx;
long m_checked_idx;
int m_start_line;
char m_write_buf[WRITE_BUFFER_SIZE];
int m_write_idx;
CHECK_STATE m_check_state;
METHOD m_method;
char *m_url;
char *m_version;
char *m_host;
long m_content_length;
bool m_linger;
char *m_string;
int cgi;
char m_real_file[FILENAME_LEN];
char *m_file_address;
struct stat m_file_stat;
struct iovec m_iv[2 ];
int m_iv_count;
int bytes_to_send;
int bytes_have_send;
char * doc_root;
int m_TRIGMode;
int m_close_log;
map<string, string> m_users;
char sql_user[100 ];
char sql_passwd[100 ];
char sql_name[100 ];
};
#endif
http_conn.cpp
**strpbrk(char *str1, char *str2)**在 str1 中查找 str2 中任意字符的第一次出现示例:strpbrk("GET /", " \t") 返回指向空格的指针
**strspn(char *str1, char *str2)**返回 str1 开头连续包含 str2 中字符的个数示例:strspn(" /index", " \t") 返回 3(3 个空格)
**strcasecmp / strncasecmp**不区分大小写的字符串比较 strncasecmp 只比较前 n 个字符
#include "http_conn.h"
#include <mysql/mysql.h>
#include <fstream>
const char *ok_200_title = "OK" ;
const char *error_400_title = "Bad Request" ;
const char *error_400_form = "Your request has bad syntax or is inherently impossible to staisfy.\n" ;
const char *error_403_title = "Forbidden" ;
const char *error_403_form = "You do not have permission to get file form this server.\n" ;
const char *error_404_title = "Not Found" ;
const char *error_404_form = "The requested file was not found on this server.\n" ;
const char *error_500_title = "Internal Error" ;
const char *error_500_form = "There was an unusual problem serving the request file.\n" ;
locker m_lock;
map<string, string> users;
void http_conn::initmysql_result (connection_pool *connPool) {
MYSQL *mysql = NULL ;
connectionRAII mysqlcon (&mysql, connPool) ;
if (mysql_query (mysql, "SELECT username,passwd FROM user" ))
{
LOG_ERROR ("SELECT error:%s\n" , mysql_error (mysql));
}
MYSQL_RES *result = mysql_store_result (mysql);
int num_fields = mysql_num_fields (result);
MYSQL_FIELD *fields = mysql_fetch_fields (result);
while (MYSQL_ROW row = mysql_fetch_row (result)) {
string temp1 (row[0 ]) ;
string temp2 (row[1 ]) ;
users[temp1] = temp2;
}
}
int setnonblocking (int fd) {
int old_option = fcntl (fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl (fd, F_SETFL, new_option);
return old_option;
}
void addfd (int epollfd, int fd, bool one_shot, int TRIGMode) {
epoll_event event;
event.data.fd = fd;
if (1 == TRIGMode)
{
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
}
else
{
event.events = EPOLLIN | EPOLLRDHUP;
}
if (one_shot)
{
event.events |= EPOLLONESHOT;
}
epoll_ctl (epollfd, EPOLL_CTL_ADD, fd, &event);
setnonblocking (fd);
}
void removefd (int epollfd, int fd) {
epoll_ctl (epollfd, EPOLL_CTL_DEL, fd, 0 );
close (fd);
}
void modfd (int epollfd, int fd, int ev, int TRIGMode) {
epoll_event event;
event.data.fd = fd;
if (1 == TRIGMode)
{
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
}
else
{
event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
}
epoll_ctl (epollfd, EPOLL_CTL_MOD, fd, &event);
}
int http_conn::m_user_count = 0 ;
int http_conn::m_epollfd = -1 ;
void http_conn::close_conn (bool real_close) {
if (real_close && (m_sockfd != -1 ))
{
printf ("close %d\n" , m_sockfd);
removefd (m_epollfd, m_sockfd);
m_sockfd = -1 ;
m_user_count--;
}
}
void http_conn::init (int sockfd, const sockaddr_in &addr, char *root, int TRIGMode, int close_log, string user, string passwd, string sqlname) {
m_sockfd = sockfd;
m_address = addr;
addfd (m_epollfd, sockfd, true , m_TRIGMode);
m_user_count++;
doc_root = root;
m_TRIGMode = TRIGMode;
m_close_log = close_log;
strcpy (sql_user, user.c_str ());
strcpy (sql_passwd, passwd.c_str ());
strcpy (sql_name, sqlname.c_str ());
init ();
}
void http_conn::init () {
mysql = NULL ;
m_check_state = CHECK_STATE_REQUESTLINE;
m_start_line = 0 ;
m_checked_idx = 0 ;
m_read_idx = 0 ;
bytes_to_send = 0 ;
bytes_have_send = 0 ;
m_write_idx = 0 ;
m_linger = false ;
m_method = GET;
m_url = 0 ;
m_version = 0 ;
m_host = 0 ;
m_content_length = 0 ;
cgi = 0 ;
timer_flag = 0 ;
improv = 0 ;
memset (m_read_buf, '\0' , READ_BUFFER_SIZE);
memset (m_write_buf, '\0' , WRITE_BUFFER_SIZE);
memset (m_real_file, '\0' , FILENAME_LEN);
}
http_conn::LINE_STATUS http_conn::parse_line () {
char temp;
for (; m_checked_idx < m_read_idx; ++m_checked_idx) {
temp = m_read_buf[m_checked_idx];
if (temp == '\r' ) {
if ((m_checked_idx + 1 ) == m_read_idx)
return LINE_OPEN;
else if (m_read_buf[m_checked_idx + 1 ] == '\n' ) {
m_read_buf[m_checked_idx++] = '\0' ;
m_read_buf[m_checked_idx++] = '\0' ;
return LINE_OK;
}
return LINE_BAD;
}
else if (temp == '\n' ) {
if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1 ] == '\r' ) {
m_read_buf[m_checked_idx - 1 ] = '\0' ;
m_read_buf[m_checked_idx++] = '\0' ;
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
bool http_conn::read_once () {
if (m_read_idx >= READ_BUFFER_SIZE)
{
return false ;
}
int bytes_read = 0 ;
if (0 == m_TRIGMode) {
bytes_read = recv (m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );
m_read_idx += bytes_read;
if (bytes_read <= 0 )
{
return false ;
}
return true ;
}
else {
while (true )
{
bytes_read = recv (m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );
if (bytes_read == -1 )
{
if (errno == EAGAIN || errno == EWOULDBLOCK)
break ;
return false ;
}
else if (bytes_read == 0 )
{
return false ;
}
m_read_idx += bytes_read;
}
return true ;
}
}
http_conn::HTTP_CODE http_conn::parse_request_line (char *text) {
m_url = strpbrk (text, " \t" );
if (!m_url) {
return BAD_REQUEST;
}
*m_url++ = '\0' ;
char *method = text;
if (strcasecmp (method, "GET" ) == 0 )
m_method = GET;
else if (strcasecmp (method, "POST" ) == 0 )
{
m_method = POST;
cgi = 1 ;
}
else
return BAD_REQUEST;
m_url += strspn (m_url, " \t" );
m_version = strpbrk (m_url, " \t" );
if (!m_version)
return BAD_REQUEST;
*m_version++ = '\0' ;
m_version += strspn (m_version, " \t" );
if (strcasecmp (m_version, "HTTP/1.1" ) != 0 )
return BAD_REQUEST;
if (strncasecmp (m_url, "http://" , 7 ) == 0 )
{
m_url += 7 ;
m_url = strchr (m_url, '/' );
}
if (strncasecmp (m_url, "https://" , 8 ) == 0 ) {
m_url += 8 ;
m_url = strchr (m_url, '/' );
}
if (!m_url || m_url[0 ] != '/' )
return BAD_REQUEST;
if (strlen (m_url) == 1 )
strcat (m_url, "judge.html" );
m_check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::parse_headers (char *text) {
if (text[0 ] == '\0' )
{
if (m_content_length != 0 )
{
m_check_state = CHECK_STATE_CONTENT
return NO_REQUEST;
}
return GET_REQUEST;
}
else if (strncasecmp (text, "Connection:" , 11 ) == 0 )
{
text += 11 ;
text += strspn (text, " \t" );
if (strcasecmp (text, "keep-alive" ) == 0 ) {
m_linger = true ;
}
}
else if (strncasecmp (text, "Content-length:" , 15 ) == 0 )
{
text += 15 ;
text += strspn (text, " \t" );
m_content_length = atol (text);
}
else if (strncasecmp (text, "Host:" , 5 ) == 0 )
{
text += 5 ;
text += strspn (text, " \t" );
m_host = text;
}
else {
LOG_INFO ("oop!unknow header: %s" , text);
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::parse_content (char *text) {
if (m_read_idx >= (m_content_length + m_checked_idx))
{
text[m_content_length] = '\0' ;
m_string = text;
return GET_REQUEST;
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::process_read () {
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char *text = 0 ;
while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line ()) == LINE_OK))
{
text = get_line ();
m_start_line = m_checked_idx;
LOG_INFO ("%s" , text);
switch (m_check_state) {
case CHECK_STATE_REQUESTLINE:
{
ret = parse_request_line (text);
if (ret == BAD_REQUEST)
return BAD_REQUEST;
break ;
}
case CHECK_STATE_HEADER:
{
ret = parse_headers (text);
if (ret == BAD_REQUEST)
return BAD_REQUEST;
else if (ret == GET_REQUEST)
{
return do_request ();
}
break ;
}
case CHECK_STATE_CONTENT:
{
ret = parse_content (text);
if (ret == GET_REQUEST)
return do_request ();
line_status = LINE_OPEN;
break ;
}
default :
return INTERNAL_ERROR;
}
}
return NO_REQUEST;
}
http_conn::HTTP_CODE http_conn::do_request () {
strcpy (m_real_file, doc_root);
int len = strlen (doc_root);
const char *p = strrchr (m_url, '/' );
if (cgi == 1 && (*(p + 1 ) == '2' || *(p + 1 ) == '3' ))
{
char flag = m_url[1 ];
char *m_url_real = (char *)malloc (sizeof (char ) * 200 );
strcpy (m_url_real, "/" );
strcat (m_url_real, m_url + 2 );
strncpy (m_real_file + len, m_url_real, FILENAME_LEN - len - 1 );
free (m_url_real);
char name[100 ], password[100 ];
int i;
for (i = 5 ; m_string[i] != '&' ; ++i)
name[i - 5 ] = m_string[i];
name[i - 5 ] = '\0' ;
int j = 0 ;
for (i = i + 10 ; m_string[i] != '\0' ; ++i, ++j)
password[j] = m_string[i];
password[j] = '\0' ;
if (*(p + 1 ) == '3' ) {
char *sql_insert = (char *)malloc (sizeof (char ) * 200 );
strcpy (sql_insert, "INSERT INTO user(username, passwd) VALUES(" );
strcat (sql_insert, "'" );
strcat (sql_insert, name);
strcat (sql_insert, "', '" );
strcat (sql_insert, password);
strcat (sql_insert, ")'" );
if (users.find (name) == users.end ())
{
m_lock.lock ();
int res = mysql_query (mysql, sql_insert);
users.insert (pair <string, string>(name, password));/更新缓存
m_lock.unlock ();
if (!res)
strcpy (m_url, "/log.html" );
else
strcpy (m_url, "/registerError.html" );
}
else
strcpy (m_url, "/registerError.html" );
}
else if (*(p + 1 ) == '2' ) {
if (users.find (name) != users.end () && users[name] == password)
strcpy (m_url, "/welcome.html" );
else
strcpy (m_url, "/logError.html" );
}
}
if (*(p + 1 ) == '0' ) {
char *m_url_real = (char *)malloc (sizeof (char ) * 200 );
strcpy (m_url_real, "/register.html" );
strncpy (m_real_file + len, m_url_real, strlen (m_url_real));
free (m_url_real);
}
else if (*(p + 1 ) == '1' ) {
char *m_url_real = (char *)malloc (sizeof (char ) * 200 );
strcpy (m_url_real, "/log.html" );
strncpy (m_real_file + len, m_url_real, strlen (m_url_real));
free (m_url_real);
}
else if (*(p + 1 ) == '5' ) {
char *m_url_real = (char *)malloc (sizeof (char ) * 200 );
strcpy (m_url_real, "/picture.html" );
strncpy (m_real_file + len, m_url_real, strlen (m_url_real));
free (m_url_real);
}
else if (*(p + 1 ) == '6' ) {
char *m_url_real = (char *)malloc (sizeof (char ) * 200 );
strcpy (m_url_real, "/video.html" );
strncpy (m_real_file + len, m_url_real, strlen (m_url_real));
free (m_url_real);
}
else if (*(p + 1 ) == '7' ) {
char *m_url_real = (char *)malloc (sizeof (char ) * 200 );
strcpy (m_url_real, "/fans.html" );
strncpy (m_real_file + len, m_url_real, strlen (m_url_real));
free (m_url_real);
}
else
strncpy (m_real_file + len, m_url, FILENAME_LEN - len - 1 );
if (stat (m_real_file, &m_file_stat) < 0 )
return NO_RESOURCE;
if (!(m_file_stat.st_mode & S_IROTH))
return FORBIDDEN_REQUEST;
if (S_ISDIR (m_file_stat.st_mode))
return BAD_REQUEST;
int fd = open (m_real_file, O_RDONLY);
m_file_address = (char *)mmap (0 , m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
close (fd);
return FILE_REQUEST;
}
void http_conn::unmap () {
if (m_file_address) {
munmap (m_file_address, m_file_stat.st_size);
m_file_address = 0 ;
}
}
bool http_conn::write () {
int temp = 0 ;
if (bytes_to_send == 0 ) {
modfd (m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
init ();
return true ;
}
while (1 ) {
temp = writev (m_sockfd, m_iv, m_iv_count);
if (temp < 0 ) {
if (errno == EAGAIN)
{
modfd (m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
return true ;
}
unmap ();
return false ;
}
bytes_have_send += temp;
bytes_to_send -= temp;
if (bytes_have_send >= m_iv[0 ].iov_len) {
m_iv[0 ].iov_len = 0 ;
m_iv[1 ].iov_base = m_file_address + (bytes_have_send - m_write_idx);
m_iv[1 ].iov_len = bytes_to_send;
}
else {
m_iv[0 ].iov_base = m_write_buf + bytes_have_send;
m_iv[0 ].iov_len = m_iv[0 ].iov_len - bytes_have_send;
}
if (bytes_to_send <= 0 ) {
unmap ();
modfd (m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
if (m_linger) {
init ();
return true ;
}
else {
return false ;
}
}
}
}
bool http_conn::add_response (const char *format, ...) {
if (m_write_idx >= WRITE_BUFFER_SIZE)
return false ;
va_list arg_list;
va_start (arg_list, format);
int len = vsnprintf (m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);
if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)) {
va_end (arg_list);
return false ;
}
m_write_idx += len;
va_end (arg_list);
LOG_INFO ("request:%s" , m_write_buf);
return true ;
}
bool http_conn::add_status_line (int status, const char *title) {
return add_response ("%s %d %s\r\n" , "HTTP/1.1" , status, title);
}
bool http_conn::add_headers (int content_len) {
return add_content_length (content_len) && add_linger () && add_blank_line ();
}
bool http_conn::add_content_length (int content_len) {
return add_response ("Content-Length:%d\r\n" , content_len);
}
bool http_conn::add_content_type () {
return add_response ("Content-Type:%s\r\n" , "text/html" );
}
bool http_conn::add_linger () {
return add_response ("Connection:%s\r\n" , (m_linger == true ) ? "keep-alive" : "close" );
}
bool http_conn::add_blank_line () {
return add_response ("%s" , "\r\n" );
}
bool http_conn::add_content (const char *content) {
return add_response ("%s" , content);
}
bool http_conn::process_write (HTTP_CODE ret) {
switch (ret) {
case INTERNAL_ERROR:
{
add_status_line (500 , error_500_title);
add_headers (strlen (error_500_form));
if (!add_content (error_500_form))
return false ;
break ;
}
case BAD_REQUEST:
{
add_status_line (404 , error_404_title);
add_headers (strlen (error_404_form));
if (!add_content (error_404_form))
return false ;
break ;
}
case FORBIDDEN_REQUEST:
{
add_status_line (403 , error_403_title);
add_headers (strlen (error_403_form));
if (!add_content (error_403_form))
return false ;
break ;
}
case FILE_REQUEST:
{
add_status_line (200 , ok_200_title);
if (m_file_stat.st_size != 0 ) {
add_headers (m_file_stat.st_size);
m_iv[0 ].iov_base = m_write_buf;
m_iv[0 ].iov_len = m_write_idx;
m_iv[1 ].iov_base = m_file_address;
m_iv[1 ].iov_len = m_file_stat.st_size;
m_iv_count = 2 ;
bytes_to_send = m_write_idx + m_file_stat.st_size;
return true ;
}
else {
const char *ok_string = "<html><body></body></html>" ;
add_headers (strlen (ok_string));
if (!add_content (ok_string))
return false ;
}
}
default :
return false ;
}
m_iv[0 ].iov_base = m_write_buf;
m_iv[0 ].iov_len = m_write_idx;
m_iv_count = 1 ;
bytes_to_send = m_write_idx;
return true ;
}
void http_conn::process () {
HTTP_CODE read_ret = process_read ();
if (read_ret == NO_REQUEST) {
modfd (m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
return ;
}
bool write_ret = process_write (read_ret);
if (!write_ret) {
close_conn ();
}
modfd (m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
}
Config
config.h 和 config.cpp 主要是对一些参数赋初值以及解析参数,以此就不过多讲解
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Gemini 图片去水印 基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online