文章目录
- 背景
- 基础知识
- 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;}
}