本文使用C++模拟实现http的客户端请求和http的服务端响应功能,并介绍几种封装HTTP协议的三方库。
1、实现简单HTTP的服务端功能
本程序使用C++ tcp服务端代码模拟HTTP的服务端,服务端返回给客户端的消息内容按照HTTP协议的消息响应格式进行了组装。
demo如下:
#include#include #include #include #include int main() { int server_fd, new_socket; struct sockaddr_in address; int addrlen = sizeof(address); char buffer[1024] = {0}; const char* hello = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\nHello, World!"; // 创建套接字 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(37777); // 绑定套接字到端口 if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); exit(EXIT_FAILURE); } // 监听连接 if (listen(server_fd, 3) < 0) { perror("listen"); exit(EXIT_FAILURE); } printf("Listening on port 37777\n"); while (true) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { perror("accept"); exit(EXIT_FAILURE); } // 读取客户端请求 int length = read(new_socket, buffer, 1024); printf("******** HTTP Server recv request length: %d,as follow: ********\n%s\n",length,buffer); // 发送响应 send(new_socket, hello, strlen(hello), 0); printf("******** HTTP Server replied to the request ********\n"); close(new_socket); } return 0; }
2、实现简单HTTP的客户端功能
本程序使用C++ tcp客户端代码模拟HTTP的客户端,客户端发给服务端的请求内容按照HTTP协议的请求格式进行了组装。
demo如下:
#include#include #include #include #include #include int main() { int sock = 0, length; struct sockaddr_in serv_addr; char buffer[1024] = {0}; const char* get_request = "GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"; if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation error"); exit(EXIT_FAILURE); } serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(37777); //serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //bind ip method 1 // 将IPv4地址从点分十进制转换为二进制形式 bind ip method 2 if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { perror("Invalid address/Address not supported"); exit(EXIT_FAILURE); } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Connection failed"); exit(EXIT_FAILURE); } send(sock, get_request, strlen(get_request), 0); printf("HTTP Client GET method has been sent\n"); length = read(sock, buffer, 1024); printf("******** HTTP Client receive msg length: %d,context as follow: ********\n%s\n",length,buffer); printf("****************\n"); close(sock); return 0; }
分别运行上面的服务端和客户端程序,服务端程序运行结果如下:
客户端程序运行结果如下:
3、实现HTTP的客户端访问百度功能
本程序使用C++ tcp客户端代码模拟HTTP的客户端,按照http协议请求格式对百度网页进行GET请求消息的组装。
实现demo如下:
#include#include #include #include #include #include #include #define URL "/" //资源 #define HTTP_VESTION "HTTP/1.1" #define HOST "www.baidu.com" //域名 #define CONNETION "Connection: close\r\n" //短链接 #define BUFFER_SIZE 1024 std::string strBuffer = ""; int getIpAddress(char** ip) { int ret = -1; struct hostent *ht = NULL; ht = gethostbyname(HOST); if(ht) { printf("type:%s\n", ht->h_addrtype == AF_INET ? "AF_INET" : "AF_INET6"); for(int i=0; ;i++) { if(ht->h_addr_list[i]!=NULL) { *ip = inet_ntoa(*((struct in_addr *)ht->h_addr_list[i])); ret = 1; break; } } } return ret; } char* receiveNew(int sockfd, size_t& len) { char* buffer = nullptr; // 初始化为nullptr ssize_t totalBytesReceived = 0; // 累计接收的字节数 ssize_t bytesReceived; // 当前接收的字节数 // 循环接收数据,直到对方关闭连接或发生错误 do { // 分配额外的空间以容纳更多数据 char* newBuffer = new char[totalBytesReceived + 1024]; // 假设每次额外分配1024字节,用于接受下次数据 if (buffer) { // 如果有旧的数据,复制到新的缓冲区,每次所有内容复制一遍 std::memcpy(newBuffer, buffer, totalBytesReceived); delete[] buffer; // 释放旧缓冲区 } buffer = newBuffer; // 更新指针以指向新的缓冲区,指向的内容可以任意大,即只要可分配 // 接收数据 bytesReceived = recv(sockfd, buffer + totalBytesReceived, 1024, 0); if (bytesReceived == -1) { // 错误处理,例如检查errno std::cerr << "Error receiving data: " << strerror(errno) << std::endl; delete[] buffer; // 释放已分配的缓冲区 return nullptr; // 返回nullptr表示错误 } totalBytesReceived += bytesReceived; // 累计已接收的字节数 // 如果需要,可以在此处设置接收的最大字节数限制 } while (bytesReceived > 0); // 当recv返回0时,表示对方已关闭连接 // 调整缓冲区的大小以匹配实际接收的数据量 char* finalBuffer = new char[totalBytesReceived + 1]; // +1为了字符串终止符'\0' std::memcpy(finalBuffer, buffer, totalBytesReceived); finalBuffer[totalBytesReceived] = '\0'; // 添加字符串终止符 delete[] buffer; // 释放中间缓冲区 len = totalBytesReceived; // 更新len以反映实际接收的数据长度 return finalBuffer; // 返回最终缓冲区 } void receiveString(int sockfd, size_t& len) { char buffer[BUFFER_SIZE] = {}; // 初始化为nullptr ssize_t totalBytesReceived = 0; // 累计接收的字节数 ssize_t bytesReceived; // 当前接收的字节数 // 循环接收数据,直到对方关闭连接或发生错误 do { // 接收数据 bytesReceived = recv(sockfd, buffer, BUFFER_SIZE, 0); if (bytesReceived == -1) { // 错误处理,例如检查errno printf("Error receiving data:%s", strerror(errno)); } strBuffer += buffer; totalBytesReceived += bytesReceived; // 累计已接收的字节数 memset(buffer,0,BUFFER_SIZE); // 如果需要,可以在此处设置接收的最大字节数限制 } while (bytesReceived > 0); // 当recv返回0时,表示对方已关闭连接 len = totalBytesReceived; } void MakeSocketConnect() { char* ipAddr = nullptr; getIpAddress(&ipAddr); int sock = 0; struct sockaddr_in serv_addr; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(80); if (inet_pton(AF_INET, ipAddr, &serv_addr.sin_addr) <= 0) { perror("Invalid address/Address not supported"); exit(EXIT_FAILURE); } if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("Socket creation error"); exit(EXIT_FAILURE); } if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { perror("Connection failed"); exit(EXIT_FAILURE); } char get_request[1024] = {}; sprintf(get_request,"GET %s %s\r\n" "Host: %s\r\n" "%s\r\n" "\r\n", URL,HTTP_VESTION,HOST,CONNETION); send(sock, get_request, strlen(get_request), 0); printf("HTTP Client GET method has been sent\n"); size_t len = 0; // 用于保存接收到的数据的长度 /* 由于HTTP服务端回复的消息客户端一次接收不完,需要多次接收,直到数据接收完毕 * 本文采用下面两种C++方法进行接收 * 方法1:采用new方法分配内存,缺点:需要多次分配,并且需要多次memcpy(可以使用c语言malloc分配内存,realloc扩容) * 方法2:采用string容器(stinrg是字符串容器),动态的存储数据,也可以采用vector 容器存储 */ #if 0 char* receivedData = receiveNew(sock,len); if (receivedData) { // 处理接收到的数据 printf("******** HTTP Client receive msg length: %d,context as follow: ********\n%s\n",len,receivedData); printf("****************\n"); // 释放动态分配的内存 delete[] receivedData; } else { // 错误处理 printf("Failed to receive data.\n"); } #else receiveString(sock,len); printf("******** HTTP Client receive msg length: %d,context as follow: ********\n%s\n",len,strBuffer.c_str()); printf("****************\n"); #endif close(sock); } int main() { MakeSocketConnect(); return 0; }
上面程序运行结果如下:
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适合需要丰富功能和稳定性的大型项目。
还没有评论,来说两句吧...