分析源码学习c++(srs中http客户端)

文章目录

  • 背景
  • 基础知识
    • c++标准库
    • 虚函数
      • 虚函数使用方法
    • 虚析构函数
  • HTTP客户端
    • 使用方法
    • TCP传输层分析
      • 使用方法
      • 结构分析
      • 连接函数
      • 读写函数
    • 协议层分析
      • 初始化函数
      • 发送请求
      • 响应数据解析

背景

通过阅读源码,编写分析笔记来学习C++是一种非常有效且深入的方法,能帮助理解C++语言的底层机制、编程范式、设计模式以及常用实践。

基础知识

c++标准库

C++标准模板库(STL,Standard Template Library)是C++标准库的一个重要组成部分,提供了一套功能强大的模板类和函数,用于实现各种常用的数据结构和算法

在 C++ 中,使用标准库容器(如 std::vector)时,不需要手动释放内存 ,标准库容器内部已经实现了智能内存管理机制。比如创建一个
std::vector 对象时,它会自动管理分配的内存,并在对象不再使用时自动释放内存。

常用STL包括

  • 容器
    vector(向量)、list(链表)、deque(双端队列)、queue(队列)、stack(栈)、set(集合)、map(映射表)
  • 算法
    排序、查找、替换等操作
  • 迭代器
    封装了用于遍历容器元素的指针的类模板
  • 适配器
    容器适配器是对其他容器的封装,提供特定的接口以适配特定的使用场景。常见的适配器包括stack、queue和priority_queue等,它们通过封装其他容器(如deque或list)来实现栈、队列和优先队列等功能

编译命令

g++ -std=c++11 test-std.cpp -o test

输出
在这里插入图片描述

测试代码

#include <iostream>
#include <vector>
#include <map> 
#include <string>
#include <set> 
int main() {// 创建一个空的整数向量std::vector<int> myVector;// 添加元素到向量中myVector.push_back(9);myVector.push_back(7);myVector.push_back(2);myVector.push_back(5);// 访问向量中的元素并输出std::cout << "Elements in the vector: ";for (int element : myVector) {std::cout << element << " ";}std::cout << std::endl;// 访问向量中的第一个元素并输出std::cout << "First element: " << myVector[0] << std::endl;// 访问向量中的第二个元素并输出std::cout << "Second element: " << myVector.at(1) << std::endl;// 获取向量的大小并输出std::cout << "Size of the vector: " << myVector.size() << std::endl;// 删除向量中的第三个元素myVector.erase(myVector.begin() + 2);// 输出删除元素后的向量std::cout << "Elements in the vector after erasing: ";for (int element : myVector) {std::cout << element << " ";}std::cout << std::endl;// 清空向量并输出myVector.clear();std::cout << "Size of the vector after clearing: " << myVector.size() << std::endl;// 创建一个map,键为string类型,值为int类型  std::map<std::string, int> ageMap;  // 向map中插入元素   ageMap["zhao"] = 30;  ageMap["qian"] = 25;  ageMap["sun"] = 35;// 遍历输出for (const auto& pair : ageMap) {  std::cout << pair.first << ": " << pair.second << std::endl;  }   // 查找并修改map中的元素  if (ageMap.find("sun") != ageMap.end()) {  ageMap["sun"] = 26; // Bob的年龄增加1岁}std::cout << "sun age: " << ageMap["sun"] << std::endl;std::cout << "li age: " << ageMap["li"] << std::endl;std::set<std::string> names; names.insert("li");  names.insert("zhou");names.insert("wu");names.insert("wu");for (const auto& name : names) {  std::cout << name << std::endl;  }  names.erase("zhou"); if (names.find("zhou") != names.end()) {  std::cout << "Found zhou in the set!" << std::endl;  }else {std::cout << "NOT Found zhou in the set!" << std::endl;}  return 0;
}

虚函数

子类可以重写(Override)父类的方法,为什么还需要虚函数?
需要理解静态绑定(在编译时确定调用哪个函数)和动态绑定(运行时)
如果你只是简单地重写一个基类方法,但没有将该方法声明为虚函数,那么通过基类指针或引用调用该方法时,将只会调用基类的版本(静态绑定)。这意味着即使你有一个派生类对象,并且该对象重写了该方法,通过基类指针或引用调用时也不会调用到派生类的版本

  • 虚函数允许在派生类中重写基类中的函数,并且在运行时根据对象的实际类型来决定调用哪个函数版本
  • 当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间

虚函数使用方法

在基类中声明虚函数时,需要使用关键字 virtual。

class Base {
public:virtual void display() {std::cout << "Base display" << std::endl;}
};

派生类重写虚函数 virtual 关键字 可加可不加

class Derived : public Base {
public:void display() override {std::cout << "Derived display" << std::endl;}
};

通过基类指针或引用调用虚函数

void callDisplay(Base* base) {base->display();  // 调用虚函数
}
int main() {Base* basePtr = new Derived();callDisplay(basePtr);  // 调用 Derived::display()Base& baseRef = *basePtr;baseRef.display();  // 调用 Derived::display()delete basePtr;  // 释放内存return 0;
}

虚析构函数

虚析构函数确保在通过基类指针删除派生类对象时,能够正确调用派生类的析构函数
虚析构函数是为了避免内存泄露,当子类中会有指针成员变量时,通过父类指针操作。很容易发生内存泄漏。此时虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的。

#include <iostream>
#include <string>
class Base {
public:virtual ~Base() {} ; // 虚析构函数virtual   void  DoSomething() { std::cout  <<   " 这里是基类! "   <<   std::endl; };
};class Derived : public Base {
public:~Derived() {std::cout << "派生类 destructor" << std::endl;}void  DoSomething() {  std::cout  <<   " 这里是派生类! "   <<   std::endl; };
};int main() {Base* basePtr = new Derived();basePtr->DoSomething();delete basePtr;  // 调用 Derived 的析构函数return 0;
}

输出

 这里是派生类! 
派生类 destructor

如果把类Base 析构函数前的virtual去掉,那输出结果就是下面

这里是派生类! 

派生类的析构函数没有被调用

HTTP客户端

以srs中的http客户端为例进行分析

使用方法

   SrsHttpClient hc;hc.initialize("127.0.0.1", 80, 9000);hc.get("/api/v1/version", "Hello world!", msg);
  • 使用例子
SrsHttpClient client;
client.initialize("http", "127.0.0.1", 8080, 1000000LL)
ISrsHttpMessage* res = NULL;
SrsAutoFree(ISrsHttpMessage, res);
client.get("/api/v1", "", &res) // 发送请求获取数据 
ISrsHttpResponseReader* br = res->body_reader(); // 读取body中内容ssize_t nn = 0; char buf[1024];br->read(buf, sizeof(buf), &nn)//数据存入buf

其中 SrsHttpClient 提供 initialize初始化 get post set_header设置头 、设置读超时 等常用方法 ,还提供kbps_sample统计方法

class SrsHttpClient{std::string schema_; // 协议类型 http 或者https std::string host;   //服务器 ip 域名int port;           //服务器端口SrsTcpClient* transport; // 提供 连接 读写方法 SrsHttpParser* parser;// http协议解析 std::map<std::string, std::string> headers;SrsNetworkKbps* kbps;
}

TCP传输层分析

SrsTcpClient 分析

使用方法

SrsTcpClient client("127.0.0.1", 1935, 9 * SRS_UTIME_SECONDS);
client.connect();
client.write("Hello world!", 12, NULL);
client.read(buf, 4096, NULL);

结构分析

class SrsTcpClient : public ISrsProtocolReadWriter // 读写接口ISrsProtocolReadWriter 中的 读接口ISrsProtocolReader(封装 IReader+统计 IStatistic)

构造函数

 SrsTcpClient::SrsTcpClient(string h, int p, srs_utime_t tm)
{stfd_ = NULL;io = new SrsStSocket();    //初始化io为NULLhost = h;port = p;timeout = tm;
}

连接函数

SrsTcpClient::connect()

srs_netfd_t stfd = NULL;// srs_netfd_t  == st_netfd_t 
srs_tcp_connect(host, port, timeout, &stfd) //调用st_connect  { 流程 1 设置addrinfo  2 getaddrinfo 解析域名 3 生成socket 4 socket转 stfd  5 st_connect}
io = new SrsStSocket(stfd); //赋值 
stfd_ = stfd;  //赋值 

SrsStSocket 封装 stfd_ 超时时间 读写字节数 read write方法

读写函数

调用的是 SrsStSocket::read(void* buf, size_t size, ssize_t* nread)
函数中根据是否设置超时时间进行不同读取

nb_read = st_read((st_netfd_t)stfd_, buf, size, ST_UTIME_NO_TIMEOUT);
或者
nb_read = st_read((st_netfd_t)stfd_, buf, size, rtm);rbytes += nb_read;

错误处理

 if (nb_read <= 0) {if (nb_read < 0 && errno == ETIME) {// 超时没有读到数据 return srs_error_new(ERROR_SOCKET_TIMEOUT, "timeout %d ms", srsu2msi(rtm));}        if (nb_read == 0) { // 连接异常 或者读取结束errno = ECONNRESET;}return srs_error_new(ERROR_SOCKET_READ, "read");}

写类似 调用
SrsStSocket::write(void* buf, size_t size, ssize_t* nwrite)方法
最终调用st库的

 st_write((st_netfd_t)stfd_, buf, size, stm);

除了支持 write 还有writev方法 iovec结构

协议层分析

初始化函数

srs_error_t SrsHttpClient::initialize(string schema, string h, int p, srs_utime_t tm)
{parser = new SrsHttpParser();parser->initialize(HTTP_RESPONSE)//设置默认头 host uaheaders["Host"] = ep;……
}

发送请求

返回结果存放在ppmsg 中

srs_error_t SrsHttpClient::get(string path, string req, ISrsHttpMessage** ppmsg) 
{设置请求头长度 req的长度调用 SrsHttpClient::connect(){ 	1 transport = new SrsTcpClient   2  transport->connect() 3  kbps->set_io(transport, transport);}按照请求协议拼接字符串GET %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s//调用 SrsHttpClient::writer() 发送  writer 判断是http 还是https 链路writer()->write((void*)data.c_str(), data.length(), NULL)ISrsHttpMessage* msg = NULL;parser->parse_message(reader(), &msg)*ppmsg = msg;return 
}

post 函数类似

POST %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %d\r\n\r\n%s

响应数据解析

SrsHttpParser::parse_message(ISrsReader* reader, ISrsHttpMessage** ppmsg)
{state = SrsHttpParseStateInit;//  初始化状态 http_parser_init(&parser, type_); parse_message_imp(reader)SrsHttpMessage* msg = new SrsHttpMessage(reader, buffer);返回msg
}
parse_message_imp
srs_error_t SrsHttpParser::parse_message_imp(ISrsReader* reader)
{ 循环从reader中读取数据到buffer  直到全部读取完成http_parser_execute(&parser, &settings, buffer->bytes(), buffer->size());buffer->read_slice(consumed);if (state >= SrsHttpParseStateHeaderComplete) { // 判断状态break;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java导入包com.alibaba.fastjson2.JSONObject

com.alibaba.fastjson2.JSONObject 是阿里巴巴的 fastjson 库中的一个类&#xff0c;用于处理 JSON 数据。 这里提供一些常见的使用示例和可能的问题解决方法。 解决方案和示例代码: 创建 JSONObject 对象: import com.alibaba.fastjson2.JSONObject;JSONObject jsonObject …

Nginx: 使用KeepAlived配置实现虚IP在多服务器节点漂移及Nginx高可用原理

使用KeepAlived配置实现虚IP在多服务器节点漂移 1 &#xff09;环境准备 2台 linux , 一主一备 节点1&#xff1a;192.168.184.30 CentOS 7 Master节点2&#xff1a;192.168.184.40 CentOS 7 BackupVIP 192.168.184.50 安装 KeepAlived, $ yum install keepalived 注意&#x…

Jmeter、Python、Postman测试工具对比

一、JMeter 总结&#xff1a;适合对代码不敏感的使用人员&#xff0c;不会代码也可以完成接口自动化&#xff0c;设计框架。适合紧急迭代的项目。 JMeter接口测试的优势 小巧轻量级&#xff0c;并且开源免费&#xff0c;社区接受度高&#xff0c;比较容易入门支持多协议&…

MCU官方IDE软件安装及学习教程集合 — STM32CubeIDE(STM32)

简介 各MCU厂商为保证产品的市场地位以及用户体验&#xff0c;不断的完善自己的产品配套&#xff0c;搭建自己的开发生态&#xff0c;像国外ST公司&#xff0c;国内的GD&#xff08;兆易创新&#xff09;&#xff0c;AT&#xff08;雅特力&#xff09;等等。目前就开发生态而言…

音频原始数据PCM

PCM全称是脉冲编码调制数据。PCM数据是未经过压的音频数据&#xff0c;它由模拟信号信号经过采样、编码等步骤抓换成的数字信号。 一、音频基础知识讲解 1.1频率 声音是由震动产生的&#xff0c;所以声音是有频率的&#xff0c;人类可以听到的声音频率大概在20HZ~20KHZ 1.2振…

Windows通过网线连接开发板共享网络

Windows端 打开更开适配器选项右键WLAN–属性–共享 右键以太网–属性–Internet协议版本4(TCP/IPv4) 记住IP地址 开发板端 查看网卡 ifconfig设置IP在同一网段 ifconfig eth0 192.168.137.2 netmask 255.255.255.0设置网关 route add default gw 192.168.137.1配置DNS su…

minio最新源码编译(处理安全扫描中跨域访问、.js.map等不安全问题) 版本:RELEASE.2024-06-26T01-06-18Z

编译前注意事项 编译基于tag为RELEASE.2024-06-26T01-06-18Z的版本处理安全扫描问题。如&#xff1a;敏感信息泄露、.js.map、跨域访问问题需要准备两个工程&#xff0c;前端工程console和minio工程&#xff0c; 目录结构处理: gowork/ │ └── src/├── github.com├── …

华为 HCIP-Datacom H12-821 题库 (6)

有需要题库的可以看主页置顶 V群仅进行学习交流 1.转发表中 FLAG 字段中B 的含义是&#xff1f; A、可用路由 B、静态路由 C、黑洞路由 D、网关路由 答案&#xff1a;C 解析&#xff1a; 可用路由用U 表示&#xff0c;静态路由用 S 表示&#xff0c;黑洞路由用 B 表示&#x…

原油市场“闪崩”,国际油价单日下跌超4%!

KlipC报道&#xff1a;当地时间9月3日周二&#xff0c;延续早些时候的跌势&#xff0c;国际油价暴跌。WTI原油收跌近4.4%&#xff0c;逼近70美元大关&#xff1b;布伦特原油跌破75美元&#xff0c;跌幅4.86%&#xff0c;均抹去今年所有涨幅。 据悉&#xff0c;受到威胁后逃离了…

系统编程-数据库

数据库 目录 数据库 引入 1、先安装数据库 2、数据库设置密码 3、数据库的进入和退出(前提 你的密码更改过了) 数据库的基本操作 1、显示所有的数据库 2、创建数据库 3、删除数据库 4、选择数据库 在数据库中对表进行操作 1、查看当前数据库中的表 2、在数据库中…

如何在算家云搭建Qwen2(智能对话)

一、Qwen2简介 Qwen2 是由阿里云通义千问团队研发的新一代大型语言模型系列&#xff0c;它在多个方面实现了技术的飞跃和性能的显著提升。以下是对 Qwen2 的详细介绍&#xff1a; GitHub - QwenLM/Qwen2: Qwen2 is the large language model series developed by Qwen team, …

GD32 Flash读写与存储数据操作,亲测可用

这里写自定义目录标题 联系作者请加一、GD32 Flash特性二、Flash读写操作1. Flash读取操作2. Flash写入操作三、注意事项四、其他方法联系作者请加 如有技术问题及项目需求请加作者微信! 一、GD32 Flash特性 存储空间划分:GD32的Flash存储空间通常分为主存储块和信息块。主存…

SpringBoot学习(5)(springboot整合mybatis)

目录 1、整合mybatis基本介绍 &#xff08;1&#xff09;spring整合mybatis &#xff08;2&#xff09;springboot整合mybatis 2、案例&#xff08;根据用户id&#xff0c;返回用户所有信息&#xff09; &#xff08;1&#xff09;新建数据库连接、数据库、表。插入值 &a…

探索MongoDB的Python之钥:pymongo的魔力

文章目录 探索MongoDB的Python之钥&#xff1a;pymongo的魔力背景&#xff1a;为什么选择pymongo&#xff1f;简介&#xff1a;pymongo是什么&#xff1f;安装&#xff1a;如何将pymongo纳入你的项目&#xff1f;基础用法&#xff1a;五个核心函数介绍1. 连接到MongoDB2. 选择数…

【Spring Boot 3】【Web】自定义过滤器

【Spring Boot 3】【Web】自定义过滤器 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花费…

【数据分享】2000-2022年我国省市县三级的逐日O3数据(免费获取\excel\shp格式)

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000-2022年的省市县三级的逐日PM2.5数据、2013-2022年的省市县三级的逐日CO数据、2013-2022年的省市县三级的逐日SO2数据、2008-2022年我国省市县三级的逐日NO2数据和2000-2022年我国省市县三级…

ubuntu20.04(wsl2)测试 arcface 人脸识别(计算特征向量)

1. 参考博客和代码、模型仓库&#xff1a; 1.1. 【C随记】collect2: error: ld returned 1 exit status错误分析与解决 1.2. Visual Studio 2022新建 cmake 工程测试 tensorRT 自带样例 sampleOnnxMNIST 1.3.报错&#xff1a;ModuleNotFoundError: No module named ‘ten…

基于tesseract实现文档OCR识别

导入环境 导入必要的库 numpy: 用于处理数值计算。 argparse: 用于处理命令行参数。 cv2: OpenCV库&#xff0c;用于图像处理。 import numpy as np import argparse import cv2设置命令行参数 ap argparse.ArgumentParser() ap.add_argument("-i", "--imag…

视频集中存储智能边缘计算网关软硬一体机智能边缘计算网关应用场景

在信息化飞速发展的今天&#xff0c;数据处理的速度和效率直接影响到各行各业的运作和发展。传统的云计算模式虽然强大&#xff0c;但在面对实时性和带宽要求越来越高的应用场景时&#xff0c;往往显得力不从心。此时&#xff0c;智能边缘计算网关的出现&#xff0c;为我们带来…

长效静态代理IP推荐:天启代理IP的优势与应用

在如今这个互联网的时代&#xff0c;代理IP已成为许多网络活动的必备工具。相比动态代理IP&#xff0c;长效静态代理IP以其稳定性和长时间有效性&#xff0c;成为了许多用户的首选。今天&#xff0c;我们将深入探讨长效静态代理IP的优势&#xff0c;并重点推荐天启代理IP。 什…