• C++ 实现HTTP的客户端、服务端demo和HTTP三方库介绍


            本文使用C++模拟实现http的客户端请求和http的服务端响应功能,并介绍几种封装HTTP协议的三方库。

    1、实现简单HTTP的服务端功能

            本程序使用C++ tcp服务端代码模拟HTTP的服务端,服务端返回给客户端的消息内容按照HTTP协议的消息响应格式进行了组装。

    demo如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main() {
    7. int server_fd, new_socket;
    8. struct sockaddr_in address;
    9. int addrlen = sizeof(address);
    10. char buffer[1024] = {0};
    11. const char* hello = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello, World!";
    12. // 创建套接字
    13. if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
    14. perror("socket failed");
    15. exit(EXIT_FAILURE);
    16. }
    17. address.sin_family = AF_INET;
    18. address.sin_addr.s_addr = INADDR_ANY;
    19. address.sin_port = htons(37777);
    20. // 绑定套接字到端口
    21. if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
    22. perror("bind failed");
    23. exit(EXIT_FAILURE);
    24. }
    25. // 监听连接
    26. if (listen(server_fd, 3) < 0) {
    27. perror("listen");
    28. exit(EXIT_FAILURE);
    29. }
    30. printf("Listening on port 37777\n");
    31. while (true) {
    32. if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
    33. perror("accept");
    34. exit(EXIT_FAILURE);
    35. }
    36. // 读取客户端请求
    37. int length = read(new_socket, buffer, 1024);
    38. printf("******** HTTP Server recv request length: %d,as follow: ********\n%s\n",length,buffer);
    39. // 发送响应
    40. send(new_socket, hello, strlen(hello), 0);
    41. printf("******** HTTP Server replied to the request ********\n");
    42. close(new_socket);
    43. }
    44. return 0;
    45. }

    2、实现简单HTTP的客户端功能

             本程序使用C++ tcp客户端代码模拟HTTP的客户端,客户端发给服务端的请求内容按照HTTP协议的请求格式进行了组装。

    demo如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main() {
    8. int sock = 0, length;
    9. struct sockaddr_in serv_addr;
    10. char buffer[1024] = {0};
    11. const char* get_request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n";
    12. if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    13. perror("Socket creation error");
    14. exit(EXIT_FAILURE);
    15. }
    16. serv_addr.sin_family = AF_INET;
    17. serv_addr.sin_port = htons(37777);
    18. //serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //bind ip method 1
    19. // 将IPv4地址从点分十进制转换为二进制形式 bind ip method 2
    20. if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
    21. perror("Invalid address/Address not supported");
    22. exit(EXIT_FAILURE);
    23. }
    24. if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    25. perror("Connection failed");
    26. exit(EXIT_FAILURE);
    27. }
    28. send(sock, get_request, strlen(get_request), 0);
    29. printf("HTTP Client GET method has been sent\n");
    30. length = read(sock, buffer, 1024);
    31. printf("******** HTTP Client receive msg length: %d,context as follow: ********\n%s\n",length,buffer);
    32. printf("****************\n");
    33. close(sock);
    34. return 0;
    35. }

    分别运行上面的服务端和客户端程序,服务端程序运行结果如下:

    客户端程序运行结果如下:

     3、实现HTTP的客户端访问百度功能

            本程序使用C++ tcp客户端代码模拟HTTP的客户端,按照http协议请求格式对百度网页进行GET请求消息的组装。

    实现demo如下:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define URL "/" //资源
    9. #define HTTP_VESTION "HTTP/1.1"
    10. #define HOST "www.baidu.com" //域名
    11. #define CONNETION "Connection: close\r\n" //短链接
    12. #define BUFFER_SIZE 1024
    13. std::string strBuffer = "";
    14. int getIpAddress(char** ip)
    15. {
    16. int ret = -1;
    17. struct hostent *ht = NULL;
    18. ht = gethostbyname(HOST);
    19. if(ht) {
    20. printf("type:%s\n", ht->h_addrtype == AF_INET ? "AF_INET" : "AF_INET6");
    21. for(int i=0; ;i++) {
    22. if(ht->h_addr_list[i]!=NULL) {
    23. *ip = inet_ntoa(*((struct in_addr *)ht->h_addr_list[i]));
    24. ret = 1;
    25. break;
    26. }
    27. }
    28. }
    29. return ret;
    30. }
    31. char* receiveNew(int sockfd, size_t& len) {
    32. char* buffer = nullptr; // 初始化为nullptr
    33. ssize_t totalBytesReceived = 0; // 累计接收的字节数
    34. ssize_t bytesReceived; // 当前接收的字节数
    35. // 循环接收数据,直到对方关闭连接或发生错误
    36. do {
    37. // 分配额外的空间以容纳更多数据
    38. char* newBuffer = new char[totalBytesReceived + 1024]; // 假设每次额外分配1024字节,用于接受下次数据
    39. if (buffer) {
    40. // 如果有旧的数据,复制到新的缓冲区,每次所有内容复制一遍
    41. std::memcpy(newBuffer, buffer, totalBytesReceived);
    42. delete[] buffer; // 释放旧缓冲区
    43. }
    44. buffer = newBuffer; // 更新指针以指向新的缓冲区,指向的内容可以任意大,即只要可分配
    45. // 接收数据
    46. bytesReceived = recv(sockfd, buffer + totalBytesReceived, 1024, 0);
    47. if (bytesReceived == -1) {
    48. // 错误处理,例如检查errno
    49. std::cerr << "Error receiving data: " << strerror(errno) << std::endl;
    50. delete[] buffer; // 释放已分配的缓冲区
    51. return nullptr; // 返回nullptr表示错误
    52. }
    53. totalBytesReceived += bytesReceived; // 累计已接收的字节数
    54. // 如果需要,可以在此处设置接收的最大字节数限制
    55. } while (bytesReceived > 0); // 当recv返回0时,表示对方已关闭连接
    56. // 调整缓冲区的大小以匹配实际接收的数据量
    57. char* finalBuffer = new char[totalBytesReceived + 1]; // +1为了字符串终止符'\0'
    58. std::memcpy(finalBuffer, buffer, totalBytesReceived);
    59. finalBuffer[totalBytesReceived] = '\0'; // 添加字符串终止符
    60. delete[] buffer; // 释放中间缓冲区
    61. len = totalBytesReceived; // 更新len以反映实际接收的数据长度
    62. return finalBuffer; // 返回最终缓冲区
    63. }
    64. void receiveString(int sockfd, size_t& len) {
    65. char buffer[BUFFER_SIZE] = {}; // 初始化为nullptr
    66. ssize_t totalBytesReceived = 0; // 累计接收的字节数
    67. ssize_t bytesReceived; // 当前接收的字节数
    68. // 循环接收数据,直到对方关闭连接或发生错误
    69. do {
    70. // 接收数据
    71. bytesReceived = recv(sockfd, buffer, BUFFER_SIZE, 0);
    72. if (bytesReceived == -1) {
    73. // 错误处理,例如检查errno
    74. printf("Error receiving data:%s", strerror(errno));
    75. }
    76. strBuffer += buffer;
    77. totalBytesReceived += bytesReceived; // 累计已接收的字节数
    78. memset(buffer,0,BUFFER_SIZE);
    79. // 如果需要,可以在此处设置接收的最大字节数限制
    80. } while (bytesReceived > 0); // 当recv返回0时,表示对方已关闭连接
    81. len = totalBytesReceived;
    82. }
    83. void MakeSocketConnect()
    84. {
    85. char* ipAddr = nullptr;
    86. getIpAddress(&ipAddr);
    87. int sock = 0;
    88. struct sockaddr_in serv_addr;
    89. serv_addr.sin_family = AF_INET;
    90. serv_addr.sin_port = htons(80);
    91. if (inet_pton(AF_INET, ipAddr, &serv_addr.sin_addr) <= 0) {
    92. perror("Invalid address/Address not supported");
    93. exit(EXIT_FAILURE);
    94. }
    95. if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
    96. perror("Socket creation error");
    97. exit(EXIT_FAILURE);
    98. }
    99. if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
    100. perror("Connection failed");
    101. exit(EXIT_FAILURE);
    102. }
    103. char get_request[1024] = {};
    104. sprintf(get_request,"GET %s %s\r\n"
    105. "Host: %s\r\n"
    106. "%s\r\n"
    107. "\r\n",
    108. URL,HTTP_VESTION,HOST,CONNETION);
    109. send(sock, get_request, strlen(get_request), 0);
    110. printf("HTTP Client GET method has been sent\n");
    111. size_t len = 0; // 用于保存接收到的数据的长度
    112. /* 由于HTTP服务端回复的消息客户端一次接收不完,需要多次接收,直到数据接收完毕
    113. * 本文采用下面两种C++方法进行接收
    114. * 方法1:采用new方法分配内存,缺点:需要多次分配,并且需要多次memcpy(可以使用c语言malloc分配内存,realloc扩容)
    115. * 方法2:采用string容器(stinrg是字符串容器),动态的存储数据,也可以采用vector容器存储
    116. */
    117. #if 0
    118. char* receivedData = receiveNew(sock,len);
    119. if (receivedData) {
    120. // 处理接收到的数据
    121. printf("******** HTTP Client receive msg length: %d,context as follow: ********\n%s\n",len,receivedData);
    122. printf("****************\n");
    123. // 释放动态分配的内存
    124. delete[] receivedData;
    125. } else {
    126. // 错误处理
    127. printf("Failed to receive data.\n");
    128. }
    129. #else
    130. receiveString(sock,len);
    131. printf("******** HTTP Client receive msg length: %d,context as follow: ********\n%s\n",len,strBuffer.c_str());
    132. printf("****************\n");
    133. #endif
    134. close(sock);
    135. }
    136. int main() {
    137. MakeSocketConnect();
    138. return 0;
    139. }

    上面程序运行结果如下:

    上面接收的响应正文太长,上面图片没有截完。

    C++ 实现http请求用法也可参考下面的文章:Linux C/C++ 实现HTTP请求器(TCP客户端)_linux c++ http-CSDN博客

    4、HTTP协议的三方库

            使用封装HTTP协议的三方库,可以高效开发HTTP的功能需求,HTTP的协议封装有很多的三方库,下面只介绍几种,更多的三方库可查看其他资料。

    (1)cpp-httplib

           优点:

                    轻量级:非常轻量,仅包含一个头文件,方便集成到项目中。

                    简单易用:API设计直观,可以快速上手。

                    跨平台:支持多种操作系统,如Windows、Linux和macOS等。

                    支持断点续传:通过ETag和Range字段,可以实现断点续传功能。

            缺点:

                    功能相对简单:相比于其他HTTP库,可能不支持一些高级特性。

    (2)libcurl

             优点:

                    功能强大:支持多种协议,包括HTTP、HTTPS、FTP等。

                    灵活性强:支持代理、HTTP POST、SSL连接、HTTP PUT等多种功能。

                    跨平台:可以在多种操作系统上运行。

                    与其他库配合:可以与其他网络库如libevent、openssl等配合使用,实现高性能的网络

                     编程。

             缺点:

                    依赖外部库:需要安装和配置libcurl库。

            libcurl的介绍和使用可参考libcurl开源库的编译与使用全攻略_libcurl编译-CSDN博客

    (3)Poco C++ Libraries

             优点:

                    功能丰富:除了HTTP功能外,还包含其他实用程序库,如日志记录、XML解析等。

                    跨平台:支持多种操作系统和编译器。

                    稳定性:经过长时间的验证和广泛的使用,具有较高的稳定性。

             缺点:

                    较为庞大:包含多个库和模块,可能对于只需要HTTP功能的项目来说过于庞大。

            cpp-httplib适合需要轻量级、简单易用且跨平台的HTTP库的项目。 libcurl适合需要强大功能和灵活性的项目,特别是需要支持多种协议和与其他库配合使用的场景。Poco C++ Libraries适合需要丰富功能和稳定性的大型项目。

  • 相关阅读:
    OD机考真题搜集:不开心的小朋友
    【深度学习】解析Vision Transformer (ViT): 从基础到实现与训练
    如何通过代码混淆绕过苹果机审,解决APP被拒问题
    论文速览【RL - Exploration】—— 【Go-Explore】First return, then explore
    Alevel经济知识点讲解:effects of deflation
    【Crypto-m】基于go-zero框架的通信加密中间件管理工具。使用不对称和对称的混合加密策略(hybrid-encryption)
    在学习DNS的过程中给我的启发
    高精度移相(MCP41xx)程序stm32F103,F407通用,更改引脚即可(SPI软件模拟通信)
    【新版】软考 - 系统架构设计师(总结笔记)
    安装集群kafka
  • 原文地址:https://blog.csdn.net/hanxiaoyong_/article/details/139637400