基于libevent的C++高并发、易扩展HTTP服务迭代之路

29 篇文章 1 订阅
19 篇文章 0 订阅

一 前言

在项目或产品开发中,需要用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有其它什么好的建议或优化措施,也可以提出来沟通交流。感谢您的耐心阅读!

  • 9
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: libevent是一个开源的C语言网络编程库,主要用于处理高并发网络连接。它提供了对事件驱动的支持,使得开发者可以方便地编写高效的并发网络应用程序。 libevent的核心是事件循环机制。在传统的网络编程中,通常需要使用多线程或多进程来处理并发连接,而使用libevent可以通过一个事件循环来处理多个连接。在事件循环中,可以注册多个事件,并定义回调函数来处理事件的触发。当有事件发生时,libevent会调用相应的回调函数来处理事件的处理逻辑。这样可以大大简化并发编程的复杂性,并提高程序的性能。 libevent的事件模型基于操作系统提供的I/O多复用机制,如select、poll和epoll等。它可以在不同的操作系统平台上运行,并提供一致的接口和高效的事件处理机制。借助这些机制,libevent可以同时处理大量的并发连接,并保持低延迟和高吞吐量。 除了处理网络连接,libevent还提供了其他常用的功能,如定时器和信号处理等。它允许开发者在事件循环中注册定时器事件,可以用于定时任务的调度。同时,libevent还可以处理来自操作系统的信号,并提供了对信号的处理接口,以便开发者能够处理各种系统事件。 总之,libevent是一个功能强大、简单用的高并发网络编程库,适用于开发各种类型的网络应用。无论是开发服务器、代理、聊天程序还是实时应用,libevent都能帮助开发者快速编写高性能的并发网络程序。 ### 回答2: libevent是一个开源的C/C++网络库,用于高性能的事件驱动编程。它提供了一个轻量级、可移植的框架,用于开发高并发的网络应用程序。 它的设计目标是提供一个高效的事件处理器,可以处理成千上万个并发连接,并且支持多线程并发处理。libevent基于事件驱动模型,通过异步I/O和回调函数来实现高并发处理网络请求。 libevent提供了一系列的函数来注册和监听各种网络事件,包括读、写、超时和信号等等。当一个事件发生时,libevent会调用相应的回调函数来处理事件。通过这种方式,我们可以非常方便地处理并发连接,并实现高性能的网络编程。 libevent的优点主要包括: 1. 高性能:libevent使用异步I/O和事件驱动模型,能够处理成千上万个并发连接,具有很高的处理能力。 2. 可移植性:libevent提供了统一的接口,可以在多种操作系统上运行,包括Linux、Windows、Mac等。 3. 用性:libevent简单用,只需注册感兴趣的事件和相应的回调函数,就可以实现高效的网络编程。 4. 多线程支持:libevent支持多线程并发处理,可以充分利用多核CPU的性能优势。 总之,libevent是一款非常适合高并发网络编程的开源库,它可以帮助我们实现高性能的服务器程序,提升系统的并发处理能力。无论是开发网络服务器还是网络应用程序,libevent都是一个不错的选择。 ### 回答3: libevent 是一个用于高并发网络编程的 C/C++ 库。它提供了一个跨平台的异步事件驱动的网络编程框架,能够实现高效地处理大量并发连接的需求。 libevent 的主要特点包括: 1. 异步事件驱动:libevent 使用事件驱动模型,主要利用非阻塞 I/O 和事件回调机制,能够高效地处理大量并发事件。 2. 跨平台支持:libevent 提供了跨不同操作系统的支持,包括 Windows、Linux、Unix 等,并且提供了统一的 API 接口,方便开发者进行跨平台开发。 3. 支持多种网络协议:libevent 支持 TCP、UDP、HTTP 等多种网络协议,为开发者提供了丰富的网络编程能力。 4. 高性能:libevent 的设计目标之一是高性能,它通过使用多复用技术,将系统资源高效地利用起来,能够同时处理大量并发连接,并且保持低延迟。 5. 灵活用:libevent 提供了简洁的 API,使用起来非常方便,可以快速实现高并发网络编程的需求。 总之,libevent 是一个强大而灵活的 C/C++ 库,适用于各种需要处理高并发连接的网络应用程序。无论是开发高性能服务器、代理、负载均衡器还是其他类似应用,libevent 都是一个值得推荐的选择。它的高效性能、跨平台支持和简洁用的 API 接口使得开发者能够快速构建稳定可靠的高并发网络应用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

来灵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值