侧边栏壁纸
博主头像
hujiayucc

学了这么久面向对象却没有对象,后来以为是因为缺少了过程,等学完面向过程早已经物人非,有没有过程并不重要,是你的终归是你的

  • 累计撰写 9 篇文章
  • 累计收到 6 条评论

Linux C++ HTTP(S) && ProgressBar && TCP/IP

2023-10-31 / 2 评论 / 1413 阅读
温馨提示:
本文最后更新于 2023-10-30,已超过半年没有更新,若内容或图片失效,请留言反馈。

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

也没啥好说的,那就简单写两个小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;
}


写了大半天,就先到这里吧,下次再讲,晚安

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

蓝奏云:点击下载

评论一下?

OωO
取消
    1. 头像
      Josephomips
      板凳
      <a href=https://moskva.restavratsiya-vann.com>修复浴缸</a>
      回复
    1. 头像
      MariskaBaf0995
      沙发
      Xevil5.0自动解决大多数类型的captchas, </b></font>
      包括这类验证码: ReCaptcha v.2, ReCaptcha v.3, Hotmail (Microsoft), Google captcha, SolveMedia, BitcoinFaucet, Steam, Amazon, Twitter, Microsoft, Twitch, Outlook, +12000
      + hCaptcha, ArkoseLabs FunCaptcha, ReCaptcha Enterprize 支持新的Xevil6.0!

      1.) 快速,轻松
      XEvil是世界上最快的验证码杀手。 它没有解决限制,没有线程数限制
      你甚至可以每天解决1.000.000.000captchas,它将花费0(零)美元! 只需购买许可证59美元和所有!

      2.) 几个Api支持
      XEvil支持超过6种不同的全球知名API: 2Captcha, anti-captcha (antigate), rucaptcha.com, DeathByCaptcha, etc.
      只要通过HTTP请求发送您的验证码,因为您可以发送到任何一个服务-和XEvil将解决您的验证码!
      因此,XEvil与数百个SEO/SMM/密码恢复/解析/发布/点击/加密货币/等应用程序兼容。

      3.) 有用的支持和手册
      购买后,您可以访问私人技术。支持论坛,维基,Skype/电报在线支持
      开发人员将免费且非常快速地训练XEvil到您的验证码类型-只需向他们发送示例

      4.) 如何免费试用XEvil完整版?
      - 尝试在Google中搜索 "Home of XEvil"
      - 您将找到Xevil用户打开端口80的Ip(点击任何IP以确保)
      - 尝试通过2captcha API ino其中一个Ip发送您的captcha
      - 如果你有坏的密钥错误,只需tru另一个IP
      - 享受吧! :)
      - (它不适用于hCaptcha!)

      警告:免费XEvil演示不支持ReCaptcha,hCaptcha和大多数其他类型的captcha!

      http://XEvil.Net/
      回复