吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1266|回复: 8
收起左侧

[C&C++ 原创] Linux C++ HTTP(S) && ProgressBar && TCP/IP

  [复制链接]
hujiayucc 发表于 2023-11-2 11:59
本帖最后由 hujiayucc 于 2023-11-2 12:11 编辑

太久没更新内容了,今天水水贴,我比较不爱交流

也没啥好说的,那就简单写两个小Demo吧,旨在互相交流学习

static const char* REGEX_DOMAIN = "[^//]*?\\.([^/|^:{1,5}/]*)";

先写个URL方便存储URL的信息

struct info {
    std::string ipv4;
    std::string url;
    std::string hostname;
    unsigned short port = 443;
};

url.cpp

#include <url.hpp>
#include <logger.hpp>

std::string URL::getIP(const char *host) {
    if (host == NULL) {
        return std::string();
    }

    struct hostent *pstHostent = NULL;
    if (inet_addr(host) == INADDR_NONE) {
        if ((pstHostent = gethostbyname(host)) == NULL) {
            return std::string();
        }
        char *ipaddr = inet_ntoa(*(struct in_addr *)pstHostent->h_addr);
        if (ipaddr) {
            return std::string(ipaddr);
        }
    } else {
        return std::string(host);
    }
    return std::string();
}

unsigned short URL::getPort(const std::string& url) {
    std::smatch match;
    std::regex pattern("http[s]?://[^:]+:([0-9]+)/.*");
    std::regex pattern2("^(http|https)://");
    if (std::regex_match(url, match, pattern)) {
        return static_cast<unsigned short>(std::stoi(match[1]));
    } else if (std::regex_search(url, match, pattern2)) {
        std::string scheme = match[1];
        if (scheme == "https") return 443;
        else if (scheme == "http") return 80;
        else Logger::error("Invalid scheme: ", scheme);
    } else {
        Logger::error("Invalid URL: ", url);
    }
    return 80;
}

url.hpp

#include <netdb.h>
#include <regex.hpp>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <cstdio>
#include "struct.hpp"

#ifndef URL_H
#define URL_H

class URL : public info {
public:
    URL(std::string url) {
        this->url = url;
        setRegex(url, hostname, REGEX_DOMAIN, 0);
        ipv4 = getIP(hostname.c_str());
        port = getPort(url);
    }

    URL(const char* url) : URL(std::string(url)) {}
private:
    static std::string getIP(const char *host);
    static unsigned short getPort(const std::string& url);
};

#endif // URL_H

http.hpp

#include <logger.hpp>
#include "request.hpp"

bool http(Request request) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        Logger::error("XYL", "Error creating socket.");
        return false;
    }
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(request.port);
    server_addr.sin_addr.s_addr = inet_addr(request.ipv4.c_str());
    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        Logger::error("XYL", "Error connecting.");
        return false;
    }
    std::string GET_REQUEST = ("GET / HTTP/1.1\r\nHost: " + request.hostname + "\r\nConnection: close\r\n\r\n");
    if (write(sockfd, GET_REQUEST.c_str(), GET_REQUEST.length()) < 0) {
        Logger::error("XYL", "Error writing to socket.");
        return false;
    }
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    std::string body;
    while (read(sockfd, buffer, sizeof(buffer)) > 0) {
        body.append(buffer);
    }
    std::cout << buffer << std::endl;
    close(sockfd);
    return true;
}

既然有http了,那就再加个https吧。整合一下思路,这里不使用网络编程库和libcurl,使用C++标准库。通过OpenSSL进行TLS握手,不过我这里没做证书验证,方便一些开发环境下的自签证书。

request.cpp

#include <request.hpp>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

Response Request::perform() {
    if (port == 80) {
        return Response(http());
    } else if (port == 443) {
        return Response(https());
    } else {
        std::smatch match;
        std::regex pattern2("^(http|https)://");
        if (std::regex_search(url, match, pattern2)) {
            std::string scheme = match[1];
            if (scheme == "https") return Response(https());
            else if (scheme == "http") return Response(http());
            else Logger::error("Invalid scheme: ", scheme);
        } else {
            Logger::error("Invalid URL: ", url);
        }
    }

    return Response();
}

std::string Request::http() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        Logger::error("XYL", "Error creating socket.");
        return std::string();
    }
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = inet_addr(ipv4.c_str());
    if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        Logger::error("XYL", "Error connecting.");
        return std::string();
    }
    std::string REQUEST = ("GET / HTTP/1.1\r\nHost: " + hostname + "\r\nConnection: close\r\n\r\n");
    Logger::error("Invalid URL: ", REQUEST);
    if (write(sockfd, REQUEST.c_str(), REQUEST.length()) < 0) {
        Logger::error("XYL", "Error writing to socket.");
        return std::string();
    }
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    std::string body;
    while (read(sockfd, buffer, sizeof(buffer)) > 0) {
        body.append(buffer);
    }
    close(sockfd);
    return buffer;
}

std::string Request::https() {
    // 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return std::string();
    }
    // 服务器地址结构体
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(ipv4.c_str());
    serv_addr.sin_port = htons(port);
    // 连接服务器
    if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect");
        return std::string();
    }
    // SSL上下文
    SSL_CTX* ctx = SSL_CTX_new(TLS_method());
    if (!ctx) {
        perror("SSL_CTX_new");
        return std::string();
    }
    // SSL连接
    SSL* ssl = SSL_new(ctx);
    if (!ssl) {
        perror("SSL_new");
        SSL_CTX_free(ctx);
        return std::string();
    }
    if (SSL_set_fd(ssl, sockfd) != 1) {
        perror("SSL_set_fd");
        SSL_free(ssl);
        SSL_CTX_free(ctx);
        return std::string();
    }
    if (SSL_connect(ssl) != 1) {
        perror("SSL_connect");
        SSL_free(ssl);
        SSL_CTX_free(ctx);
        return std::string();
    }
    // HTTPS请求
    std::string REQUEST = ("GET / HTTP/1.1\r\nHost: " + hostname + "\r\nConnection: close\r\n\r\n");
    if (SSL_write(ssl, REQUEST.c_str(), REQUEST.length()) != REQUEST.length()) {
        perror("SSL_write");
    }
    // 接收和打印响应
    char *buffer;
    buffer = (char*) malloc(1024);
    std::string body;
    while (SSL_read(ssl, buffer, sizeof(buffer)) > 0) {
        body.append(buffer);
        free(buffer);
        buffer = (char*) malloc(1024);
    }
    free(buffer);
    // 关闭连接
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sockfd);
    SSL_CTX_free(ctx);
    return body;
}

request.hpp

#include <string>
#include "url.hpp"
#include "struct.hpp"
#include "response.hpp"
#include "logger.hpp"

class Request : public info, public request {
public:
    Request(URL url) : info(url) {}
    Request(std::string url) : info(URL(url)) {}
    Request(const char* url) : info(URL(url)) {}
    Response perform();
private:
    std::string http();
    std::string https();
};

以上代码写了两个结构体,一个是URL的信息,一个是Request结构体。enum.h这里可以忽略,因为考虑到GET以及其他请求模式做的请求类型枚举,不过具体功能还没写。

struct.hpp

#include <string>
#include "enum.h"

struct info {
    std::string ipv4;
    std::string url;
    std::string hostname;
    unsigned short port = 443;
};

struct request {
    std::string ua;
    methods method;
};

enum.h

#ifndef ENUM_H
#define ENUM_H

typedef enum {
    DEFAULT = 0,
    RED = 31,
    GREEN = 32,
    YELLOW = 33,
    BLUE = 34,
    MAGENTA = 35,
    CYAN = 36,
    WHITE = 37
} textcolor;

typedef enum {
    GET,
    POST,
    HEAD,
    DELETE,
    PUT
} methods;

#endif // ENUM_H

logger.cpp

#include <logger.hpp>

void Logger::info(const char* tag, const char* log) {
    /*
    // ANSI转义序列,设置文本颜色为青色
    std::cout << colorToString(CYAN) << tag << ":";
    // 重置颜色为默认
    std::cout << colorToString(DEFAULT) << " " << log << std::endl;
    */
    printf("\033[%dm%s:\033[0m %s\n", CYAN, tag, log);
}

void Logger::error(const char* tag, const char* log) {
    /*
    // ANSI转义序列,设置文本颜色为红色
    std::cout << colorToString(RED) << tag << ":";
    // 重置颜色为默认
    std::cout << colorToString(DEFAULT) << " " << log << std::endl;
    */
    printf("\033[%dm%s:\033[0m %s\n", RED, tag, log);
}

void Logger::warn(const char* tag, const char* log) {
    /*
    // ANSI转义序列,设置文本颜色为黄色
    std::cout << colorToString(YELLOW) << tag << ":";
    // 重置颜色为默认
    std::cout << colorToString(DEFAULT) << " " << log << std::endl;
    */
    printf("\033[%dm%s:\033[0m %s\n", YELLOW, tag, log);
}

logger.hpp

#ifndef LOGGER_H
#define LOGGER_H
#include "enum.h"
#include <cstdio>
#include <string>

class Logger {
    public:
        static void info(const char* tag, const char* log);
        static void error(const char* tag, const char* log);
        static void warn(const char* tag, const char* log);
        static void info(std::string tag, std::string log) { info(tag.c_str(), log.c_str()); }
        static void error(std::string tag, std::string log) { error(tag.c_str(), log.c_str()); }
        static void warn(std::string tag, std::string log) { warn(tag.c_str(), log.c_str()); }
        static void info(const char* tag, std::string log) { info(tag, log.c_str()); }
        static void error(const char* tag, std::string log) { error(tag, log.c_str()); }
        static void warn(const char* tag, std::string log) { warn(tag, log.c_str()); }
};
#endif // LOGGER_H

废话讲了这么多,回到原题

现在需要通过http(s)请求获取一个返回体的判断,如果返回体为空就抛出错误

response.hpp

#include <string>

class Response {
public:
    Response(std::string request) : body(request) {
        if (body == "") {
            throw std::string("\033[31mResponseException:\033[0m Request Body is empty!");
        }
    }

    Response() {
        throw std::string("\033[31mResponseException:\033[0m Request Body is empty!");
    }

    std::string body;
};

写个调用示例运行一下看看

XYL.cpp

#include <logger.hpp>
#include <request.hpp>
#include <iostream>

int main() {
    URL url("https://www.hujiayucc.cn");
    Logger::info("url", url.url);
    Logger::info("hostname", url.hostname);
    Logger::info("ipv4", url.ipv4);
    Logger::info("port", std::to_string(url.port));
    Request request```cpp
(url);
    try {
        Response response = request.perform();
        std::cout << response.body << std::endl;
    } catch (const std::string& e) {
        std::cerr << e << std::endl;
    }

    return 0;
}

效果似乎还行

下面是写的一个简洁型进度条(ProgressBar)

progress.cpp

#include <iostream>
#include <chrono>
#include <thread>

// 定义一个进度条类
class ProgressBar {
    // 定义私有的成员变量
    private:
        int width; // 进度条的宽度,即显示多少个字符
        int progress; // 进度条的进度,即完成了多少百分比
        char fill; // 进度条的填充字符,即用什么字符来表示进度
        char empty; // 进度条的空白字符,即用什么字符来表示剩余空间

    // 定义公有的成员函数
    public:
        // 构造函数,初始化进度条的参数
        ProgressBar(int w, char f, char e) {
            width = w; // 设置宽度
            progress = 0; // 设置初始进度为0
            fill = f; // 设置填充字符
            empty = e; // 设置空白字符
        }

        // 更新函数,根据给定的百分比更新进度条的显示
        void update(int p) {
            progress = p; // 更新进度
            int n = width * progress / 100; // 计算需要显示多少个填充字符
            std::cout << "\r["; // 输出一个回车符和一个左括号,将光标移动到行首
            for (int i = 0; i < n; i++) { // 循环输出填充字符
                std::cout << fill;
            }
            for (int i = 0; i < width - n; i++) { // 循环输出空白字符
                std::cout << empty;
            }
            std::cout << "] " << progress << "%"; // 输出一个右括号和一个百分比
            std::cout.flush(); // 刷新输出缓冲区,使进度条立即显示
        }
};

// 主函数,测试进度条的效果
int main() {
    ProgressBar bar(50, '#', '-'); // 创建一个宽度为50,填充字符为#,空白字符为-的进度条对象
    for (int i = 0; i <= 100; i++) { // 循环更新进度条的进度,从0到100
        bar.update(i); // 调用更新函数,传入当前的百分比
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 暂停0.1秒,模拟一个耗时的任务
    }
    std::cout << std::endl; // 输出一个换行符,结束进度条的显示
    return 0;
}

运行看一下效果

稍作调整改成彩色试试看

// 导入需要的头文件
#include <iostream>
#include <chrono>
#include <thread>

// 定义一些ANSI转义序列,用于设置文本颜色
#define NONE "\033[0m" // 关闭所有属性
#define RED "\033[0;31m" // 红色
#define GREEN "\033[0;32m" // 绿色
#define YELLOW "\033[0;33m" // 黄色
#define BLUE "\033[0;34m" // 蓝色
#define PURPLE "\033[0;35m" // 紫色

// 定义一个进度条类
class ProgressBar {
    // 定义私有的成员变量
    private:
        int width; // 进度条的宽度,即显示多少个字符
        int progress; // 进度条的进度,即完成了多少百分比
        char fill; // 进度条的填充字符,即用什么字符来表示进度
        char empty; // 进度条的空白字符,即用什么字符来表示剩余空间

    // 定义公有的成员函数
    public:
        // 构造函数,初始化进度条的参数
        ProgressBar(int w, char f, char e) {
            width = w; // 设置宽度
            progress = 0; // 设置初始进度为0
            fill = f; // 设置填充字符
            empty = e; // 设置空白字符
        }

        // 更新函数,根据给定的百分比更新进度条的显示
        void update(int p) {
            progress = p; // 更新进度
            int n = width * progress / 100; // 计算需要显示多少个填充字符
            std::cout << "\r["; // 输出一个回车符和一个左括号,将光标移动到行首
            for (int i = 0; i < n; i++) { // 循环输出填充字符
                switch (i % 5) { // 根据不同的位置,选择不同的颜色
                    case 0: std::cout << RED << fill << NONE; break;
                    case 1: std::cout << GREEN << fill << NONE; break;
                    case 2: std::cout << YELLOW << fill << NONE; break;
                    case 3: std::cout << BLUE << fill << NONE; break;
                    case 4: std::cout << PURPLE << fill << NONE; break;
                }
            }
            for (int i = 0; i < width - n; i++) { // 循环输出空白字符
                std::cout << empty;
            }
            std::cout << "] " << progress << "%"; // 输出一个右括号和一个百分比
            std::cout.flush(); // 刷新输出缓冲区,使进度条立即显示
        }
};

// 主函数,测试进度条的效果
int main() {
    ProgressBar bar(50, '#', '-'); // 创建一个宽度为50,填充字符为#,空白字符为-的进度条对象
    for (int i = 0; i <= 100; i++) { // 循环更新进度条的进度,从0到100
        bar.update(i); // 调用更新函数,传入当前的百分比
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 暂停0.1秒,模拟一个耗时的任务
    }
    std::cout << std::endl; // 输出一个换行符,结束进度条的显示
    return 0;
}


写了大半天,就先到这里吧,还有挺多地方需要改,下次再讲吧,晚安

如果对你有帮助,记得关注+点赞哦~~~

蓝奏云:点击下载

免费评分

参与人数 2威望 +1 吾爱币 +11 热心值 +2 收起 理由
苏紫方璇 + 1 + 10 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
9152pojie + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

JuncoJet 发表于 2023-11-2 12:45
还以为 Linux 下HTTP HTTPS需要用libcurl
没想到 socket
 楼主| hujiayucc 发表于 2023-11-2 13:34
JuncoJet 发表于 2023-11-2 12:45
还以为 Linux 下HTTP HTTPS需要用libcurl
没想到 socket

开始我也想过用libcurl和boost,后面还是选择原生库自己实现一个http和OpenSSL TLS握手
可坏 发表于 2023-11-2 14:51
 楼主| hujiayucc 发表于 2023-11-2 15:53
可坏 发表于 2023-11-2 14:51
好家伙,直接手机开发的吗

MT管理器手搓
巧言乱德 发表于 2023-11-2 16:32

牛逼啊,大佬
BonnieRan 发表于 2023-11-2 17:17
Request类的http()函数返回值应该是body 不是buffer吧;
还有函数体中logger::error这行是不是不要;
我是小白不太懂
 楼主| hujiayucc 发表于 2023-11-2 17:35
BonnieRan 发表于 2023-11-2 17:17
Request类的http()函数返回值应该是body 不是buffer吧;
还有函数体中logger::error这行是不是不要;
我是 ...

Logger是方便调试,buffer和body是两个不同的变量,buffer是返回体的片段数据,把片段逐个插入body再返回整个body
BonnieRan 发表于 2023-11-2 18:07
hujiayucc 发表于 2023-11-2 17:35
Logger是方便调试,buffer和body是两个不同的变量,buffer是返回体的片段数据,把片段逐个插入body再返回 ...

明白了,感谢感谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 21:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表