一 前言
在项目或产品开发中,需要用C++实现一个高并发且易扩展的HTTP Server,那么我们可以基于libevent来做这件事情。Libevent提供了HTTP Server等组件,并且支持多线程编程。下面我们一起来看一下实现过程。
二?初版代码
如何用libevent实现一个自己的HTTP Server呢?网上有很多的文章和资料,可供参考。最简单的一种方式就是在main函数中直接调用其接口,实现服务端程序,这种方法的优点是简便易上手。缺点是HTTP服务请求处理过程,直接在程序主线程中,会卡住主线程,所以在主线程中没法进行其它业务操作。
下边启动一个HTTP Server工作线程,在后台处理HTTP请求,也就解决了上边卡主线程的问题。Libevent使用从官网下载的最新版libevent-2.1.12-stable,开发环境:Win10系统,VS2022。我们设计了一个CMyHTTPServer类,封装了libevent的相关接口,类的属性、方法及其它声明定义请参见下边代码,看代码之前,先了解下server主要流程。
代码流程
1 创建HTTP服务后台工作线程
使用std::thread
2 创建event base对象
EVENT2_EXPORT_SYMBOL
struct event_base *event_base_new(void);
3 创建http server
EVENT2_EXPORT_SYMBOL
struct evhttp *evhttp_new(struct event_base *base);
4 设置http请求回调函数
EVENT2_EXPORT_SYMBOL
void evhttp_set_gencb(struct evhttp *http, void (*cb)(struct evhttp_request *, void *), void *arg);
5 绑定、监听IP和端口
EVENT2_EXPORT_SYMBOL
struct evhttp_bound_socket *evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port);
6 进入事件循环
EVENT2_EXPORT_SYMBOL
int event_base_dispatch(struct event_base *);
7 在回调函数中,处理客户端各种HTTP请求
源代码
代码中有详细的注释,就不多说了,请看代码。
MyDefine.h
#pragma once
#define HTTP_SERVER_LISTEN_IP "0.0.0.0" //http服务监听地址
#define HTTP_SERVER_LISTEN_PORT 8080 //http服务监听端口
#define HTTP_CLIENT_LOGIN "/client?Action=Login" //系统登录URI
#define HTTP_CLIENT_LOGOUT "/client?Action=Logout" //系统登出URI
#define HTTP_CLIENT_HEARBEAT "/client?Action=Heartbeat" //心跳URI
/*
* 系统各种业务请求URL宏定义,格式与登录、登出、心跳类似
*/
MyHeader.h
#pragma once
#include<stdio.h>
#include<mutex>
#include<thread>
#include<string>
#include<map>
#include<vector>
#include<iostream>
#include"event2/bufferevent.h"
#include"event2/buffer.h"
#include"event2/listener.h"
#include"event2/util.h"
#include"event2/event_compat.h"
#include"event2/event.h"
#include"event2/keyvalq_struct.h"
#include"event2/http.h"
#include"event2/http_struct.h"
#include"event2/http_compat.h"
using std::mutex;
using std::thread;
using std::string;
using std::map;
using std::vector;
CMyHttpServer.h
#pragma once
#include"MyHeader.h"
/*****************************************************************************
**FileName: MyHeader.h
**Function: http服务器启动/停止,接收客户端http请求及处理
**Version record:
**Version Author Data Description
**v1.0.0 chexlong 2022.09 初稿
*****************************************************************************/
class CMyHttpServer
{
public:
CMyHttpServer(const int& listenPort);
~CMyHttpServer();
//启动http服务
int Start();
//停止http服务
int Stop();
private:
//处理文件请求
void OnRequestFile(struct evhttp_request* pstReq);
//处理数据请求
void OnRequestData(struct evhttp_request* pstReq);
//处理系统各种业务的GET请求
void RequestProcessGet(struct evhttp_request* pstReq);
//处理系统各种业务的POST请求
void RequestProcessPost(struct evhttp_request* pstReq);
//http请求回调函数
static void HttpReqCallback(struct evhttp_request* pstReq, void* userData);
//http工作线程函数
void WorkThread();
//发送http请求应答消息
int SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb);
private:
//event base
event_base* m_base;
//http server
evhttp* m_http;
//绑定监听socket句柄
evhttp_bound_socket* m_handle;
//http服务线程
std::thread m_thread;
//http监听端口
int m_listenPort;
};
CMyHttpServer.cpp
#include"CMyHttpServer.h"
#include"MyDefine.h"
CMyHttpServer::CMyHttpServer(const int& listenPort)
{
m_base = nullptr;
m_http = nullptr;
m_handle = nullptr;
m_listenPort = listenPort;
}
CMyHttpServer::~CMyHttpServer()
{
Stop();
}
int CMyHttpServer::SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb)
{
if (nullptr == pstReq)
{
if (evb)
{
evbuffer_free(evb);
}
return -1;
}
//返回HTTP头部
evhttp_add_header(pstReq->output_headers, "Server", "MyHttpServer");
evhttp_add_header(pstReq->output_headers, "Content-Type", "application/json");
evhttp_add_header(pstReq->output_headers, "Connection", "keep-alive");
//发送应答
evhttp_send_reply(pstReq, code, reason, evb);
if (evb)
{
evbuffer_free(evb);
}
return 0;
}
int CMyHttpServer::Start()
{
m_thread = std::move(std::thread([this]() {
WorkThread();
}));
m_thread.detach();
return 0;
}
int CMyHttpServer::Stop()
{
if (m_base)
{
event_base_loopbreak(m_base);
event_base_free(m_base);
m_base = nullptr;
}
return 0;
}
void CMyHttpServer::WorkThread()
{
//创建event base对象
m_base = event_base_new();
if (!m_base)
{
std::cout << "create event base failed." << std::endl;
return;
}
//创建http server
m_http = evhttp_new(m_base);
if (!m_http)
{
std::cout << "create evhttp failed." << std::endl;
goto err;
}
//设置http请求回调函数
evhttp_set_gencb(m_http, HttpReqCallback, this);
//绑定、监听IP和端口
m_handle = evhttp_bind_socket_with_handle(m_http, HTTP_SERVER_LISTEN_IP, m_listenPort);
if (!m_handle)
{
std::cout << "bind socket failed, please check port has been used." << std::endl;
goto err;
}
std::cout << "http server started." << std::endl;
//进入事件循环
event_base_dispatch(m_base);
err:
//停止接收新的客户端连接
if(m_handle)
evhttp_del_accept_socket(m_http, m_handle);
//销毁和释放http server资源
if(m_http)
evhttp_free(m_http);
//销毁和释放event base资源
if(m_base)
event_base_free(m_base);
}
void CMyHttpServer::HttpReqCallback(struct evhttp_request* pstReq, void* userData)
{
evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
if (EVHTTP_REQ_GET == cmdType || EVHTTP_REQ_POST == cmdType)
{
CMyHttpServer* this_ = (CMyHttpServer*)userData;
if (!this_)
{
std::cout << "get this failed." << std::endl;
evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
return;
}
//URI中包含?的,用于数据请求;否则用于文件请求
const char* uri = evhttp_request_get_uri(pstReq);
if (strstr(uri, "?"))
this_->OnRequestData(pstReq);
else
this_->OnRequestFile(pstReq);
}
else
{
std::cout << "not support request." << std::endl;
evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
}
}
void CMyHttpServer::OnRequestFile(evhttp_request* pstReq)
{
//TODO:文件下载逻辑代码
}
void CMyHttpServer::OnRequestData(struct evhttp_request* pstReq)
{
if (nullptr == pstReq)
{
std::cout << "invalid parameter." << std::endl;
return;
}
evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
if (EVHTTP_REQ_GET == cmdType) //GET请求
{
RequestProcessGet(pstReq);
}
else if (EVHTTP_REQ_POST == cmdType) //POST请求
{
RequestProcessPost(pstReq);
}
else
{
std::cout << "not support method." << std::endl;
SendReply(pstReq, HTTP_BADMETHOD, "NOT-SUPPORT-METHOD", NULL);
}
}
void CMyHttpServer::RequestProcessGet(evhttp_request* pstReq)
{
//TODO:系统各种业务的GET请求
}
void CMyHttpServer::RequestProcessPost(evhttp_request* pstReq)
{
evhttp_cmd_type cmdType = EVHTTP_REQ_POST;
const char* puri = evhttp_request_get_uri(pstReq);
std::string suri(puri);
char bodyBuf[1024] = { 0 };
if (pstReq->body_size > 0)
{
evbuffer_remove(pstReq->input_buffer, bodyBuf, pstReq->body_size);
std::cout << "POST request uri:" << suri << std::endl << "msg body:" << bodyBuf << std::endl;
}
else
{
std::cout << "POST request uri:" << suri << std::endl;
}
if (suri == std::string(HTTP_CLIENT_LOGIN))
{
//TODO:登录
}
else if (suri == std::string(HTTP_CLIENT_LOGOUT))
{
//TODO:登出
}
else if (suri == std::string(HTTP_CLIENT_HEARBEAT))
{
//TODO:心跳
}
/*else if (suri == std::string(HTTP_CLIENT_XXX))
{
//TODO:系统各种业务的POST请求
}*/
else
{
std::cout << "not support get method" << std::endl;
SendReply(pstReq, HTTP_BADMETHOD, "NOT-SUPPORT-GET", NULL);
}
SendReply(pstReq, HTTP_OK, "200 OK", NULL);
}
CMyHttpServer.cpp
int main()
{
#ifdef WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
std::cout << "Hello MyHttpServer!\n";
CMyHttpServer myHttpServer(HTTP_SERVER_LISTEN_PORT);
myHttpServer.Start();
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
myHttpServer.Stop();
std::cout << "Stop MyHttpServer!\n";
system("pause");
#ifdef WIN32
WSACleanup();
#endif
}
下边是VS2022中的项目工程截图,有个直观的总体认识。
工程配置
头文件包含目录
Lib库目录
引入Lib文件
运行调测
编译、启动运行,使用postman发送登录和登出模拟请求。
登录请求
登出请求
控制台打印输出
单线程模式的思考
对于并发量要求不高、业务请求种类不多的场景,单线程的HTTP服务完全能满足要求了。但是单线程模式,还存在哪些缺陷呢?
存在问题:
1 虽然启用了HTTP Server后台工作线程,但只有1个。如果有多个客户端同时发送请求,或者Server端某个请求操作比较耗时,可能阻塞当前线程。显而易见,面对这种用户场景,单线程的HTTP Server,其并发量还是不够的。
2 在CMyHttpServer::RequestProcessPost函数中,目前有3个if/else分支,随着项目迭代及业务变更,可能会有几十甚至上百的if/else分支,该怎么优化,以便与扩展和维护呢?
三 第二版代码
解决思路
针对第1个问题,查看libevent资料后得知,libevent多线程编程的关键是将每个事件关联到自己的event_base。回过头来再看一下CMyHttpServer类,已经封装了event_base,且类的一个对象实例,可以启用一个HTTP Server后台工作线程。依据这一思路,我们可以再创建一个管理类CMyHttpServerMgr,在这个类中,创建多个CMyHttpServer实例对象,每个实例启用一个工作线程。
针对第2个问题,我们详细看一下HTTP URI的定义格式,有没有什么规律:
红线框中的路径都是一样的,就后边的方法名称不一样。把方法名称当做关键字Key,来定义一组HTTP请求处理函数映射列表。具体实现技术用到了函数指针,达到接口与实现的解耦。
根据上边的优化思路,第二版代码来了。
代码流程
1 创建HTTP Server管理类的套接字监听线程
std::thread
2 创建监听套接字,并开启监听
BindSocket
3?创建HTTP Server后台工作线程池
4 初始化HTTP请求消息映射表
5 启动HTTP Server后台工作线程
std::thread
6?创建event base对象
event_base_new
7 创建http server
evhttp_new
8 接收新的连接请求
evhttp_accept_socket
9 设置http请求回调函数
evhttp_set_gencb
10 进入事件循环
event_base_dispatch
11在回调函数中,并发处理客户端各种HTTP请求
源代码
MyHeader.h和MyDefine.h两个文件没变动。
MyHttpCmdDef.h(新加)
#pragma once
#include"MyHeader.h"
//HTTP请求消息映射结构体
struct HTTPReqInfo
{
//HTTP消息处理关键字
const char* cmdKey;
//HTTP消息处理函数地址
int(*called_fun)(const struct evhttp_request* pstReq, const string& data, void* userData);
};
struct HTTPReqInfoMap
{
//请求消息索引
int index;
//HTTP请求命令
struct HTTPReqInfo* cmd;
};
//存储HTTP命令请求的map表
typedef map<string, HTTPReqInfoMap> HTTP_REQ_INFO_MAP;
CMyHttpServer.h(更新)
#pragma once
#include"MyHeader.h"
#include"MyHttpCmdDef.h"
/*****************************************************************************
**FileName: MyHeader.h
**Function: http服务器启动/停止,接收客户端http请求及处理
**Version record:
**Version Author Data Description
**v1.0.0 chexlong 2022.09 初稿
**v1.0.2 chexlong 2022.09 1,支持多线程
** 2,添加HTTP请求命令模式
*****************************************************************************/
class CMyHttpServer;
typedef std::shared_ptr<CMyHttpServer> CMyHttpServerPtr;
typedef vector<CMyHttpServerPtr> MyHTTPServerVec;
class CMyHttpServerMgr
{
public:
CMyHttpServerMgr(const int& listenPort);
~CMyHttpServerMgr();
//启动http服务
int Start();
//停止http服务
int Stop();
private:
//监听线程
void ListenThreadFunc();
//创建套接字,绑定地址和端口,开启监听
int BindSocket(int port, int backlog);
private:
//http服务监听线程
std::thread m_thread;
//http server监听端口
int m_listenPort;
//监听套接字
int m_listenSocket;
//http消息处理线程池
MyHTTPServerVec m_httpServerPool;
};
class CMyHttpServer
{
public:
CMyHttpServer(const int& listenSocket);
~CMyHttpServer();
//启动http服务
int Start();
//停止http服务
int Stop();
private:
//处理文件请求
void OnRequestFile(struct evhttp_request* pstReq);
//处理数据请求
void OnRequestData(struct evhttp_request* pstReq);
//处理系统各种业务的GET请求
void RequestProcessGet(struct evhttp_request* pstReq);
//处理系统各种业务的POST请求
void RequestProcessPost(struct evhttp_request* pstReq);
//http请求回调函数
static void HttpReqCallback(struct evhttp_request* pstReq, void* userData);
//http工作线程函数
void WorkThread();
//获取http请求负载数据
std::string GetContentFromRequest(struct evhttp_request* req);
//发送http请求应答消息
int SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb);
private:
static int Login(const struct evhttp_request* pstReq, const string& data, void* param);
static int Logout(const struct evhttp_request* pstReq, const string& data, void* param);
static int Heartbeat(const struct evhttp_request* pstReq, const string& data, void* param);
private:
//event base
event_base* m_base;
//http server
evhttp* m_http;
//绑定监听socket句柄
//evhttp_bound_socket* m_handle;
//http服务工作线程
std::thread m_thread;
//http监听套接字
int m_listenSocket;
private:
//HTTP请求消息映射列表
struct HTTPReqInfo httpReqInfo[10] =
{
{"Login", CMyHttpServer::Login,},
{"Logout", CMyHttpServer::Logout,},
{"Heartbeat", CMyHttpServer::Heartbeat,},
{ NULL }
};
HTTP_REQ_INFO_MAP m_httpReqMap;
};
CMyHttpServer.cpp(更新)
#include"CMyHttpServer.h"
#include"MyDefine.h"
CMyHttpServerMgr::CMyHttpServerMgr(const int& listenPort)
{
m_listenPort = listenPort;
m_listenSocket = INVALID_SOCKET;
}
CMyHttpServerMgr::~CMyHttpServerMgr()
{
}
int CMyHttpServerMgr::Start()
{
m_thread = std::move(std::thread([this]() {
ListenThreadFunc();
}));
m_thread.detach();
return 0;
}
int CMyHttpServerMgr::Stop()
{
for (auto& httpServer : m_httpServerPool)
{
httpServer->Stop();
}
return 0;
}
void CMyHttpServerMgr::ListenThreadFunc()
{
std::cout << "http server listen thread id : " << std::this_thread::get_id() << std::endl;
//创建监听套接字,并开启监听
int result = BindSocket(m_listenPort, SOMAXCONN);
if (0 != result)
{
std::cout << "HTTP服务监听套接字创建失败,端口:" << m_listenPort << std::endl;
return;
}
std::cout << "HTTP服务监听端口:" << m_listenPort << std::endl;
//线程池数量:CPU核数 x 2
int threadPoolSize = std::thread::hardware_concurrency() * 2;
for (int i = 0; i < threadPoolSize; i++)
{
CMyHttpServerPtr httpServer(new CMyHttpServer(m_listenSocket));
httpServer->Start();
m_httpServerPool.push_back(httpServer);
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int CMyHttpServerMgr::BindSocket(int port, int backlog)
{
//创建监听套接字
m_listenSocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_listenSocket == INVALID_SOCKET)
{
std::cout << "create listen socket failed." << std::endl;
return -1;
}
//地址可复用
int result = 0, optval = 1;
result = setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(int));
//设为非阻塞模式
int block = 1;
result = ::ioctlsocket(m_listenSocket, FIONBIO, (u_long FAR*) & block);
if (SOCKET_ERROR == result)
{
std::cout << "ioctlsocket failed : " << WSAGetLastError() << std::endl;
closesocket(m_listenSocket);
m_listenSocket = INVALID_SOCKET;
return -1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(struct sockaddr_in));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(port);
local_addr.sin_addr.s_addr = INADDR_ANY;
//绑定IP地址和端口
if (INVALID_SOCKET == ::bind(m_listenSocket, (struct sockaddr*)&local_addr, sizeof(struct sockaddr)))
{
std::cout << "bind failed : " << WSAGetLastError() << std::endl;
closesocket(m_listenSocket);
m_listenSocket = INVALID_SOCKET;
return -1;
}
//开启监听
result = listen(m_listenSocket, backlog);
if (result < 0)
{
std::cout << "listen failed : " << WSAGetLastError() << std::endl;
closesocket(m_listenSocket);
m_listenSocket = INVALID_SOCKET;
return -1;
}
return 0;
}
CMyHttpServer::CMyHttpServer(const int& listenSocket)
{
m_base = nullptr;
m_http = nullptr;
//m_handle = nullptr;
m_listenSocket = listenSocket;
}
CMyHttpServer::~CMyHttpServer()
{
Stop();
}
int CMyHttpServer::SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb)
{
if (nullptr == pstReq)
{
if (evb)
{
evbuffer_free(evb);
}
return -1;
}
//返回HTTP头部
evhttp_add_header(pstReq->output_headers, "Server", "MyHttpServer");
evhttp_add_header(pstReq->output_headers, "Content-Type", "application/json");
evhttp_add_header(pstReq->output_headers, "Connection", "keep-alive");
//发送应答
evhttp_send_reply(pstReq, code, reason, evb);
if (evb)
{
evbuffer_free(evb);
}
return 0;
}
int CMyHttpServer::Start()
{
//初始化HTTP请求消息映射表
struct HTTPReqInfo* cmd = httpReqInfo;
int index = 0;
for (; cmd->cmdKey != NULL; cmd++)
{
struct HTTPReqInfoMap cmdMap;
cmdMap.index = index;
cmdMap.cmd = cmd;
m_httpReqMap[cmd->cmdKey] = cmdMap;
index++;
}
//启动http服务工作线程
m_thread = std::move(std::thread([this]() {
WorkThread();
}));
m_thread.detach();
return 0;
}
int CMyHttpServer::Stop()
{
if (m_base)
{
event_base_loopbreak(m_base);
event_base_free(m_base);
m_base = nullptr;
}
return 0;
}
void CMyHttpServer::WorkThread()
{
std::cout << "http server work thread id : " << std::this_thread::get_id() << std::endl;
//创建event base对象
m_base = event_base_new();
if (!m_base)
{
std::cout << "create event base failed." << std::endl;
return;
}
//创建http server
m_http = evhttp_new(m_base);
if (!m_http)
{
std::cout << "create evhttp failed." << std::endl;
goto err;
}
//接收新的连接请求
if (0 != evhttp_accept_socket(m_http, m_listenSocket))
{
std::cout << "evhttp accecpt failed." << std::endl;
goto err;
}
//设置HTTP请求超时处理时间,60秒
evhttp_set_timeout(m_http, 60);
//设置HTTP支持的请求类型
evhttp_set_allowed_methods(m_http, EVHTTP_REQ_GET | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_POST);
//设置http请求回调函数
evhttp_set_gencb(m_http, HttpReqCallback, this);
std::cout << "http server started." << std::endl;
//进入事件循环
event_base_dispatch(m_base);
err:
//销毁和释放http server资源
if(m_http)
evhttp_free(m_http);
//销毁和释放event base资源
if(m_base)
event_base_free(m_base);
}
void CMyHttpServer::HttpReqCallback(struct evhttp_request* pstReq, void* userData)
{
std::cout << "HttpReqCallback thread id : " << std::this_thread::get_id() << std::endl;
evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
if (EVHTTP_REQ_GET == cmdType || EVHTTP_REQ_POST == cmdType)
{
CMyHttpServer* this_ = (CMyHttpServer*)userData;
if (!this_)
{
std::cout << "get this failed." << std::endl;
evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
return;
}
//URI中包含?的,用于数据请求;否则用于文件请求
const char* uri = evhttp_request_get_uri(pstReq);
if (strstr(uri, "?"))
this_->OnRequestData(pstReq);
else
this_->OnRequestFile(pstReq);
}
else
{
std::cout << "not support request." << std::endl;
evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
}
}
void CMyHttpServer::OnRequestFile(evhttp_request* pstReq)
{
//TODO:文件下载逻辑代码
}
void CMyHttpServer::OnRequestData(struct evhttp_request* pstReq)
{
if (nullptr == pstReq)
{
std::cout << "invalid parameter." << std::endl;
return;
}
evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
if (EVHTTP_REQ_GET == cmdType) //GET请求
{
RequestProcessGet(pstReq);
}
else if (EVHTTP_REQ_POST == cmdType) //POST请求
{
RequestProcessPost(pstReq);
}
else
{
std::cout << "not support method." << std::endl;
SendReply(pstReq, HTTP_BADMETHOD, "NOT-SUPPORT-METHOD", NULL);
}
}
void CMyHttpServer::RequestProcessGet(evhttp_request* pstReq)
{
//TODO:系统各种业务的GET请求
}
std::string CMyHttpServer::GetContentFromRequest(struct evhttp_request* req)
{
std::string data;
struct evbuffer* buf = evhttp_request_get_input_buffer(req);
while (evbuffer_get_length(buf))
{
int n;
char cbuf[256];
memset(cbuf, 0, sizeof(cbuf));
n = evbuffer_remove(buf, cbuf, sizeof(cbuf));
if (n > 0)
{
data.append(cbuf, n);
}
}
return data;
}
void CMyHttpServer::RequestProcessPost(evhttp_request* pstReq)
{
//获取请求URI
evhttp_cmd_type cmdType = EVHTTP_REQ_POST;
const char* puri = evhttp_request_get_uri(pstReq);
struct evkeyvalq headers;
if (evhttp_parse_query(puri, &headers) != 0)
{
std::cout << "http bad request." << std::endl;
evhttp_send_error(pstReq, HTTP_BADREQUEST, 0);
return;
}
//获取请求方法
const char* cmd = evhttp_find_header(&headers, "Action");
if (cmd == NULL)
{
std::cout << "http bad request." << std::endl;
evhttp_send_error(pstReq, HTTP_BADREQUEST, 0);
return;
}
//获取http请求负载数据
std::string jsonData(std::move(GetContentFromRequest(pstReq)));
//http请求消息分发
if (m_httpReqMap.count(cmd) > 0)
{
struct HTTPReqInfo* cmdFound = m_httpReqMap.at(cmd).cmd;
if (cmdFound && cmdFound->called_fun)
{
std::string suri(puri);
std::cout << "POST request uri:" << suri << std::endl << "msg body:" << jsonData << std::endl;
//触发回调
cmdFound->called_fun(pstReq, jsonData, this);
}
else
{
std::cout << "invalid http request cmd : " << cmd << std::endl;
SendReply(pstReq, HTTP_BADMETHOD, "bad method", NULL);
}
}
else
std::cout << "no http request cmd." << std::endl;
SendReply(pstReq, HTTP_OK, "200 OK", NULL);
}
int CMyHttpServer::Login(const struct evhttp_request* pstReq, const string& data, void* param)
{
std::cout << "recv login request..." << std::endl;
//TODO:登录
return -1;
}
int CMyHttpServer::Logout(const struct evhttp_request* pstReq, const string& data, void* param)
{
std::cout << "recv logout request..." << std::endl;
//TODO:登出
return -1;
}
int CMyHttpServer::Heartbeat(const struct evhttp_request* pstReq, const string& data, void* param)
{
std::cout << "recv hreatbeat request..." << std::endl;
//TODO:心跳
return -1;
}
MyHttpServer.cpp(更新)
#include"MyDefine.h"
#include"CMyHttpServer.h"
int main()
{
#ifdef WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
std::cout << "Hello MyHttpServer!\n";
CMyHttpServerMgr myHttpServerMgr(HTTP_SERVER_LISTEN_PORT);
myHttpServerMgr.Start();
while (true)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
}
myHttpServerMgr.Stop();
std::cout << "Stop MyHttpServer!\n";
system("pause");
#ifdef WIN32
WSACleanup();
#endif
}
运行调测
使用postman发送登录和登出请求,在打印输出上可以看到,请求被分配到了不同的线程上去执行处理。
我的机器是8核心数,所以创建了16个HTTP Server后台工作线程。
四 第二版代码的思考
1 HTTP Server适用于客户端主动向服务端发起请求的场景,如果我的服务端程序,除了被动应答请求,还想主动向客户端发起通知怎么办?
?解决办法:添加Websocket服务端模块。
2 如果我的HTTP Server想把接收到的HTTP请求,透传转发给系统内的其它服务模块,该怎么办呢?
?解决办法:添加HTTP客户端模块,建议使用libcurl。
3 如果我想在linux或IOS或Arm等系统上使用基于libevent的HTTP Server怎么办?
?解决办法:libevent本身是支持跨平台的。示例工程MyHttpServer仅支持Windows平台,对其进行跨平台移植便可以了。
4 除了HTTP之外,我还想支持HTTPS,怎么办呢?
?解决办法:libevent编译好了后,可以看到有个libevent_openssl.lib,将它引入工程。
由于个人水平有限,文章或代码中难免出现不当或缺陷的地方,欢迎您指正出来。
同时对于MyHTTPServer有其它什么好的建议或优化措施,也可以提出来沟通交流。感谢您的耐心阅读!
|