IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 猿创征文|基于libevet的C++高并发、易扩展HTTP服务迭代之路 -> 正文阅读

[网络协议]猿创征文|基于libevet的C++高并发、易扩展HTTP服务迭代之路

一 前言

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

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 02:20:34  更:2022-09-15 02:21:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 2:26:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码