C++ | 字节抖音客户端一面
Http握手过程
1. 客户端问候(Client Hello)
客户端向服务器发送一个“问候”消息,其中包含客户端支持的SSL/TLS版本、加密算法、压缩方法以及一个随机数。
version 版本号,https也有版本号哦TLS 1.0、TLS 1.1、TLS 1.2等等
random 随机数,用来以后计算对称加密key
最后加密数据用的主密钥,需要客户端和服务端一起协商出来。后面服务端的 Server Hello 阶段也会生成一个随机数。一同用来计算出主密钥。
Session ID 保存会话的标识
这个 Session ID 是可以重用的,具体看服务端资源和支持情况。如果要复用 Session ID, SSL 服务端需要维护连接的状态和上次握手成功留下的加密信息。如果是第一次访问该网址,会话 ID 尚未创建,客户端没记录,为 0。如果客户端保存了 Session ID 的信息,下次发起 SSL 请求的时候会带上。
Cipher Suites 说明客户端支持的加密方式,如上说明支持16种加密方式。
客户端可以支持的密码套件列表。这些套件会根据优先级排序。每一个套件代表一个密钥规格。以 “TLS” 开头,接着是密钥交换算法,然后用 “WITH” 连接加密算法和认证算法。一个加密套件有这么几个内容:密钥交换算法、加密算法(会带有支持的最高密钥位数)、认证算法还有加密方式。最终使用什么密码套件是服务端决定的。要什么密码套件会在 Server Hello 中进行反馈。
压缩算法
这里为 0,说明不支持压缩算法
2. 服务器问候(Server Hello)
服务器响应客户端的“问候”消息,选择一个双方都支持的SSL/TLS版本、加密算法和压缩方法,并生成一个随机数,同时将其发送给客户端。
random 随机数
和客户端的随机数两个随机数将参与主密钥(master key)的创建。
压缩方法
这里为 0,表示不使用压缩算法
3. 服务器证书(Server Certificate)
服务器将自己的数字证书发送给客户端。这个证书由受信任的第三方(如CA机构)签发,包含服务器的公钥和服务器身份信息。客户端使用内置的根证书列表来验证服务器证书的合法性和有效性。
版本
对应的就是 X.509 V3 标准
序列号
serialNumber,证书颁发者唯一序列号。
签名算法ID
这里指的是使用 SHA-256 进行摘要,RSA 进行加密的签名算法。
证书颁发者
issuer,就是颁发该证书的 CA 的信息。里面携带后该 CA 的唯一名称(DN,Distinguished Name),比如国家为 US(美国),组织机构为 DigiCert Inc.,名称为 GeoTrust CN RSA CA G1。后面我们需要从证书链找到该 CA 证书(具体方法:在其他证书的subject字段查找国家、组织机构、名称),去认证当前证书
有效期
validity,证书的起始时间和终止时间
对象公钥信息
subjectPublicKeyInfo。因为这是服务端证书,这个公钥后面将用于主密钥的交换过程,从中可以了解到这个公钥采用 RSA 加密
然后是证书颁发机构的签名信息:
签名算法
algorithmIdentifier,这里得出使用的还是 SHA-256 摘要加 RSA 加密的签名算法。这个就是认证该证书的 CA 证书使用的签名算法。
签名信息
encrypted,这个信息的内容,CA 证书对 SHA-256 对上面的数据部分进行摘要后,使用 RSA 的私钥加密获得。后面会用在该证书的认证过程,取出 CA 证书的公钥,解密签名信息,用同样的算法获取数据摘要,对比一下是否相同。
4. 服务器密钥交换(Server Key Exchange)
密钥交换阶段,这个步骤是可选步骤,对 Certificate 阶段的补充,只有在这几个场景存在:
协商采用了 RSA 加密,但是服务端证书没有提供 RSA 公钥。
协商采用了 DH(EC Diffie-Hellman) 加密,但是服务端证书没有提供 DH 参数。
协商采用 fortezza_kea 加密,但是服务端证书没有提供参数。
5. 服务器完成(Server Hello Done)
服务器表示问候阶段已经完成,等待客户端的响应。
6. 客户端密钥交换(Client Key Exchange)
这里,客户端不直接生成加密密钥,而是通过之前客户端和服务端生成的随机数又再生成一个随机数,使用前面协商好的用 EC Diffie-Hellman 算法进行加密传输给服务端。这个值又被称为 “premaster secret“。
服务端收到这个报文后,会使用自己的私钥解开这个随机数。在这个阶段过后,服务端和客户端都有三个随机数:客户端随机数、服务端随机数和预备主密钥。在服务端收到了 Client Key Exchange 消息后,两端都按照相应的算法生成了主密钥,加密密钥交换完成。交换完了,因为主密钥是两个端按照约定好的算法产生的,如何保证这个主密钥是正确的?这时候会进入下一个阶段。客户端和服务端会对握手信息使用 SHA 做个摘要,用 AES 加密算法和主密钥加密,传递给对方验证。
7. 客户端完成(Client Finished)
客户端通知服务端,后续的报文将会被加密
8. 服务器完成(Server Finished)
服务器也发送一条加密消息,表示它已经完成了密钥交换和会话密钥的生成。
9. 加密通信开始
此时,客户端和服务器都已经生成了相同的会话密钥,接下来的通信将使用会话密钥进行对称加密,以确保数据传输的保密性和完整性。
Https能否防住DNS攻击
DNS攻击是指针对DNS系统的各种攻击手段,包括但不限于:
- DNS缓存污染(DNS Cache Poisoning):攻击者向DNS缓存服务器注入虚假的DNS记录,使用户访问错误的IP地址。
- DNS劫持(DNS Hijacking):攻击者通过篡改DNS解析过程,将用户引导到恶意网站。
- 中间人攻击(Man-in-the-Middle Attack):攻击者在客户端和DNS服务器之间拦截并篡改DNS请求和响应。
HTTPS的保护范围
HTTPS通过加密传输数据,确保数据在客户端和服务器之间传输时不会被窃听或篡改。具体来说,它可以保护以下内容:
- 数据加密:防止数据在传输过程中被窃听。
- 数据完整性:防止数据在传输过程中被篡改。
- 身份验证:通过数字证书验证服务器的身份,确保客户端连接到的是合法服务器。
虽然HTTPS能保护数据传输的安全,但在DNS解析过程中,HTTPS并不能直接防止DNS攻击。因为在建立HTTPS连接之前,客户端需要通过DNS解析来获取服务器的IP地址,如果DNS解析过程被攻击者篡改,用户可能会连接到错误的服务器。
什么是拥塞控制
主机A 主机C
------路由器A------> 路由器B------路由器C--------->
主机B 主机D
前提条件:有限的链路缓存
链路容量是 R
当分组的到达路由器C被丢弃的时候,那么上游路由器A-C之间的路由器的传输容量都被浪费掉了。
如果网络发生拥塞,将会导致我们会重传不需要重传的分组,导致恶性循环,或者由于丢失导致上游路由器带宽的浪费,所有拥塞控制的目的就是:
最大限度的利用带宽,传输有效的数据。
拥塞控制算法需要解决以下三个问题
TCP如何限制数据的发送速率(拥塞窗口机制cwnd);
TCP如何检测网络中是否拥塞(轻微拥塞,拥塞);
TCP采用什么算法来调整速率(什么时候调整,调整多少,轻微拥塞如何调整,拥塞如何调整)。
拥塞控制检测的方法
端到端的拥塞控制:
在这种拥塞控制方法中,由发送端自己来判断是否拥塞,然后调整传输速率;
对于TCP发送方来说,没有一个精确的方法知晓路由器的中间状态,没有一个明确的信号告知拥塞已经发生,典型的TCP推断是否拥塞,通常看是否有丢包的情况发生。
为什么会丢包?
中间路由器缓存溢出导致丢包。
段所在的分组,分组所在的帧出现bit反转,出错导致校验未通过丢包。
针对以上两种情况需要澄清一个问题就是,无线网以前出错导致的丢包远远小于溢出导致的丢包,可以忽略不记。
拥塞控制的主要方法
拥塞控制机制通常分为端到端控制和网络辅助控制两大类。
端到端控制方法
- TCP 拥塞控制:
- 慢启动(Slow Start):发送方开始时发送数据的速率较低,然后逐步增加发送速率,直到检测到网络拥塞。
- 拥塞避免(Congestion Avoidance):在检测到拥塞信号(如数据包丢失或延迟增加)后,发送方会降低发送速率,然后再缓慢增加,避免再次发生拥塞。
- 快速重传(Fast Retransmit)和快速恢复(Fast Recovery):在检测到数据包丢失时,发送方会立即重传丢失的数据包,并进入快速恢复阶段,在此阶段会减少发送速率以防止拥塞恶化。
- 流量控制:确保发送方不会向接收方发送超过其处理能力的数据量。流量控制通常通过滑动窗口协议来实现。
网络辅助控制方法
- 主动队列管理(Active Queue Management, AQM):
- RED(Random Early Detection):在队列达到某个阈值时,随机丢弃部分数据包,以提醒发送方减缓发送速率。
- ECN(Explicit Congestion Notification):通过在数据包头部标记拥塞通知,让发送方知道网络中存在拥塞,从而调整发送速率。
- 流量整形(Traffic Shaping):控制数据发送的时间和速率,使得数据流平稳,避免突发流量导致拥塞。
死锁的条件
1. 互斥条件(Mutual Exclusion)
资源在某个时刻只能被一个进程占用。如果某个资源已经被一个进程占用了,其他进程必须等待,直到该资源被释放。这意味着资源不能被多个进程共享。
2. 请求和保持条件(Hold and Wait)
一个进程已经持有至少一个资源,同时又请求新的资源,而新的资源已经被其他进程占有,此时该进程会被阻塞,但它对自己已持有的资源保持不放。
3. 不可剥夺条件(No Preemption)
资源不能被强行从一个进程中夺走,只有该进程自己才能在完成任务后自愿释放所占有的资源。
4. 环路等待条件(Circular Wait)
在系统中存在一个进程集合{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,……,Pn等待P1占有的资源,从而形成一个环状的等待关系。
static
变量存储在哪里
static
变量存储在全局数据区(Data Segment),具体来说,它们位于以下两种区域之一:
- 静态/全局数据区(Data Segment):这是程序的数据段,专门存储初始化的全局变量和静态变量。这部分内存由操作系统在程序加载时分配,并在程序运行期间一直存在。
- BSS段(Block Started by Symbol Segment):这部分内存用于存储未初始化的全局变量和静态变量。虽然这部分变量在编译过程中没有显式初始化,但在程序开始执行时会被操作系统初始化为0。
进程和线程的区别
本质区别:
进程是资源分配的基本单位,线程是CPU调度的基本单位
- 定义和结构:
- 进程:进程是程序的一个实例,它在其自己的地址空间中运行。每个进程至少包含一个线程(主线程),并拥有自己的虚拟内存、系统资源和独立的执行环境。进程之间互相隔离,一个进程的崩溃通常不会影响到其他进程。
- 线程:线程是进程中的执行单元。一个进程可以包含多个线程,它们共享父进程的地址空间和资源,如内存和文件句柄等。线程主要用于实现任务的并行执行。
- 资源共享:
- 进程:进程之间不共享内存或资源,除非通过进程间通信(IPC)机制显式共享,如套接字、信号量、共享内存等。
- 线程:同一进程内的线程共享内存和资源,这使得线程间的数据交换和通信更为容易和快速,但也需要注意同步和互斥问题,以避免竞态条件和死锁。
- 开销和性能:
- 进程:创建和管理进程的开销相对较大,因为每个进程需要独立的地址空间和系统资源。进程切换涉及更多的时间和资源,如保存和加载不同进程的上下文。
- 线程:线程的创建和切换开销较小,因为它们共享相同的环境和资源。线程间的切换只需要较少的资源重新配置。
- 通信方式:
- 进程:进程间通信需要特定的机制,如管道、消息队列、共享内存等,这些机制通常涉及更复杂的设置和管理。
- 线程:由于线程共享同一内存空间,它们可以直接通过读写同一内存区域来进行通信,但这要求程序正确地处理同步问题,以防数据不一致。
继承是否会破坏封装
个人理解。一般而言,继承是不会破坏封装的。继承的本意是为了增强代码的复用性和组织性。只要使用正常的访问控制修饰符public
, private
和protected
就不会破坏封装。
了解的设计模式
LRU算法:设定Cache大小 + 过期时间
见LeetCode 146。相比于原题多了设定过期时间,逻辑是一样的,只需要掌握对应语言的设定时间戳的API,即可。此题在大厂秋招的话难度还可能继续进阶,比如实现线程安全。此时需要加锁,但是直接对哈希表加锁的话粒度太大,所以需要分桶。然后继续加难度,如何实现高并发,此时需要异步,对应到C++就是std::async
,可以直接调用API或者手写(太花时间且难度有点高了)。
#include <iostream>
#include <unordered_map>
#include <chrono>
#include <thread>
class Node {
public:
int key, val;
std::chrono::time_point<std::chrono::steady_clock> timestamp;
Node *prev = nullptr, *next = nullptr;
Node (int k = 0, int v = 0): key(k), val(v), timestamp(std::chrono::steady_clock::now()){ }
};
class LRUCache {
public:
LRUCache(int capacity, int ttl): capacity(capacity), ttl(ttl) {
dummy = new Node();
dummy->prev = dummy;
dummy->next = dummy;
}
int get(int key) {
auto node = getNode(key);
if (node == nullptr) {
return -1;
}
return node->val;
}
void put(int key, int value) {
auto node = getNode(key);
if (node != nullptr) {
node->val = value;
node->timestamp = std::chrono::steady_clock::now();
return;
}
node = new Node(key, value);
keyToNode[key] = node;
pushToFront(node);
if (keyToNode.size() > this->capacity) {
auto last = dummy->prev;
keyToNode.erase(last->key);
remove(last);
delete last;
}
return;
}
private:
int capacity;
std::chrono::seconds ttl;
std::unordered_map<int, Node*> keyToNode;
Node *dummy;
void remove(Node *x) {
if (x != nullptr) {
x->prev->next = x->next;
x->next->prev = x->prev;
}
}
void pushToFront(Node *x) {
if (x != nullptr) {
x->prev = dummy;
x->next = dummy->next;
x->prev->next = x;
x->next->prev = x;
}
}
Node* getNode(int key) {
auto it = keyToNode.find(key);
if (it == keyToNode.end()) {
return nullptr;
}
auto node = it->second;
if (std::chrono::steady_clock::now() - node->timestamp > ttl) {
remove(node);
keyToNode.erase(it);
delete node;
return nullptr;
}
node->timestamp = std::chrono::steady_clock::now();
remove(node);
pushToFront(node);
return node;
}
};
int main() {
using namespace std;
LRUCache* cache = new LRUCache(2, 5);
cout << "Test Case 1:" << endl;
cache->put(1, 1);
cache->put(2, 2);
cout << "Get 1: " << cache->get(1) << " (Expected: 1)" << endl;
cache->put(3, 3);
cout << "Get 2: " << cache->get(2) << " (Expected: -1)" << endl;
cache->put(4, 4);
cout << "Get 1: " << cache->get(1) << " (Expected: -1)" << endl;
cout << "Get 3: " << cache->get(3) << " (Expected: 3)" << endl;
cout << "Get 4: " << cache->get(4) << " (Expected: 4)" << endl;
cout << "Test Case 2:" << endl;
cache->put(5, 5);
cout << "Get 3: " << cache->get(3) << " (Expected: -1)" << endl;
cout << "Get 4: " << cache->get(4) << " (Expected: 4)" << endl;
cout << "Get 5: " << cache->get(5) << " (Expected: 5)" << endl;
std::this_thread::sleep_for(std::chrono::seconds(6));
cout << "Get 4 after TTL expiration: " << cache->get(4) << " (Expected: -1)" << endl;
delete cache;
return 0;
}
最后给大家推荐一个LinuxC/C++高级架构系统教程的学习资源与课程,可以帮助你有方向、更细致地学习C/C++后端开发,具体内容请见 https://xxetb.xetslk.com/s/1o04uB