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

背景

通过阅读源码,编写分析笔记来学习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为NULL
    host = 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 ua
	headers["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;
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值