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

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

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

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

demo如下:

#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

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 <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>

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 <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>


#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<char>容器存储
    */
#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适合需要丰富功能和稳定性的大型项目。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/705678.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

15:HAL----ADC模数转化器

STM32C8T6有2个ADC,ADC1和ADC2 一&#xff1a;介绍 1:简历 ADC&#xff08;Analog-Digital Converter&#xff09;模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量&#xff0c;建立模拟电路到数字电路的桥梁 12位逐次逼近型ADC&#xff0c;1us转…

科技项目验收测试必须进行吗?软件测试公司推荐

科技项目验收测试是指在科技项目开发周期中&#xff0c;对项目完成后进行的一种测试和评估工作。它的目的是验证项目是否达到预期的要求&#xff0c;并确保项目交付给客户前达到预期的质量标准。 一、科技项目验收测试的必要性   科技项目验收测试是项目管理中不可或缺的一个…

Lua实现自定义函数面向对象编程

本文目录 1、引言2、原理3、实例4、层析验证 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我的B站主页~~~ 1、引言 在现代软件开发中&#xff0c;面向对象编程&#xff08;OOP&#xff09;已经成为一种广泛使用的编程范式…

k8s+pv+pvc+nas 数据持久化volumes使用

1 k8s pod申请持久化卷配置 apiVersion: v1 kind: Service metadata:name: $IMG_NAMEnamespace: rz-dtlabels:app: $IMG_NAME spec:type: NodePortports:- port: 8091nodePort: 31082 #service对外开放端口selector:app: $IMG_NAME --- apiVersion: apps/v1 kind: Deployment …

HarmonyOs修改应用名称和图标方法

最近在开发Harmony应用&#xff0c;发现修改app.json5下的lable:app_name和icon不生效 后来经过查找&#xff0c;原来还需要更改entry下的src/main/module.json5才行&#xff0c;具体操作路径是&#xff1a; 更改后生效&#xff1a;

线程池前置知识

并发和并行 并发是指在单核CPU上&#xff0c;多个线程占用不同的CPU时间片。线程在物理上还是串行执行的&#xff0c;但是由于每个线程占用的CPU时间片非常短&#xff08;比如10ms&#xff09;&#xff0c;看起来就像是多个线程都在共同执行一样&#xff0c;这样的场景称作并发…

架构设计 - nginx 的核心机制与主要应用场景

一、nginx 的核心机制&#xff1a; 1. 事件驱动模型&#xff08;epoll 多路复用&#xff09; 事件循环&#xff1a; Nginx的核心组件是一个事件循环&#xff0c;它不断地监听事件&#xff08;如新连接的到来、请求数据的可读性等&#xff09;。 当有事件发生时&#xff0c;事…

『 Linux 』动态库的加载

文章目录 动静态库的区别动态库-共享库动态库的加载动态库的管理 总结 动静态库的区别 动态库(Dynamic Libraries) 链接方式 动态链接,程序在运行时(而不是在编译时)与动态库链接; 操作系统负责加载动态库文件; 文件大小 使用动态库的应用程序通常其可执行文件大小更小; 因…

missing authentication credentials for REST request

1、报错截图 2、解决办法 将elasticsearch的elasticsearch.yml的 xpack.security.enabled: true 改为 xpack.security.enabled: false

vba学习系列(5)--指定区域指定字符串计数

系列文章目录 文章目录 系列文章目录前言一、需求背景二、vba自定义函数1.引入库 总结 前言 一、需求背景 想知道所有客诉项目里面什么项目最多&#xff0c;出现过多少次。 二、vba自定义函数 1.引入库 引用&#xff1a; CountCharInRange(区域,“字符串”) Function CountCh…

光伏电站阵列式冲击波声压光伏驱鸟器

光伏电站内鸟群的聚集可不是一件好事&#xff0c;鸟类排泄物&#xff0c;因其粘度大、具有腐蚀性的特点&#xff0c;一旦堆积在太阳能板上&#xff0c;会严重影响光伏电站的发电效率。长期积累的鸟粪不仅难以清洗&#xff0c;还可能引发组件的热斑效应&#xff0c;严重时甚至可…

Bean基础配置

黑马程序员SSM 文章目录 一、Bean基础配置二、bean别名配置2.1 ban的别名配置2.2 注意事项 三、Bean作用范围配置3.1 Bean作用范围3.2 bean作用范围说明 一、Bean基础配置 二、bean别名配置 2.1 ban的别名配置 2.2 注意事项 获取bean无论是通过id还是name获取&#xff0c;如果…

.NET MAUI Sqlite程序应用-数据库配置(一)

项目名称:Ownership&#xff08;权籍信息采集&#xff09; 一、安装 NuGet 包 安装 sqlite-net-pcl 安装 SQLitePCLRawEx.bundle_green 二、创建多个表及相关字段 Models\OwnershipItem.cs using SQLite;namespace Ownership.Models {public class fa_rural_base//基础数据…

ArcGIS Pro SDK (三)Addin控件 1 按钮类

ArcGIS Pro SDK &#xff08;一&#xff09;Addin控件 目录 ArcGIS Pro SDK &#xff08;一&#xff09;Addin控件1 Addin控件2 ArcGIS Pro 按钮2.1 添加控件2.2 Code 3 ArcGIS Pro 按钮面板3.1 添加控件3.2 Code 4 ArcGIS Pro 菜单4.1 添加控件4.2 Code 5 ArcGIS Pro 分割按钮…

如何从零训练多模态大模型(预训练方向)

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学. 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 汇总合集&…

所爱隔山海,上海婚恋交友app开发,我奔向你

每每讲到婚恋&#xff0c;就有感叹“难”的声音&#xff0c;在婚恋市场上&#xff0c;到处充斥者“难”的声音&#xff0c;更有胜者&#xff0c;甚至发出了“难于上青天”的感叹。在婚恋市场蹉跎浮沉的青年俊女们&#xff0c;越挫越勇&#xff0c;向着爱的呼唤&#xff0c;一次…

Java工具-实现无损png转换jpg格式

目录 1、背景说明 2、通过代码实现格式转换 3、无损转化 4、说明 读取 PNG 图像&#xff1a; 创建空的 JPG 图像&#xff1a; 绘制 PNG 图像到 JPG 图像&#xff1a; 设置 JPG 图片压缩质量&#xff1a; 写入 JPG 文件并关闭流&#xff1a; 5、jpg转png 1、背景说明 …

Opencv图像梯度计算

Opencv图像梯度计算 Sobel算子 可以理解为是做边缘检测的一种方法。 首先说明自己对图像梯度的简单理解&#xff1a;简单理解就是图像的颜色发生变化的边界区域在X方向和Y方向上的梯度值 Gx Gy 而Gx和Gy处的梯度的计算—使用下面的公式来进行计算。 G x [ − 1 0 1 − 2 0 …

计算机网络(5) ARP协议

什么是ARP 地址解析协议&#xff0c;即ARP&#xff08;Address Resolution Protocol&#xff09;&#xff0c;是根据IP地址获取物理地址的一个TCP/IP协议。主机发送信息时将包含目标IP地址的ARP请求广播到局域网络上的所有主机&#xff0c;并接收返回消息&#xff0c;以此确定…

【MySQL】mysql中常见的内置函数(日期、字符串、数学函数)

文章目录 案例表日期函数字符串函数数学函数其他函数 案例表 emp students 表 exam_result 表 日期函数 注意current_time和now的区别 案例一&#xff1a; 创建一张表用来记录生日&#xff0c;表结构如下 添加日期&#xff1a; insert tmp (birthday) values (2003-01-3…