• 猿创征文|基于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对象

    1. EVENT2_EXPORT_SYMBOL
    2. struct event_base *event_base_new(void);

    3 创建http server

    1. EVENT2_EXPORT_SYMBOL
    2. struct evhttp *evhttp_new(struct event_base *base);

    4 设置http请求回调函数

    1. EVENT2_EXPORT_SYMBOL
    2. void evhttp_set_gencb(struct evhttp *http, void (*cb)(struct evhttp_request *, void *), void *arg);

    5 绑定、监听IP和端口

    1. EVENT2_EXPORT_SYMBOL
    2. struct evhttp_bound_socket *evhttp_bind_socket_with_handle(struct evhttp *http, const char *address, ev_uint16_t port);

    6 进入事件循环

    1. EVENT2_EXPORT_SYMBOL
    2. int event_base_dispatch(struct event_base *);

    7 在回调函数中,处理客户端各种HTTP请求

    源代码

    代码中有详细的注释,就不多说了,请看代码。

    MyDefine.h

    1. #pragma once
    2. #define HTTP_SERVER_LISTEN_IP "0.0.0.0" //http服务监听地址
    3. #define HTTP_SERVER_LISTEN_PORT 8080 //http服务监听端口
    4. #define HTTP_CLIENT_LOGIN "/client?Action=Login" //系统登录URI
    5. #define HTTP_CLIENT_LOGOUT "/client?Action=Logout" //系统登出URI
    6. #define HTTP_CLIENT_HEARBEAT "/client?Action=Heartbeat" //心跳URI
    7. /*
    8. * 系统各种业务请求URL宏定义,格式与登录、登出、心跳类似
    9. */

    MyHeader.h

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include"event2/bufferevent.h"
    10. #include"event2/buffer.h"
    11. #include"event2/listener.h"
    12. #include"event2/util.h"
    13. #include"event2/event_compat.h"
    14. #include"event2/event.h"
    15. #include"event2/keyvalq_struct.h"
    16. #include"event2/http.h"
    17. #include"event2/http_struct.h"
    18. #include"event2/http_compat.h"
    19. using std::mutex;
    20. using std::thread;
    21. using std::string;
    22. using std::map;
    23. using std::vector;

    CMyHttpServer.h

    1. #pragma once
    2. #include"MyHeader.h"
    3. /*****************************************************************************
    4. **FileName: MyHeader.h
    5. **Function: http服务器启动/停止,接收客户端http请求及处理
    6. **Version record:
    7. **Version Author Data Description
    8. **v1.0.0 chexlong 2022.09 初稿
    9. *****************************************************************************/
    10. class CMyHttpServer
    11. {
    12. public:
    13. CMyHttpServer(const int& listenPort);
    14. ~CMyHttpServer();
    15. //启动http服务
    16. int Start();
    17. //停止http服务
    18. int Stop();
    19. private:
    20. //处理文件请求
    21. void OnRequestFile(struct evhttp_request* pstReq);
    22. //处理数据请求
    23. void OnRequestData(struct evhttp_request* pstReq);
    24. //处理系统各种业务的GET请求
    25. void RequestProcessGet(struct evhttp_request* pstReq);
    26. //处理系统各种业务的POST请求
    27. void RequestProcessPost(struct evhttp_request* pstReq);
    28. //http请求回调函数
    29. static void HttpReqCallback(struct evhttp_request* pstReq, void* userData);
    30. //http工作线程函数
    31. void WorkThread();
    32. //发送http请求应答消息
    33. int SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb);
    34. private:
    35. //event base
    36. event_base* m_base;
    37. //http server
    38. evhttp* m_http;
    39. //绑定监听socket句柄
    40. evhttp_bound_socket* m_handle;
    41. //http服务线程
    42. std::thread m_thread;
    43. //http监听端口
    44. int m_listenPort;
    45. };

    CMyHttpServer.cpp

    1. #include"CMyHttpServer.h"
    2. #include"MyDefine.h"
    3. CMyHttpServer::CMyHttpServer(const int& listenPort)
    4. {
    5. m_base = nullptr;
    6. m_http = nullptr;
    7. m_handle = nullptr;
    8. m_listenPort = listenPort;
    9. }
    10. CMyHttpServer::~CMyHttpServer()
    11. {
    12. Stop();
    13. }
    14. int CMyHttpServer::SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb)
    15. {
    16. if (nullptr == pstReq)
    17. {
    18. if (evb)
    19. {
    20. evbuffer_free(evb);
    21. }
    22. return -1;
    23. }
    24. //返回HTTP头部
    25. evhttp_add_header(pstReq->output_headers, "Server", "MyHttpServer");
    26. evhttp_add_header(pstReq->output_headers, "Content-Type", "application/json");
    27. evhttp_add_header(pstReq->output_headers, "Connection", "keep-alive");
    28. //发送应答
    29. evhttp_send_reply(pstReq, code, reason, evb);
    30. if (evb)
    31. {
    32. evbuffer_free(evb);
    33. }
    34. return 0;
    35. }
    36. int CMyHttpServer::Start()
    37. {
    38. m_thread = std::move(std::thread([this]() {
    39. WorkThread();
    40. }));
    41. m_thread.detach();
    42. return 0;
    43. }
    44. int CMyHttpServer::Stop()
    45. {
    46. if (m_base)
    47. {
    48. event_base_loopbreak(m_base);
    49. event_base_free(m_base);
    50. m_base = nullptr;
    51. }
    52. return 0;
    53. }
    54. void CMyHttpServer::WorkThread()
    55. {
    56. //创建event base对象
    57. m_base = event_base_new();
    58. if (!m_base)
    59. {
    60. std::cout << "create event base failed." << std::endl;
    61. return;
    62. }
    63. //创建http server
    64. m_http = evhttp_new(m_base);
    65. if (!m_http)
    66. {
    67. std::cout << "create evhttp failed." << std::endl;
    68. goto err;
    69. }
    70. //设置http请求回调函数
    71. evhttp_set_gencb(m_http, HttpReqCallback, this);
    72. //绑定、监听IP和端口
    73. m_handle = evhttp_bind_socket_with_handle(m_http, HTTP_SERVER_LISTEN_IP, m_listenPort);
    74. if (!m_handle)
    75. {
    76. std::cout << "bind socket failed, please check port has been used." << std::endl;
    77. goto err;
    78. }
    79. std::cout << "http server started." << std::endl;
    80. //进入事件循环
    81. event_base_dispatch(m_base);
    82. err:
    83. //停止接收新的客户端连接
    84. if(m_handle)
    85. evhttp_del_accept_socket(m_http, m_handle);
    86. //销毁和释放http server资源
    87. if(m_http)
    88. evhttp_free(m_http);
    89. //销毁和释放event base资源
    90. if(m_base)
    91. event_base_free(m_base);
    92. }
    93. void CMyHttpServer::HttpReqCallback(struct evhttp_request* pstReq, void* userData)
    94. {
    95. evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
    96. if (EVHTTP_REQ_GET == cmdType || EVHTTP_REQ_POST == cmdType)
    97. {
    98. CMyHttpServer* this_ = (CMyHttpServer*)userData;
    99. if (!this_)
    100. {
    101. std::cout << "get this failed." << std::endl;
    102. evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
    103. return;
    104. }
    105. //URI中包含?的,用于数据请求;否则用于文件请求
    106. const char* uri = evhttp_request_get_uri(pstReq);
    107. if (strstr(uri, "?"))
    108. this_->OnRequestData(pstReq);
    109. else
    110. this_->OnRequestFile(pstReq);
    111. }
    112. else
    113. {
    114. std::cout << "not support request." << std::endl;
    115. evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
    116. }
    117. }
    118. void CMyHttpServer::OnRequestFile(evhttp_request* pstReq)
    119. {
    120. //TODO:文件下载逻辑代码
    121. }
    122. void CMyHttpServer::OnRequestData(struct evhttp_request* pstReq)
    123. {
    124. if (nullptr == pstReq)
    125. {
    126. std::cout << "invalid parameter." << std::endl;
    127. return;
    128. }
    129. evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
    130. if (EVHTTP_REQ_GET == cmdType) //GET请求
    131. {
    132. RequestProcessGet(pstReq);
    133. }
    134. else if (EVHTTP_REQ_POST == cmdType) //POST请求
    135. {
    136. RequestProcessPost(pstReq);
    137. }
    138. else
    139. {
    140. std::cout << "not support method." << std::endl;
    141. SendReply(pstReq, HTTP_BADMETHOD, "NOT-SUPPORT-METHOD", NULL);
    142. }
    143. }
    144. void CMyHttpServer::RequestProcessGet(evhttp_request* pstReq)
    145. {
    146. //TODO:系统各种业务的GET请求
    147. }
    148. void CMyHttpServer::RequestProcessPost(evhttp_request* pstReq)
    149. {
    150. evhttp_cmd_type cmdType = EVHTTP_REQ_POST;
    151. const char* puri = evhttp_request_get_uri(pstReq);
    152. std::string suri(puri);
    153. char bodyBuf[1024] = { 0 };
    154. if (pstReq->body_size > 0)
    155. {
    156. evbuffer_remove(pstReq->input_buffer, bodyBuf, pstReq->body_size);
    157. std::cout << "POST request uri:" << suri << std::endl << "msg body:" << bodyBuf << std::endl;
    158. }
    159. else
    160. {
    161. std::cout << "POST request uri:" << suri << std::endl;
    162. }
    163. if (suri == std::string(HTTP_CLIENT_LOGIN))
    164. {
    165. //TODO:登录
    166. }
    167. else if (suri == std::string(HTTP_CLIENT_LOGOUT))
    168. {
    169. //TODO:登出
    170. }
    171. else if (suri == std::string(HTTP_CLIENT_HEARBEAT))
    172. {
    173. //TODO:心跳
    174. }
    175. /*else if (suri == std::string(HTTP_CLIENT_XXX))
    176. {
    177. //TODO:系统各种业务的POST请求
    178. }*/
    179. else
    180. {
    181. std::cout << "not support get method" << std::endl;
    182. SendReply(pstReq, HTTP_BADMETHOD, "NOT-SUPPORT-GET", NULL);
    183. }
    184. SendReply(pstReq, HTTP_OK, "200 OK", NULL);
    185. }

    CMyHttpServer.cpp

    1. int main()
    2. {
    3. #ifdef WIN32
    4. WSADATA wsaData;
    5. WSAStartup(MAKEWORD(2, 2), &wsaData);
    6. #endif
    7. std::cout << "Hello MyHttpServer!\n";
    8. CMyHttpServer myHttpServer(HTTP_SERVER_LISTEN_PORT);
    9. myHttpServer.Start();
    10. while (true)
    11. {
    12. std::this_thread::sleep_for(std::chrono::seconds(1));
    13. }
    14. myHttpServer.Stop();
    15. std::cout << "Stop MyHttpServer!\n";
    16. system("pause");
    17. #ifdef WIN32
    18. WSACleanup();
    19. #endif
    20. }

    下边是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(新加)

    1. #pragma once
    2. #include"MyHeader.h"
    3. //HTTP请求消息映射结构体
    4. struct HTTPReqInfo
    5. {
    6. //HTTP消息处理关键字
    7. const char* cmdKey;
    8. //HTTP消息处理函数地址
    9. int(*called_fun)(const struct evhttp_request* pstReq, const string& data, void* userData);
    10. };
    11. struct HTTPReqInfoMap
    12. {
    13. //请求消息索引
    14. int index;
    15. //HTTP请求命令
    16. struct HTTPReqInfo* cmd;
    17. };
    18. //存储HTTP命令请求的map表
    19. typedef map<string, HTTPReqInfoMap> HTTP_REQ_INFO_MAP;

    CMyHttpServer.h(更新)

    1. #pragma once
    2. #include"MyHeader.h"
    3. #include"MyHttpCmdDef.h"
    4. /*****************************************************************************
    5. **FileName: MyHeader.h
    6. **Function: http服务器启动/停止,接收客户端http请求及处理
    7. **Version record:
    8. **Version Author Data Description
    9. **v1.0.0 chexlong 2022.09 初稿
    10. **v1.0.2 chexlong 2022.09 1,支持多线程
    11. ** 2,添加HTTP请求命令模式
    12. *****************************************************************************/
    13. class CMyHttpServer;
    14. typedef std::shared_ptr<CMyHttpServer> CMyHttpServerPtr;
    15. typedef vector<CMyHttpServerPtr> MyHTTPServerVec;
    16. class CMyHttpServerMgr
    17. {
    18. public:
    19. CMyHttpServerMgr(const int& listenPort);
    20. ~CMyHttpServerMgr();
    21. //启动http服务
    22. int Start();
    23. //停止http服务
    24. int Stop();
    25. private:
    26. //监听线程
    27. void ListenThreadFunc();
    28. //创建套接字,绑定地址和端口,开启监听
    29. int BindSocket(int port, int backlog);
    30. private:
    31. //http服务监听线程
    32. std::thread m_thread;
    33. //http server监听端口
    34. int m_listenPort;
    35. //监听套接字
    36. int m_listenSocket;
    37. //http消息处理线程池
    38. MyHTTPServerVec m_httpServerPool;
    39. };
    40. class CMyHttpServer
    41. {
    42. public:
    43. CMyHttpServer(const int& listenSocket);
    44. ~CMyHttpServer();
    45. //启动http服务
    46. int Start();
    47. //停止http服务
    48. int Stop();
    49. private:
    50. //处理文件请求
    51. void OnRequestFile(struct evhttp_request* pstReq);
    52. //处理数据请求
    53. void OnRequestData(struct evhttp_request* pstReq);
    54. //处理系统各种业务的GET请求
    55. void RequestProcessGet(struct evhttp_request* pstReq);
    56. //处理系统各种业务的POST请求
    57. void RequestProcessPost(struct evhttp_request* pstReq);
    58. //http请求回调函数
    59. static void HttpReqCallback(struct evhttp_request* pstReq, void* userData);
    60. //http工作线程函数
    61. void WorkThread();
    62. //获取http请求负载数据
    63. std::string GetContentFromRequest(struct evhttp_request* req);
    64. //发送http请求应答消息
    65. int SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb);
    66. private:
    67. static int Login(const struct evhttp_request* pstReq, const string& data, void* param);
    68. static int Logout(const struct evhttp_request* pstReq, const string& data, void* param);
    69. static int Heartbeat(const struct evhttp_request* pstReq, const string& data, void* param);
    70. private:
    71. //event base
    72. event_base* m_base;
    73. //http server
    74. evhttp* m_http;
    75. //绑定监听socket句柄
    76. //evhttp_bound_socket* m_handle;
    77. //http服务工作线程
    78. std::thread m_thread;
    79. //http监听套接字
    80. int m_listenSocket;
    81. private:
    82. //HTTP请求消息映射列表
    83. struct HTTPReqInfo httpReqInfo[10] =
    84. {
    85. {"Login", CMyHttpServer::Login,},
    86. {"Logout", CMyHttpServer::Logout,},
    87. {"Heartbeat", CMyHttpServer::Heartbeat,},
    88. { NULL }
    89. };
    90. HTTP_REQ_INFO_MAP m_httpReqMap;
    91. };

    CMyHttpServer.cpp(更新)

    1. #include"CMyHttpServer.h"
    2. #include"MyDefine.h"
    3. CMyHttpServerMgr::CMyHttpServerMgr(const int& listenPort)
    4. {
    5. m_listenPort = listenPort;
    6. m_listenSocket = INVALID_SOCKET;
    7. }
    8. CMyHttpServerMgr::~CMyHttpServerMgr()
    9. {
    10. }
    11. int CMyHttpServerMgr::Start()
    12. {
    13. m_thread = std::move(std::thread([this]() {
    14. ListenThreadFunc();
    15. }));
    16. m_thread.detach();
    17. return 0;
    18. }
    19. int CMyHttpServerMgr::Stop()
    20. {
    21. for (auto& httpServer : m_httpServerPool)
    22. {
    23. httpServer->Stop();
    24. }
    25. return 0;
    26. }
    27. void CMyHttpServerMgr::ListenThreadFunc()
    28. {
    29. std::cout << "http server listen thread id : " << std::this_thread::get_id() << std::endl;
    30. //创建监听套接字,并开启监听
    31. int result = BindSocket(m_listenPort, SOMAXCONN);
    32. if (0 != result)
    33. {
    34. std::cout << "HTTP服务监听套接字创建失败,端口:" << m_listenPort << std::endl;
    35. return;
    36. }
    37. std::cout << "HTTP服务监听端口:" << m_listenPort << std::endl;
    38. //线程池数量:CPU核数 x 2
    39. int threadPoolSize = std::thread::hardware_concurrency() * 2;
    40. for (int i = 0; i < threadPoolSize; i++)
    41. {
    42. CMyHttpServerPtr httpServer(new CMyHttpServer(m_listenSocket));
    43. httpServer->Start();
    44. m_httpServerPool.push_back(httpServer);
    45. std::this_thread::sleep_for(std::chrono::milliseconds(10));
    46. }
    47. }
    48. int CMyHttpServerMgr::BindSocket(int port, int backlog)
    49. {
    50. //创建监听套接字
    51. m_listenSocket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    52. if (m_listenSocket == INVALID_SOCKET)
    53. {
    54. std::cout << "create listen socket failed." << std::endl;
    55. return -1;
    56. }
    57. //地址可复用
    58. int result = 0, optval = 1;
    59. result = setsockopt(m_listenSocket, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(int));
    60. //设为非阻塞模式
    61. int block = 1;
    62. result = ::ioctlsocket(m_listenSocket, FIONBIO, (u_long FAR*) & block);
    63. if (SOCKET_ERROR == result)
    64. {
    65. std::cout << "ioctlsocket failed : " << WSAGetLastError() << std::endl;
    66. closesocket(m_listenSocket);
    67. m_listenSocket = INVALID_SOCKET;
    68. return -1;
    69. }
    70. struct sockaddr_in local_addr;
    71. memset(&local_addr, 0, sizeof(struct sockaddr_in));
    72. local_addr.sin_family = AF_INET;
    73. local_addr.sin_port = htons(port);
    74. local_addr.sin_addr.s_addr = INADDR_ANY;
    75. //绑定IP地址和端口
    76. if (INVALID_SOCKET == ::bind(m_listenSocket, (struct sockaddr*)&local_addr, sizeof(struct sockaddr)))
    77. {
    78. std::cout << "bind failed : " << WSAGetLastError() << std::endl;
    79. closesocket(m_listenSocket);
    80. m_listenSocket = INVALID_SOCKET;
    81. return -1;
    82. }
    83. //开启监听
    84. result = listen(m_listenSocket, backlog);
    85. if (result < 0)
    86. {
    87. std::cout << "listen failed : " << WSAGetLastError() << std::endl;
    88. closesocket(m_listenSocket);
    89. m_listenSocket = INVALID_SOCKET;
    90. return -1;
    91. }
    92. return 0;
    93. }
    94. CMyHttpServer::CMyHttpServer(const int& listenSocket)
    95. {
    96. m_base = nullptr;
    97. m_http = nullptr;
    98. //m_handle = nullptr;
    99. m_listenSocket = listenSocket;
    100. }
    101. CMyHttpServer::~CMyHttpServer()
    102. {
    103. Stop();
    104. }
    105. int CMyHttpServer::SendReply(struct evhttp_request* pstReq, int code, const char* reason, struct evbuffer* evb)
    106. {
    107. if (nullptr == pstReq)
    108. {
    109. if (evb)
    110. {
    111. evbuffer_free(evb);
    112. }
    113. return -1;
    114. }
    115. //返回HTTP头部
    116. evhttp_add_header(pstReq->output_headers, "Server", "MyHttpServer");
    117. evhttp_add_header(pstReq->output_headers, "Content-Type", "application/json");
    118. evhttp_add_header(pstReq->output_headers, "Connection", "keep-alive");
    119. //发送应答
    120. evhttp_send_reply(pstReq, code, reason, evb);
    121. if (evb)
    122. {
    123. evbuffer_free(evb);
    124. }
    125. return 0;
    126. }
    127. int CMyHttpServer::Start()
    128. {
    129. //初始化HTTP请求消息映射表
    130. struct HTTPReqInfo* cmd = httpReqInfo;
    131. int index = 0;
    132. for (; cmd->cmdKey != NULL; cmd++)
    133. {
    134. struct HTTPReqInfoMap cmdMap;
    135. cmdMap.index = index;
    136. cmdMap.cmd = cmd;
    137. m_httpReqMap[cmd->cmdKey] = cmdMap;
    138. index++;
    139. }
    140. //启动http服务工作线程
    141. m_thread = std::move(std::thread([this]() {
    142. WorkThread();
    143. }));
    144. m_thread.detach();
    145. return 0;
    146. }
    147. int CMyHttpServer::Stop()
    148. {
    149. if (m_base)
    150. {
    151. event_base_loopbreak(m_base);
    152. event_base_free(m_base);
    153. m_base = nullptr;
    154. }
    155. return 0;
    156. }
    157. void CMyHttpServer::WorkThread()
    158. {
    159. std::cout << "http server work thread id : " << std::this_thread::get_id() << std::endl;
    160. //创建event base对象
    161. m_base = event_base_new();
    162. if (!m_base)
    163. {
    164. std::cout << "create event base failed." << std::endl;
    165. return;
    166. }
    167. //创建http server
    168. m_http = evhttp_new(m_base);
    169. if (!m_http)
    170. {
    171. std::cout << "create evhttp failed." << std::endl;
    172. goto err;
    173. }
    174. //接收新的连接请求
    175. if (0 != evhttp_accept_socket(m_http, m_listenSocket))
    176. {
    177. std::cout << "evhttp accecpt failed." << std::endl;
    178. goto err;
    179. }
    180. //设置HTTP请求超时处理时间,60
    181. evhttp_set_timeout(m_http, 60);
    182. //设置HTTP支持的请求类型
    183. evhttp_set_allowed_methods(m_http, EVHTTP_REQ_GET | EVHTTP_REQ_OPTIONS | EVHTTP_REQ_POST);
    184. //设置http请求回调函数
    185. evhttp_set_gencb(m_http, HttpReqCallback, this);
    186. std::cout << "http server started." << std::endl;
    187. //进入事件循环
    188. event_base_dispatch(m_base);
    189. err:
    190. //销毁和释放http server资源
    191. if(m_http)
    192. evhttp_free(m_http);
    193. //销毁和释放event base资源
    194. if(m_base)
    195. event_base_free(m_base);
    196. }
    197. void CMyHttpServer::HttpReqCallback(struct evhttp_request* pstReq, void* userData)
    198. {
    199. std::cout << "HttpReqCallback thread id : " << std::this_thread::get_id() << std::endl;
    200. evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
    201. if (EVHTTP_REQ_GET == cmdType || EVHTTP_REQ_POST == cmdType)
    202. {
    203. CMyHttpServer* this_ = (CMyHttpServer*)userData;
    204. if (!this_)
    205. {
    206. std::cout << "get this failed." << std::endl;
    207. evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
    208. return;
    209. }
    210. //URI中包含?的,用于数据请求;否则用于文件请求
    211. const char* uri = evhttp_request_get_uri(pstReq);
    212. if (strstr(uri, "?"))
    213. this_->OnRequestData(pstReq);
    214. else
    215. this_->OnRequestFile(pstReq);
    216. }
    217. else
    218. {
    219. std::cout << "not support request." << std::endl;
    220. evhttp_send_error(pstReq, HTTP_BADREQUEST, "Bad Request");
    221. }
    222. }
    223. void CMyHttpServer::OnRequestFile(evhttp_request* pstReq)
    224. {
    225. //TODO:文件下载逻辑代码
    226. }
    227. void CMyHttpServer::OnRequestData(struct evhttp_request* pstReq)
    228. {
    229. if (nullptr == pstReq)
    230. {
    231. std::cout << "invalid parameter." << std::endl;
    232. return;
    233. }
    234. evhttp_cmd_type cmdType = evhttp_request_get_command(pstReq);
    235. if (EVHTTP_REQ_GET == cmdType) //GET请求
    236. {
    237. RequestProcessGet(pstReq);
    238. }
    239. else if (EVHTTP_REQ_POST == cmdType) //POST请求
    240. {
    241. RequestProcessPost(pstReq);
    242. }
    243. else
    244. {
    245. std::cout << "not support method." << std::endl;
    246. SendReply(pstReq, HTTP_BADMETHOD, "NOT-SUPPORT-METHOD", NULL);
    247. }
    248. }
    249. void CMyHttpServer::RequestProcessGet(evhttp_request* pstReq)
    250. {
    251. //TODO:系统各种业务的GET请求
    252. }
    253. std::string CMyHttpServer::GetContentFromRequest(struct evhttp_request* req)
    254. {
    255. std::string data;
    256. struct evbuffer* buf = evhttp_request_get_input_buffer(req);
    257. while (evbuffer_get_length(buf))
    258. {
    259. int n;
    260. char cbuf[256];
    261. memset(cbuf, 0, sizeof(cbuf));
    262. n = evbuffer_remove(buf, cbuf, sizeof(cbuf));
    263. if (n > 0)
    264. {
    265. data.append(cbuf, n);
    266. }
    267. }
    268. return data;
    269. }
    270. void CMyHttpServer::RequestProcessPost(evhttp_request* pstReq)
    271. {
    272. //获取请求URI
    273. evhttp_cmd_type cmdType = EVHTTP_REQ_POST;
    274. const char* puri = evhttp_request_get_uri(pstReq);
    275. struct evkeyvalq headers;
    276. if (evhttp_parse_query(puri, &headers) != 0)
    277. {
    278. std::cout << "http bad request." << std::endl;
    279. evhttp_send_error(pstReq, HTTP_BADREQUEST, 0);
    280. return;
    281. }
    282. //获取请求方法
    283. const char* cmd = evhttp_find_header(&headers, "Action");
    284. if (cmd == NULL)
    285. {
    286. std::cout << "http bad request." << std::endl;
    287. evhttp_send_error(pstReq, HTTP_BADREQUEST, 0);
    288. return;
    289. }
    290. //获取http请求负载数据
    291. std::string jsonData(std::move(GetContentFromRequest(pstReq)));
    292. //http请求消息分发
    293. if (m_httpReqMap.count(cmd) > 0)
    294. {
    295. struct HTTPReqInfo* cmdFound = m_httpReqMap.at(cmd).cmd;
    296. if (cmdFound && cmdFound->called_fun)
    297. {
    298. std::string suri(puri);
    299. std::cout << "POST request uri:" << suri << std::endl << "msg body:" << jsonData << std::endl;
    300. //触发回调
    301. cmdFound->called_fun(pstReq, jsonData, this);
    302. }
    303. else
    304. {
    305. std::cout << "invalid http request cmd : " << cmd << std::endl;
    306. SendReply(pstReq, HTTP_BADMETHOD, "bad method", NULL);
    307. }
    308. }
    309. else
    310. std::cout << "no http request cmd." << std::endl;
    311. SendReply(pstReq, HTTP_OK, "200 OK", NULL);
    312. }
    313. int CMyHttpServer::Login(const struct evhttp_request* pstReq, const string& data, void* param)
    314. {
    315. std::cout << "recv login request..." << std::endl;
    316. //TODO:登录
    317. return -1;
    318. }
    319. int CMyHttpServer::Logout(const struct evhttp_request* pstReq, const string& data, void* param)
    320. {
    321. std::cout << "recv logout request..." << std::endl;
    322. //TODO:登出
    323. return -1;
    324. }
    325. int CMyHttpServer::Heartbeat(const struct evhttp_request* pstReq, const string& data, void* param)
    326. {
    327. std::cout << "recv hreatbeat request..." << std::endl;
    328. //TODO:心跳
    329. return -1;
    330. }

    MyHttpServer.cpp(更新)

    1. #include"MyDefine.h"
    2. #include"CMyHttpServer.h"
    3. int main()
    4. {
    5. #ifdef WIN32
    6. WSADATA wsaData;
    7. WSAStartup(MAKEWORD(2, 2), &wsaData);
    8. #endif
    9. std::cout << "Hello MyHttpServer!\n";
    10. CMyHttpServerMgr myHttpServerMgr(HTTP_SERVER_LISTEN_PORT);
    11. myHttpServerMgr.Start();
    12. while (true)
    13. {
    14. std::this_thread::sleep_for(std::chrono::seconds(1));
    15. }
    16. myHttpServerMgr.Stop();
    17. std::cout << "Stop MyHttpServer!\n";
    18. system("pause");
    19. #ifdef WIN32
    20. WSACleanup();
    21. #endif
    22. }

    运行调测

    使用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有其它什么好的建议或优化措施,也可以提出来沟通交流。感谢您的耐心阅读!

  • 相关阅读:
    近世代数——Part2 群:基础与子群
    @Transactional 注解 同一个类下的两个方法
    MQTT 基础--MQTT 发布、订阅和取消订阅 :第 4 部分
    char指针和unsigned char指针间的转换
    excel如何让线条消失,直接设置网格即可,碰到不方便的地方优先百度,再采取蛮干
    深入剖析云计算与云服务器ECS:从基础到实践
    7天酒店蝉联“年度杰出投资价值酒店品牌” 加速下沉市场拓展
    多层全连接网络:实现手写数字识别50轮准确率92.1%
    推荐一款数据mock框架,无需任何依赖,贼牛逼
    SystemUI控制状态栏通知面板自动展开和收起
  • 原文地址:https://blog.csdn.net/chexlong/article/details/126726886