update: content update

This commit is contained in:
Unix_Kernel
2024-11-13 14:47:32 +08:00
parent dae10db9d4
commit aca020701b
14 changed files with 285 additions and 78 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -537,7 +537,7 @@ QA优先级反转是什么
举个例子:优先级:线程 A < 线程 B < 线程 C线程A、C 都会使用共享资源 R该资源由信号量控制进行互斥访问。 举个例子:优先级:线程 A < 线程 B < 线程 C线程A、C 都会使用共享资源 R该资源由信号量控制进行互斥访问。
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/Thread_priority.jpg) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/Thread_priority.jpg)
线程 A 在 T1 时刻使用资源 R 并拿到锁。开始执行线程内的逻辑。 线程 A 在 T1 时刻使用资源 R 并拿到锁。开始执行线程内的逻辑。
@@ -1707,6 +1707,58 @@ dispatch_barrier_async(self.queue, ^{
#### 栅栏函数拦不住全局队列
Demo
```objective-c
- (void)testBarrierWithGlobalQueue {
NSLog(@"%s", __func__);
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 100; i++) {
dispatch_async(queue, ^() {
NSLog(@"%d", i);
});
}
dispatch_barrier_async(queue, ^() {
NSLog(@"100");
});
dispatch_async(queue, ^() {
NSLog(@"101");
});
NSLog(@"%s", __func__);
}
```
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/GCDBarrierCannotHoldGlobalQueue.png" style="zoom:30%" />
```objective-c
- (void)testBarrierWithCustomQueue {
NSLog(@"%s", __func__);
dispatch_queue_t queue = dispatch_queue_create(0, 0);
for (int i = 0; i < 100; i++) {
dispatch_async(queue, ^() {
NSLog(@"%d", i);
});
}
dispatch_barrier_async(queue, ^() {
NSLog(@"100");
});
dispatch_async(queue, ^() {
NSLog(@"101");
});
NSLog(@"%s", __func__);
}
```
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/GCDBarrierCanHoldCustomQueue.png" style="zoom:30%" />
结论:可以发现 GCD 的栅栏函数,拦不住全局队列,却可以拦住普通的队列。这是为什么?
全局队列的业务方不只是当前 App 进程,还有一些系统任务(全局并发队列中不仅有开发者的任务,还有系统的任务),如果我们用我们的任务去栏住系统的任务,可能会导致一些未知的错误。栅栏函数对全局并发队列无效,所以我们在开发的时候一定要注意
### dispatch_group_async ### dispatch_group_async
如何实现 A、B、C 三个任务并发执行完,再去执行任务 D ?假设需求是根据省市区下载 json然后根据 json 数据,选中地址 picker view。 如何实现 A、B、C 三个任务并发执行完,再去执行任务 D ?假设需求是根据省市区下载 json然后根据 json 数据,选中地址 picker view。

View File

@@ -182,7 +182,7 @@ NSLog(@"%p %p %@", obj, &obj, obj);
### 为什么有 Tagged Pointer ### 为什么有 Tagged Pointer
现状:一般,存放 NSNumber、NSDate 这类变量的时候本身占用的内存大小常常不需要8个字节。4字节带符号的证书可以达到2^31= 214748364899% 的情况都能满足了。因此为了更高效、更节省空间,用一个看似是指针的计数,来存储数据,且在 Runtime 侧判断了节省了消息机制那一套冗长的流程Tagged Pointer cover 一些小数据的场景cover 不了则申请堆内存。 现状:一般,存放 NSNumber、NSDate 这类变量的时候本身占用的内存大小常常不需要8个字节。4字节带符号的整数可以达到2^31= 214748364899% 的情况都能满足了。因此为了更高效、更节省空间,用一个看似是指针的计数,来存储数据,且在 Runtime 侧判断了节省了消息机制那一套冗长的流程Tagged Pointer cover 一些小数据的场景cover 不了则申请堆内存。
@@ -1505,7 +1505,7 @@ weak_table_t 结构如下:
struct weak_entry_t { struct weak_entry_t {
DisguisedPtr<objc_object> referent; // 被弱引用的对象 DisguisedPtr<objc_object> referent; // 被弱引用的对象
// 引用该对象的对象列表,联合。 引用个数小于4用 inline_referrers 数组。 个数大于4用动态数组 weak_referrer_t *referrers // 引用该对象的对象列表,联合。 引用个数小于4用 inline_referrers 数组。 个数大于4用动态数组 weak_referrer_t *referrers
union { union {
struct { struct {
weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组 weak_referrer_t *referrers; // 弱引用该对象的对象指针地址的hash数组
@@ -1551,10 +1551,32 @@ struct weak_entry_t {
#### 存 weak 对象 #### 存 weak 对象
声明一个 `__weak` 对象
```objective-c
{
id __weak obj = strongObj;
}
```
LLVM转换成对应的代码
```objective-c
id __attribute__((objc_ownership(none))) obj1 = strongObj;
```
相应的会调用
```objective-c
id obj ;
objc_initWeak(&obj,strongObj);
objc_destoryWeak(&obj);
```
上 Demo
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ObjcStoreWeakWhenUseWeak.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ObjcStoreWeakWhenUseWeak.png" style="zoom:30%" />
可以看到当一个 weak 指针被赋值的时候,底层调用了 `objc_initWeak`,跟踪查看 objc 源码 可以看到当一个 weak 指针被赋值的时候,底层调用了 `objc_initWeak`,跟踪查看 objc 源码
```c++ ```c++
@@ -2273,7 +2295,7 @@ weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
- object_remove_assocations(obj):去除该对象相关的关联属性(Category 添加的) - object_remove_assocations(obj):去除该对象相关的关联属性(Category 添加的)
- obj->clearDeallocating():清空引用技术表和弱引用表,将 weak 引用设置为 nil - obj->clearDeallocating():清空引用计数表和弱引用表,将 weak 引用设置为 nil
继续看看 object_cxxDestruct 方法内部细节。 继续看看 object_cxxDestruct 方法内部细节。
@@ -2574,7 +2596,7 @@ struct FinishARCDealloc : EHScopeStack::Cleanup {
#### LLVM + Runtime 共同协作的结果 #### LLVM + Runtime 共同协作的结果
LLVM 编译器前端 clang 在编译阶段,自动帮我们给对象加了 release、retain、autorelease 的代码(比如在一个大括号内的代码,生命的对象,在大括号将要结束的时候会自动加 `[person release] 之类的代码`)。 LLVM 编译器前端 clang 在编译阶段,自动帮我们给对象加了 release、retain、autorelease 的代码(比如在一个大括号内的代码,声明的对象,在大括号将要结束的时候会自动加 `[person release] 之类的代码`)。
ARC 中禁止手动调用 retain/release/retainCount/dealloc 方法。 ARC 中禁止手动调用 retain/release/retainCount/dealloc 方法。
@@ -4093,6 +4115,27 @@ IMP Caching 比其他方法快2倍。
## 典型的内存问题 ## 典型的内存问题
### 使用NSArray 保存weak对象会有什么问题
Foundation 中数组在元素被添加的时候(这里的数组 指平常使用的 NSArray 和 NSMutableArray )会强引用持有,就算使用 `__weak` 修饰也没有用,导致一些奇特的内存泄漏和循环引用问题。
`-(NSValue *)valueWithNonretainedObject:(nullable id)anObject;`
`NSPointerArray` 提供 `strongObjectsPointerArray` 和 `weakObjectsPointerArray`工厂weakObjectsPointerArray 就是我们需要的弱引用数组方法。
NSHashTable 和 NSMapTable
```
// 弱应用对象
NSMapTable *map = [NSMapTable weakToWeakObjectsMapTable];
[map setObject:dog forKey:@"first"];
// 弱应用对象
NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];
[hashTable addObject:dog];
```
### OC 中有没有不对内存进行强持有的集合类型? ### OC 中有没有不对内存进行强持有的集合类型?
`NSHashMap`、`NSMapTable` 都可以描述 key、value 的内存修饰。 `NSHashMap`、`NSMapTable` 都可以描述 key、value 的内存修饰。

View File

@@ -573,7 +573,7 @@ void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
} }
``` ```
`map_images_nolock` 会调用 `_read_images` 方法,用于初始化 map 后的 `image`,这里面干了很多的事情,像 load所 有的类、协议和 category著名的`+ load` 方法就是这一步调用的。如下: `map_images_nolock` 会调用 `_read_images` 方法,用于初始化 map 后的 `image`,这里面干了很多的事情,像 load所有的类、协议和 category著名的 `+ load` 方法就是这一步调用的。如下:
```c++ ```c++
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses) void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
@@ -1278,7 +1278,7 @@ Demo: 为 Person 类创建2个 Category分别存在同名方法 study
} }
``` ```
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryMethodOrderExplore.png" style="zoom:25%"> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OCCategoryMethodOrderExplore.png" style="zoom:25%">
可以看到 sayHi 方法存在多个,但是由于 Category 同名的方法在方法列表的前面,所以类自身的方法实现”被覆盖了“(根据 isa 查找方法实现的时候,优先查找到 Category 的方法实现,则停止查找了) 可以看到 sayHi 方法存在多个,但是由于 Category 同名的方法在方法列表的前面,所以类自身的方法实现”被覆盖了“(根据 isa 查找方法实现的时候,优先查找到 Category 的方法实现,则停止查找了)
@@ -1291,17 +1291,17 @@ Demo: 为 Person 类创建2个 Category分别存在同名方法 study
Demo: 为 Person 类创建2个 Category分别存在同名方法 study具有不同实现。探索编译顺序决定方法实现 Demo: 为 Person 类创建2个 Category分别存在同名方法 study具有不同实现。探索编译顺序决定方法实现
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryBuildOrderDemo1.png" style="zoom:25%"> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OCCategoryBuildOrderDemo1.png" style="zoom:25%">
2个对比实验 2个对比实验
让 `Person+Study` 参与后编译 让 `Person+Study` 参与后编译
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryBuildOrderDemo2.png" style="zoom:25%"> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OCCategoryBuildOrderDemo2.png" style="zoom:25%">
让 `Person+Learn` 参与后编译 让 `Person+Learn` 参与后编译
<img src="https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/OCCategoryBuildOrderDemo3.png" style="zoom:25%"> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/OCCategoryBuildOrderDemo3.png" style="zoom:25%">
@@ -1765,7 +1765,7 @@ static void call_class_loads(void)
if (PrintLoading) { if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging()); _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
} }
// 直接调用 +load 方法,而不是采用发消息的形式 // 方法指针。直接调用 +load 方法,而不是采用发消息的形式
(*load_method)(cls, @selector(load)); (*load_method)(cls, @selector(load));
} }
@@ -2160,7 +2160,7 @@ Person +load
查看分类在 Runtime 加载类信息时候的调用原理可以知道分类中的类方法、对象方法都会被加载原始类的前面去initialize 是类方法)如下图: 查看分类在 Runtime 加载类信息时候的调用原理可以知道分类中的类方法、对象方法都会被加载原始类的前面去initialize 是类方法)如下图:
![](https://github.com/FantasticLBP/knowledge-kit/raw/master/assets/runtime-categoryattachLists.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/runtime-categoryattachLists.png)
### 为什么给子类发消息,父类和子类的 +initialize 都会被调用?且父类的先调用 ### 为什么给子类发消息,父类和子类的 +initialize 都会被调用?且父类的先调用
@@ -2743,8 +2743,6 @@ NS_ASSUME_NONNULL_END
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) { void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy); _object_set_associative_reference(object, (void *)key, value, policy);
} }
``` ```
简化版 简化版

View File

@@ -68,3 +68,26 @@ NSLog(@"%@",[self class]);
## 手饰事件和点击事件的响应顺序
假如给某个 view 所在的父视图添加了手饰识别器。
**手势识别器的优先级**:如果你将 `UITapGestureRecognizer` 添加到了视图上UIKit 会首先尝试识别手势。如果视图上添加了多个手势识别器,它们的识别顺序将根据它们被添加到视图的顺序或者它们的 `delaysTouchesBegan``delaysTouchesEnded` 属性来决定。
想要子 view 响应事件而不是被根视图拦截,则需要给手势识别器添加代理,实现代理方法
```objective-c
UIGestureRecognizer *gesture;
gesture.delegate = self;
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
return NO;
}
```
如果 `UITapGestureRecognizer` 识别了一个手势,它可以通过设置 `cancelsTouchesInView` 属性为 `YES` 来取消视图上的触摸事件,这样点击事件就不会被进一步传递到视图控制器的 `touchesBegan` 或 `touchesEnded` 方法。

View File

@@ -28,7 +28,7 @@ _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(p_di
![显示器和 CPU、GPU 关系](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-02-02-screen_display_gpu.png) ![显示器和 CPU、GPU 关系](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2020-02-02-screen_display_gpu.png)
通常,屏幕上一张画面的显示是由 CPU、GPU 和显示器是按照上图的方式协同工作的。CPU 根据工程师写的代码计算好需要实的内容(比如视图创建、布局计算、图片解码、文本绘制等),然后把计算结果提交到 GPUGPU 负责图层合成、纹理渲染,随后 GPU 将渲染结果提交到帧缓冲区。随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过数模转换传递给显示器显示。 通常,屏幕上一张画面的显示是由 CPU、GPU 和显示器是按照上图的方式协同工作的。CPU 根据工程师写的代码计算好需要实的内容(比如视图创建、布局计算、图片解码、文本绘制等),然后把计算结果提交到 GPUGPU 负责图层合成、纹理渲染,随后 GPU 将渲染结果提交到帧缓冲区。随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过数模转换传递给显示器显示。
在帧缓冲区只有一个的情况下,帧缓冲区的读取和刷新都存在效率问题,为了解决效率问题,显示系统会引入 2 个缓冲区即双缓冲机制。在这种情况下GPU 会预先渲染好一帧放入帧缓冲区让视频控制器来读取当下一帧渲染好后GPU 直接把视频控制器的指针指向第二个缓冲区。提升了效率。 在帧缓冲区只有一个的情况下,帧缓冲区的读取和刷新都存在效率问题,为了解决效率问题,显示系统会引入 2 个缓冲区即双缓冲机制。在这种情况下GPU 会预先渲染好一帧放入帧缓冲区让视频控制器来读取当下一帧渲染好后GPU 直接把视频控制器的指针指向第二个缓冲区。提升了效率。
@@ -44,7 +44,7 @@ _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(p_di
设想一个显示器显示第一帧图像和第二帧图像的过程。首先在双缓冲区的情况下GPU 首先渲染好一帧图像存入到帧缓冲区,然后让视频控制器的指针直接直接这个缓冲区,显示第一帧图像。第一帧图像的内容显示完成后,视频控制器发送 V-Sync 信号GPU 收到 V-Sync 信号后渲染第二帧图像并将视频控制器的指针指向第二个帧缓冲区。 设想一个显示器显示第一帧图像和第二帧图像的过程。首先在双缓冲区的情况下GPU 首先渲染好一帧图像存入到帧缓冲区,然后让视频控制器的指针直接直接这个缓冲区,显示第一帧图像。第一帧图像的内容显示完成后,视频控制器发送 V-Sync 信号GPU 收到 V-Sync 信号后渲染第二帧图像并将视频控制器的指针指向第二个帧缓冲区。
**看上去第二帧图像是在等第一帧显示后的视频控制器发送 V-Sync 信号。是吗?真是这样的吗? 😭 想啥呢,当然不是。 🐷 不然双缓冲区就没有存在的意义了** **看上去第二帧图像是在等第一帧显示后的视频控制器发送 V-Sync 信号。是吗?真是这样的吗? 当然不是,不然双缓冲区就没有存在的意义了**
揭秘。请看下图 揭秘。请看下图
@@ -2131,7 +2131,7 @@ OC 语言:指针指向的内存对象已经被释放或回收了,但是指
#### 什么是僵尸对象? #### 什么是僵尸对象?
僵尸对象就是指一个 OC 对象释放后所占用的内存还没被写(重新分配给其他对象)前被称为僵尸对象。此时僵尸对象内存很不稳定,内存随时可能被系统分配给其他对象所使用。所以此时僵尸对象不应该访问和使用(调用对象的方法等) 僵尸对象就是指一个 OC 对象释放后所占用的内存还没被写(重新分配给其他对象)前被称为僵尸对象。此时僵尸对象内存很不稳定,内存随时可能被系统分配给其他对象所使用。所以此时僵尸对象不应该访问和使用(调用对象的方法等)
#### 为什么 OC 野指针 Crash 很多? #### 为什么 OC 野指针 Crash 很多?
@@ -7930,6 +7930,8 @@ runZoned<Future<Null>>(() async {
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUIXcode1@2x.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUIXcode1@2x.png)
本来常见的开发都会规避这些写法,没机会看到子线程操作 UI 的问题,但是 Weex 的业务代码,检测出存在子线程操作 UI 的问题,所以还是有必要增加这个能力的。
其实我们可以给 Xcode 打个 `Runtime Issue Breakpoint` type 选择 `Main Thread Checker` 在发生子线程操作 UI 的时候就会被系统检测到并触发断点,同时可以看到堆栈情况 其实我们可以给 Xcode 打个 `Runtime Issue Breakpoint` type 选择 `Main Thread Checker` 在发生子线程操作 UI 的时候就会被系统检测到并触发断点,同时可以看到堆栈情况
![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUISymbolBreakpoints@2x.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/2022-0204-SubThreadUISymbolBreakpoints@2x.png)
@@ -7956,7 +7958,7 @@ runZoned<Future<Null>>(() async {
对 [dlopen](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlopen.3.html)、[dlsym](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html) 陌生的小伙伴可以直接看 Apple 官方文档,这里不做展开。 对 [dlopen](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlopen.3.html)、[dlsym](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/dlsym.3.html) 陌生的小伙伴可以直接看 Apple 官方文档,这里不做展开。
具体可以参考这个 [saDemo](https://github.com/FantasticLBP/MainThreadChecker) 具体可以参考这个 [Demo](https://github.com/FantasticLBP/MainThreadChecker)
## 十一、页面渲染时长统计 ## 十一、页面渲染时长统计

View File

@@ -502,7 +502,7 @@ dispatch_queue_t group1 = dispatch_group_create();
下断点后可以看到 `dispatch_group_create` 是位于 `libdispatch.dylib` 库中的。 下断点后可以看到 `dispatch_group_create` 是位于 `libdispatch.dylib` 库中的。
<img src="./../assets/DispatchGroupTSourceLocation.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchGroupTSourceLocation.png" style="zoom:30%" />
打开 `libdispatch.dylib` 搜索 `dispatch_group_create`。可以看到源码实现。 打开 `libdispatch.dylib` 搜索 `dispatch_group_create`。可以看到源码实现。
@@ -526,7 +526,7 @@ _dispatch_group_create_with_count(uint32_t n)
`dispatch_group_create` 调用 `_dispatch_group_create_with_count`, `_dispatch_group_create_with_count` 调用 `_dispatch_object_alloc`有2个参数`DISPATCH_VTABLE(group)` 和 `sizeof(struct dispatch_group_s)` `dispatch_group_create` 调用 `_dispatch_group_create_with_count`, `_dispatch_group_create_with_count` 调用 `_dispatch_object_alloc`有2个参数`DISPATCH_VTABLE(group)` 和 `sizeof(struct dispatch_group_s)`
<img src="./../assets/DispatchGroupCreateSourceCode.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchGroupCreateSourceCode.png" style="zoom:30%" />
在汇编调试模式下,对其打印输出,对于 x86 架构的汇编,第一个参数存放在 rdi第二个参数存放在 rsi 中。 在汇编调试模式下,对其打印输出,对于 x86 架构的汇编,第一个参数存放在 rdi第二个参数存放在 rsi 中。
@@ -538,7 +538,7 @@ _dispatch_group_create_with_count(uint32_t n)
然后顺着源码看看,左侧可以看到内部就是调用 `calloc` 分配的内存。 然后顺着源码看看,左侧可以看到内部就是调用 `calloc` 分配的内存。
<img src="./../assets/DispatchGroupTAllocObject.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchGroupTAllocObject.png" style="zoom:30%" />
那 `dispatch_group_t` 是什么就很明显了。和 id 一样,是一个结构体指针。 那 `dispatch_group_t` 是什么就很明显了。和 id 一样,是一个结构体指针。
@@ -568,7 +568,7 @@ struct dispatch_group_s {
对 `dispach_group_enter` 下断点,可以看到如下图 对 `dispach_group_enter` 下断点,可以看到如下图
<img src="./../assets/DispatchGroupEnterSourceCode.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchGroupEnterSourceCode.png" style="zoom:30%" />
一进断点就查看寄存器的值,因为 dispatch_group_enter 函数就一个参数,所以直接读取寄存器 rdi 的值,可以看到就是 `dispatch_group_t` 对象。其中 count 为0. 一进断点就查看寄存器的值,因为 dispatch_group_enter 函数就一个参数,所以直接读取寄存器 rdi 的值,可以看到就是 `dispatch_group_t` 对象。其中 count 为0.
@@ -794,7 +794,7 @@ _dispatch_group_wait_slow(dispatch_group_t dg, dispatch_time_t timeout)
线程的生命周期分为5种新建 -> 就绪 -> 运行 -> 阻塞 -> 死亡 线程的生命周期分为5种新建 -> 就绪 -> 运行 -> 阻塞 -> 死亡
<img src="./../assets/ThreadLifeCycle.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ThreadLifeCycle.png" style="zoom:30%" />
- 新建:使用 `new` 实例化一个线程对象,但该线程对象还未使用 `start()` 方法启动线程这个阶段,该阶段只在内存的堆中为该对象的实例变量分配了内存空间,但线程还**无法参与抢夺CPU的使用权** - 新建:使用 `new` 实例化一个线程对象,但该线程对象还未使用 `start()` 方法启动线程这个阶段,该阶段只在内存的堆中为该对象的实例变量分配了内存空间,但线程还**无法参与抢夺CPU的使用权**
- 就绪:一个线程对象调用 `start()` 方法将线程加入到 **可调度线程池**,同时也变成就绪状态,等待 CPU 来调度执行 - 就绪:一个线程对象调用 `start()` 方法将线程加入到 **可调度线程池**,同时也变成就绪状态,等待 CPU 来调度执行
@@ -808,7 +808,7 @@ _dispatch_group_wait_slow(dispatch_group_t dg, dispatch_time_t timeout)
## 可调度线程池 ## 可调度线程池
<img src="./../assets/DispatchableThreadPool.png" style="zoom:40%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/DispatchableThreadPool.png" style="zoom:40%" />
有新任务过来,会先判断线程池是否都在执行任务: 有新任务过来,会先判断线程池是否都在执行任务:

View File

@@ -4,7 +4,7 @@
> >
> - block 原理是什么,系统是如何实现的? > - block 原理是什么,系统是如何实现的?
> - __block 的作用是什么? > - __block 的作用是什么?
> - block 作为属性时,为什么用 copu 修饰? > - block 作为属性时,为什么用 copy 修饰?
> - block 在修改 NSMutableArray 的时候,需要加 __block 吗? > - block 在修改 NSMutableArray 的时候,需要加 __block 吗?
> >
> 带着问题探究本文。 > 带着问题探究本文。
@@ -19,11 +19,11 @@ Demo
```objective-c ```objective-c
NSInteger age = 27; NSInteger age = 27;
void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b) { void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b) {
NSLog(@"age is %zd", age); NSLog(@"age is %zd", age);
NSLog(@"a is %zd, b is %zd", a, b); NSLog(@"a is %zd, b is %zd", a, b);
}; };
block(1, 2); block(1, 2);
``` ```
用指令`xcrun --sdk iphoneos clang -arch arm64 ViewController.m -rewrite-objc -o ViewController-arm64.cpp` 转为 c++ 用指令`xcrun --sdk iphoneos clang -arch arm64 ViewController.m -rewrite-objc -o ViewController-arm64.cpp` 转为 c++
@@ -224,6 +224,8 @@ struct __ViewController__viewDidLoad_block_desc_0 {
### auto 变量捕获 ### auto 变量捕获
> 在 C 和 Objective-C 编程语言中,`auto` 关键字用于声明自动存储期的变量。自动存储期的变量会在定义它们的块block或作用域scope中自动创建并在退出该作用域时自动销毁。这是变量存储期的默认行为因此 `auto` 关键字实际上是可选的,但有时候为了清晰起见,开发者可能会显式使用它。
Demo1 Demo1
一个最简单的 block参数和返回值都是 void内部仅一条打印语句。 一个最简单的 block参数和返回值都是 void内部仅一条打印语句。
@@ -248,11 +250,13 @@ printBlock();
Demo2: 捕获外部变量 Demo2: 捕获外部变量
```objective-c ```objective-c
age = 27;
void(^printAgeBlock)(void) = ^ { void(^printAgeBlock)(void) = ^ {
NSLog(@"age is %zd", age); NSLog(@"age is %zd", age);
}; };
age = 28; age = 28;
printAgeBlock(); printAgeBlock();
// 27
``` ```
用指令 `xcrun --sdk iphoneos clang -arch arm64 ViewController.m -rewrite-objc -o ViewController-arm64.cpp` 转换为 c++ 代码 用指令 `xcrun --sdk iphoneos clang -arch arm64 ViewController.m -rewrite-objc -o ViewController-arm64.cpp` 转换为 c++ 代码
@@ -289,6 +293,7 @@ void(^printInfoBlock)(void) = ^ {
age = 28; age = 28;
height = 176; height = 176;
printInfoBlock(); printInfoBlock();
// age is 27, height is 176
``` ```
用指令 `xcrun --sdk iphoneos clang -arch arm64 ViewController.m -rewrite-objc -o ViewController-arm64.cpp` 转换为 c++ 代码 用指令 `xcrun --sdk iphoneos clang -arch arm64 ViewController.m -rewrite-objc -o ViewController-arm64.cpp` 转换为 c++ 代码
@@ -301,7 +306,7 @@ printInfoBlock();
- 可以看到我们编写的 block 被声明为一个 `__ViewController__viewDidLoad_block_impl_0` 类型的结构体 - 可以看到我们编写的 block 被声明为一个 `__ViewController__viewDidLoad_block_impl_0` 类型的结构体
- 结构体内有个构造函数见50774行代码。 - 结构体内有个构造函数见50774行代码。
- c++ 中,构造方法中 `age(_age) `的写法,表明传入的 `_age` 会被赋值给结构体内的 ageage 为值传递;`height(_height)` 写法,表明传入的 `_height` 会被复制给结构体内的 heightheight 为引用传递 - c++ 中,构造方法中 `age(_age) `的写法,表明传入的 `_age` 会被赋值给结构体内的 age`NSInteger _age` 则 age 为值传递;`height(_height)` 写法,表明传入的 `_height` 会被复制给结构体内的 height`NSInteger *_height` 则 height 为引用传递
- 50797行代码调用结构体的构造方法age 以值传递的方式传入参数,结构体构造方法内部将 参数 age 的值保存到结构体内部的 age 中。height 以引用传递的方式传入参数,结构体构造方法内,将参数 height 的引用保存起来 - 50797行代码调用结构体的构造方法age 以值传递的方式传入参数,结构体构造方法内部将 参数 age 的值保存到结构体内部的 age 中。height 以引用传递的方式传入参数,结构体构造方法内,将参数 height 的引用保存起来
- 因为 age 是值传递。所以即使在 50798 行代码对 age 进行了修改,结构体内部的 age 值不变 - 因为 age 是值传递。所以即使在 50798 行代码对 age 进行了修改,结构体内部的 age 值不变
- 因为 height 是引用传递。所以在 50799 行代码对 height 进行了修改,结构体内部的 height 值跟着改变 - 因为 height 是引用传递。所以在 50799 行代码对 height 进行了修改,结构体内部的 height 值跟着改变
@@ -352,6 +357,12 @@ QA为什么局部变量存在捕获全局变量不需要捕获
全局变量到哪都可以访问,所以没必要捕获。局部变量因为作用域的问题,所以需要捕获到哪步,以便后续使用。 全局变量到哪都可以访问,所以没必要捕获。局部变量因为作用域的问题,所以需要捕获到哪步,以便后续使用。
理解 block 的本质和意义:
- block 本质上就是一个 oc 对象,也有 isa 指针
- block 是封装了函数调用和函数调用环境的 OC 对象
### 变量捕获总结 ### 变量捕获总结
@@ -752,6 +763,46 @@ Demo4
block 嵌套。多个 block 存在先后关系时
- 看看最晚的一个 block 是什么修饰的。如果是 strong早期的是 weak则也不会释放。
- 看看最晚的一个 block 是什么修饰的。如果是 weak早起是 strong则第一个 block 内部的可以正常访问,之后调用对象的 dealloc 方法,最后的 block 访问因为对象释放了,所以访问为 null
```objective-c
Person *p = [[Person alloc] init];
p.name = @"杭城小刘";
__weak Person *weakPerson = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakPerson.name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", p.name);
});
});
NSLog(@"-touchesBegan:withEvent:");
2024-08-13 20:58:46.500553+0800 BlockExplore[29848:967516] -touchesBegan:withEvent:
2024-08-13 20:58:47.549486+0800 BlockExplore[29848:967516] 杭城小刘
2024-08-13 20:58:49.550015+0800 BlockExplore[29848:967516] 杭城小刘
2024-08-13 20:58:49.550315+0800 BlockExplore[29848:967516] -[Person dealloc]
Person *p = [[Person alloc] init];
p.name = @"杭城小刘";
__weak Person *weakPerson = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", p.name);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakPerson.name);
});
});
NSLog(@"-touchesBegan:withEvent:");
2024-08-13 20:59:51.265796+0800 BlockExplore[29889:968688] -touchesBegan:withEvent:
2024-08-13 20:59:52.313063+0800 BlockExplore[29889:968688] 杭城小刘
2024-08-13 20:59:52.313265+0800 BlockExplore[29889:968688] -[Person dealloc]
2024-08-13 20:59:54.313367+0800 BlockExplore[29889:968688] (null)
```
### block 如何修改变量 ### block 如何修改变量
#### __block 修饰基本数据类型 #### __block 修饰基本数据类型
@@ -949,7 +1000,7 @@ __attribute__((__blocks__(byref))) __Block_byref_num2_0 num2 = {(void*)0,(__Bloc
- block 外定义的 NSMutableArrayblock 内只是使用数组则不需要` __block` - block 外定义的 NSMutableArrayblock 内只是使用数组则不需要` __block`
- 如果在 block 操作指针,则需要加 `__block` - 如果在 block 操作指针,则需要加 `__block`
注意:`__weak` 只可以用来修饰对象,(终端用 clang 处理)否则 clang 会报错 `warning: 'objc_ownership' only applies to Objective-C object or block pointer types; type here is 'int' [-Wignored-attributes]` 注意:`__weak` 只可以用来修饰对象,(终端用 clang 处理)否则 clang 会报错 `warning: 'objc_ownership' only applies to Objective-C object or block pointer types; type here is 'int' [-Wignored-attributes]`
@@ -1160,7 +1211,7 @@ in block: age = 28, address is 0x600000464938
**一言以蔽之,`__forwarding` 指针是为了在 `__block` 变量从栈复制到堆上后,在 block 外对 `__block` 变量的修改也可以同步到堆上实际存储的 `__block` 变量的结构体上。也就是抹平栈、堆上对变量操作的差异。** **一言以蔽之,`__forwarding` 指针是为了在 `__block` 变量从栈复制到堆上后,在 block 外对 `__block` 变量的修改也可以同步到堆上实际存储的 `__block` 变量的结构体上。也就是抹平栈、堆上对变量操作的差异。**
不论在
## Block 内存引用 ## Block 内存引用
@@ -1221,10 +1272,9 @@ __Block_byref_p_0 *__forwarding; 8
int __size; 4 int __size; 4
void (*__Block_byref_id_object_copy)(void*, void*); 8 void (*__Block_byref_id_object_copy)(void*, void*); 8
void (*__Block_byref_id_object_dispose)(void*); 8 void (*__Block_byref_id_object_dispose)(void*); 8
Person *p; Person *p; 8
}; };
__attribute__((__blocks__(byref))) __Block_byref_p_0 p = { __attribute__((__blocks__(byref))) __Block_byref_p_0 p = {
0, 0,
&p, &p,
@@ -1316,6 +1366,14 @@ p.block();
## 为什么加 weakself、strongself
weakSelf 是为了使 block 不持有 self避免 Retain Circle 循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
strongSelf 的目的是因为一旦进入 block 执行,假设不允许 self 在这个执行过程中释放,就需要加入 strongSelf。
block 执行完后这个 strongSelf 会自动释放,没有不会存在循环引用问题。如果在 Block 内需要多次 访问 self则需要使用 strongSelf。
## 总结 ## 总结

View File

@@ -2425,7 +2425,7 @@ import 举个例子吧。在 `Person.m`
### 编写工具生成 hmap 文件 ### 编写工具生成 hmap 文件
#### 为什么要编写 hmap 文件 #### 为什么要生成 hmap 文件
如果2个 `.m` 文件有相同的头文件代码,造成编译浪费。 如果2个 `.m` 文件有相同的头文件代码,造成编译浪费。
@@ -2458,7 +2458,7 @@ Xcode 会主动生成 `.hmap` 文件,那为什么还需要研究生成 `hmap`
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/StaticLibUseHeaderMapOnlyQuoteMethod.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/StaticLibUseHeaderMapOnlyQuoteMethod.png" style="zoom:30%" />
分析:在 App 使用 Static Library 的情况下,假设开启了 `Use Header Map`,静态库中所有头文件类型为 `Project`(只有 Project、Private、Public 3种类型public 就是字面意思的公开p r)的情况,最终生成的 `.hmap` 文件中只会包含类似 `#import "Student.h"` 的键值引用。也就是说使用的地方,只有 `#import "Student.h"` 的这种方式才会走 hmap 策略,否则还是走 `Header Search Path` 来寻找头文件路径。 分析:在 App 使用 Static Library 的情况下,假设开启了 `Use Header Map`,静态库中所有头文件类型为 `Project`(只有 Project、Private、Public 3种类型public 就是字面意思的公开private 则代表 In Progress project 才是通常意义上的 Private 含义)的情况,最终生成的 `.hmap` 文件中只会包含类似 `#import "Student.h"` 的键值引用。也就是说使用的地方,只有 `#import "Student.h"` 的这种方式才会走 hmap 策略,否则还是走 `Header Search Path` 来寻找头文件路径。
组件、库使用 `#import <SomeFramework/SomeFrameworkExportedHeader.h>` 是访问的标准做法。好处有3点 组件、库使用 `#import <SomeFramework/SomeFrameworkExportedHeader.h>` 是访问的标准做法。好处有3点
1. 明确头文件的由来,避免歧义 1. 明确头文件的由来,避免歧义
@@ -2505,7 +2505,7 @@ LLVM 真是好东西,把 Xcode 编译、链接等一些幕后的事情变成
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/StaticLibUseHeaderMapEffect.png" style="zoom:30%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/StaticLibUseHeaderMapEffect.png" style="zoom:30%" />
可以看到静态库使用了自定义的 Header Maps 文件后,使用静态的 App 前后,编译耗时减少了0.1s。 可以看到静态库使用了自定义的 Header Maps 文件后,使用静态的 App 前后,编译耗时减少了1.1s节省了57%
@@ -2696,6 +2696,37 @@ objdump --macho --private-headers DSYMDemo
### main 函数
实验一:
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/MainFuncHasNoDifferenceWithOthers.png" style="zoom:30%" />
编写2个函数main 函数和 test 函数,除了方法名不同,在汇编侧是一样的。
实验二:
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/CanNotLinkWithoutMainFunc.png" style="zoom:30%" />
可以看到创建的 `test.c` 文件中,只有一个 `test` 方法。没有 `main` 方法,然后用 gcc 发现链接报错。
然后利用 gcc 指令 ` gcc -nostartfiles -e_test test.c` 发现可以编译通过,运行也没问题。
当然除了 gcc很多嵌入式平台可以在代码中指定 c 程序的起点。
比如 STM32专门有个汇编文件用于系统的初始化。
总结一下:
- main 函数和其他普通函数并无区别
- main 函数是很多程序的默认起点,但绝不是非它不可,任何函数都可以被设置成程序起点。
iOS 侧dyld 默认以 main 函数作为函数起点。
### dyld 加载过程 ### dyld 加载过程
<img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/dyldFullProcess.png" style="zoom:40%" /> <img src="https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/dyldFullProcess.png" style="zoom:40%" />

View File

@@ -2,7 +2,7 @@
## TCP/UDP ## TCP/UDP
TCP 传输的核心公式:速度 = 窗口大小/往返时间,这个公式对于理解传输本质和排查传输问题具有很强的知道意义。 TCP 传输的核心公式:速度 = 窗口大小/往返时间,这个公式对于理解传输本质和排查传输问题具有很强的指导意义。
TCP 里面有三种窗口发送窗口、接收窗口、拥塞窗口。如果没有特别说明TCP Window 指的是接收窗口。 TCP 里面有三种窗口发送窗口、接收窗口、拥塞窗口。如果没有特别说明TCP Window 指的是接收窗口。
@@ -18,7 +18,7 @@ TCP 传输的起始阶段,速度都是从低到高升上来的,很少一上
这个机制其实就是 TCP 的拥塞控制。 这个机制其实就是 TCP 的拥塞控制。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TCPWindowDiff.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TCPWindowDiff.png)
@@ -32,7 +32,7 @@ TCP 使用拥塞机制来确保传输速度和稳定性。拥塞机制是通信
Slow start即 TCP 传输的开始阶段是从一个相对低的速度开始的。之后拥塞窗口会翻倍方式增长。每次 TCP 收到一个确认了数据的 ACK拥塞窗口就增加1个 MSS Slow start即 TCP 传输的开始阶段是从一个相对低的速度开始的。之后拥塞窗口会翻倍方式增长。每次 TCP 收到一个确认了数据的 ACK拥塞窗口就增加1个 MSS
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TCP-SlowStart.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TCP-SlowStart.png)
当收到重复的 ACK 报文即确认号一样比如收到2个 ACK但他们的确认号一样那么第二个 ACK 就不算是“确认数据的 ACK”拥塞窗口就不会增加2个 MSS只会增加1个 MSS。 当收到重复的 ACK 报文即确认号一样比如收到2个 ACK但他们的确认号一样那么第二个 ACK 就不算是“确认数据的 ACK”拥塞窗口就不会增加2个 MSS只会增加1个 MSS。
@@ -52,11 +52,11 @@ Slow start即 TCP 传输的开始阶段是从一个相对低的速度开始
**慢启动阈值** ssthresh过了这个阈值拥塞窗口的增长速度就立刻变慢了变为每过一个 RTT拥塞窗口就增加一个 MSS之前是没收到一个确认数据的 ACK 就增加1个 MSS **慢启动阈值** ssthresh过了这个阈值拥塞窗口的增长速度就立刻变慢了变为每过一个 RTT拥塞窗口就增加一个 MSS之前是没收到一个确认数据的 ACK 就增加1个 MSS
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TCP-SlowStartChart.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TCP-SlowStartChart.png)
上图所示,假设 ICW 是4个 MSSssthresh 是32个 MSS慢启动阶段经过1个 RTT 后CW 扩大为8MSS、16MSS、32MSS。等到了阈值之后TCP就进入拥塞避免阶段了。每过一个 RTT拥塞窗口只增加1MSS曲线就变为较为斜率较低的直线了。 上图所示,假设 ICW 是4个 MSSssthresh 是32个 MSS慢启动阶段经过1个 RTT 后CW 扩大为8MSS、16MSS、32MSS。等到了阈值之后TCP就进入拥塞避免阶段了。每过一个 RTT拥塞窗口只增加1MSS曲线就变为较为斜率较低的直线了。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TCP-CongestionAvoidance.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TCP-CongestionAvoidance.png)
QA如果拥塞窗口大小正好等于慢启动阈值那么发送方这时候是需要采用拥塞避免过程线性增长还是继续选择慢启动过程指数增长[RFC5681](https://datatracker.ietf.org/doc/html/rfc5681) 规定是说两者都可以。 QA如果拥塞窗口大小正好等于慢启动阈值那么发送方这时候是需要采用拥塞避免过程线性增长还是继续选择慢启动过程指数增长[RFC5681](https://datatracker.ietf.org/doc/html/rfc5681) 规定是说两者都可以。
@@ -89,7 +89,7 @@ Congestion Window拥塞窗口简写 CW。拥塞窗口是针对每个连接
TCP 野蛮生长后当达到慢启动阈值之后会进入拥塞避免阶段。这个阶段的特点是“和性增长乘性降低”Addictive increase/multiplicative decreaseAIMD解释下就是拥塞避免阶段每个RTT时间拥塞窗口只增长1MSS这个阶段的拥塞窗口增长是线性的斜率比较低的直线当探测到拥塞时拥塞窗口就要往下降下降是直接减半的叫做乘性降低。 TCP 野蛮生长后当达到慢启动阈值之后会进入拥塞避免阶段。这个阶段的特点是“和性增长乘性降低”Addictive increase/multiplicative decreaseAIMD解释下就是拥塞避免阶段每个RTT时间拥塞窗口只增长1MSS这个阶段的拥塞窗口增长是线性的斜率比较低的直线当探测到拥塞时拥塞窗口就要往下降下降是直接减半的叫做乘性降低。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TCP-CongestionAvoidanceProgress.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TCP-CongestionAvoidanceProgress.png)
@@ -115,7 +115,7 @@ TCP 会利用另一种机制来解决超时重传带来的时间等待问题,
快速恢复是 TCP Reno 算法引入的一个阶段,是和“快速重传”搭配工作的。跟之前的“慢启动-拥塞避免-慢启动-拥塞避免”不同的是,当遇到拥塞点之后,通过快速重传,就不再进入慢启动了,而是从这个减半的拥塞窗口开始,保持跟拥塞避免一样的线性增长,直到遇到下一个拥塞点。 快速恢复是 TCP Reno 算法引入的一个阶段,是和“快速重传”搭配工作的。跟之前的“慢启动-拥塞避免-慢启动-拥塞避免”不同的是,当遇到拥塞点之后,通过快速重传,就不再进入慢启动了,而是从这个减半的拥塞窗口开始,保持跟拥塞避免一样的线性增长,直到遇到下一个拥塞点。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TCP-QuickRecover.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TCP-QuickRecover.png)
@@ -280,7 +280,7 @@ Cache-Control 中比较常用的就是 max-age此外还有几个
将网页点击前进、后退会发现“ Status Code200 from disk cache” 将网页点击前进、后退会发现“ Status Code200 from disk cache”
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/CacheControlFromDisk.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/CacheControlFromDisk.png)
### 条件请求 ### 条件请求
@@ -290,7 +290,7 @@ Cache-Control 中比较常用的就是 max-age此外还有几个
条件请求共5个头字段常用的是 `If-Modified-Since` + `Last-modified``If-None-Match` + `ETag`这两个(需要搭配使用)。需要在第一次响应报文预先提供 `Last-modified``ETag`,然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。如果服务端资源没有更新,则返回 “304 Not Modified”表示缓存有效 浏览器只需要更新缓存日期便可继续使用本地缓存 条件请求共5个头字段常用的是 `If-Modified-Since` + `Last-modified``If-None-Match` + `ETag`这两个(需要搭配使用)。需要在第一次响应报文预先提供 `Last-modified``ETag`,然后第二次请求时就可以带上缓存里的原值,验证资源是否是最新的。如果服务端资源没有更新,则返回 “304 Not Modified”表示缓存有效 浏览器只需要更新缓存日期便可继续使用本地缓存
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/CacheComunicate.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/CacheComunicate.png)
Last Modified 代表资源的最后修改时间 Last Modified 代表资源的最后修改时间
@@ -401,7 +401,7 @@ HTTP 传输链路上,不只是客户端有缓存,服务器上的缓存也是
### 客户端的缓存控制 ### 客户端的缓存控制
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/ClientCacheControl.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/ClientCacheControl.png)
- max-stale如果代理上的缓存过期了也可以接受但不能过期太多超过 x 秒也会不要 - max-stale如果代理上的缓存过期了也可以接受但不能过期太多超过 x 秒也会不要
@@ -593,7 +593,7 @@ HTTPS 其实是一个“非常简单”的协议RFC 只有短短的 7 页
HTTPS 的核心在于 `s` ,也就是把 HTTP 下层传输协议由 TCP/IP 换为了 SSL/TLS从 "HTTP Over TCP/IP" 变为 "HTTP Over SSL/TLS"。让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API而是调用专门的安全接口。 HTTPS 的核心在于 `s` ,也就是把 HTTP 下层传输协议由 TCP/IP 换为了 SSL/TLS从 "HTTP Over TCP/IP" 变为 "HTTP Over SSL/TLS"。让 HTTP 运行在了安全的 SSL/TLS 协议上,收发报文不再使用 Socket API而是调用专门的安全接口。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTPSOverSSL.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTPSOverSSL.png)
SSL(Secure Sockets Layer),安全套接字层,在 OSI 模型中第五层会话层由网景公司在1994年发明有 V2、V3 版本V1 因为有严重缺陷从未公开过。SSL 发展到 V3 被证明是一个非常好的安全通信协议,于是互联网工程组 IEFT 在1999年更名为 TLS(Transport Layer Security)传输层安全正式标准化版本号从1.0计数,所以 TLS1.0 也就是 SSL V3.1/ SSL(Secure Sockets Layer),安全套接字层,在 OSI 模型中第五层会话层由网景公司在1994年发明有 V2、V3 版本V1 因为有严重缺陷从未公开过。SSL 发展到 V3 被证明是一个非常好的安全通信协议,于是互联网工程组 IEFT 在1999年更名为 TLS(Transport Layer Security)传输层安全正式标准化版本号从1.0计数,所以 TLS1.0 也就是 SSL V3.1/
@@ -695,7 +695,7 @@ SHA-2 其实是一系列摘要算法的统称包含6中常用的是 SHA224
知道问题症结所在,也比较好解,真正的完整性必须建立在“机密性”基础上。使用混合加密系统中用会话密钥加密消息和摘要,这样中间人无法得知明文。这个过程叫做“哈希消息认证码”(HMAC) 知道问题症结所在,也比较好解,真正的完整性必须建立在“机密性”基础上。使用混合加密系统中用会话密钥加密消息和摘要,这样中间人无法得知明文。这个过程叫做“哈希消息认证码”(HMAC)
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTPSIntegrity.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTPSIntegrity.png)
#### 数字签名 #### 数字签名
@@ -707,7 +707,7 @@ SHA-2 其实是一系列摘要算法的统称包含6中常用的是 SHA224
非对称加密效率太低,所以私钥只加密原报文的摘要,这样运算量小,速度也快。得到的数字签名也很小,方便传输和保管。 非对称加密效率太低,所以私钥只加密原报文的摘要,这样运算量小,速度也快。得到的数字签名也很小,方便传输和保管。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTPSSign.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTPSSign.png)
这个过程被叫做“签名”、“验签”。 这个过程被叫做“签名”、“验签”。
@@ -725,7 +725,7 @@ CA 如何自证?
信任链。小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是 Root CA就只能自己证明自己了这个就叫“自签名证书”Self-Signed Certificate或者“根证书”Root Certificate。你必须相信否则整个证书信任链就走不下去了。 信任链。小一点的 CA 可以让大 CA 签名认证,但链条的最后,也就是 Root CA就只能自己证明自己了这个就叫“自签名证书”Self-Signed Certificate或者“根证书”Root Certificate。你必须相信否则整个证书信任链就走不下去了。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTPSCAChain.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTPSCAChain.png)
浏览器内置各大 CA 的根证书上网的时候只要服务器发过来它的证书就可以验证证书里的签名顺着证书链Certificate Chain一层层地验证直到找到根证书就能够确定证书是可信的从而里面的公钥也是可信的。 浏览器内置各大 CA 的根证书上网的时候只要服务器发过来它的证书就可以验证证书里的签名顺着证书链Certificate Chain一层层地验证直到找到根证书就能够确定证书是可信的从而里面的公钥也是可信的。
@@ -759,7 +759,7 @@ TLS 包含多个子协议,每个协议都有各自的职责,比较常用的
关于记录可以用下图解释 关于记录可以用下图解释
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSRecords.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSRecords.png)
可以看出: 可以看出:
@@ -773,19 +773,19 @@ TLS 包含多个子协议,每个协议都有各自的职责,比较常用的
下图是 TLS 完整的流程 下图是 TLS 完整的流程
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSFullPipeline.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSFullPipeline.png)
上 Demo以 Mac 上 wireshark 抓取 `https://github.com` 为例。 上 Demo以 Mac 上 wireshark 抓取 `https://github.com` 为例。
第一步:在 TCP 3次握手建立连接后客户端发送一个 “Client Hello“ 的消息,表示开始和服务器沟通。包含客户端的 TLS 版本号、支持的密码套件、随机数,这些信息用于后续生成会话密钥。 第一步:在 TCP 3次握手建立连接后客户端发送一个 “Client Hello“ 的消息,表示开始和服务器沟通。包含客户端的 TLS 版本号、支持的密码套件、随机数,这些信息用于后续生成会话密钥。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSHandShake1.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSHandShake1.png)
其实,这些信息的作用都是**协商**,客户端告诉浏览器我这边的 TLS 协议是什么版本,我本地支持的加密套件都有哪些,你后续从我支持的列表中选一个。 其实,这些信息的作用都是**协商**,客户端告诉浏览器我这边的 TLS 协议是什么版本,我本地支持的加密套件都有哪些,你后续从我支持的列表中选一个。
第二步:服务端收到 “Client Hello” 的消息后,会返回一个 “Server Hello” 的消息。核对版本号,同时也会生成一个随机数,然后从客户端的加密套件中选择一个作为本次通信使用的密码套件。 第二步:服务端收到 “Client Hello” 的消息后,会返回一个 “Server Hello” 的消息。核对版本号,同时也会生成一个随机数,然后从客户端的加密套件中选择一个作为本次通信使用的密码套件。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSHandShake2.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSHandShake2.png)
可以看到此时,服务端选择了 `Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)`这个加密套件。服务端返回给客户端的这些信息也就代表“TLS 版本号对上了,你这边给的加密套件很多,我选了一个最合适的,椭圆曲线 + RSA + AES + SHA384。另外我给了你一个随机数你需要保存后续使用” 可以看到此时,服务端选择了 `Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)`这个加密套件。服务端返回给客户端的这些信息也就代表“TLS 版本号对上了,你这边给的加密套件很多,我选了一个最合适的,椭圆曲线 + RSA + AES + SHA384。另外我给了你一个随机数你需要保存后续使用”
@@ -799,13 +799,13 @@ TLS 是建立在 TCP 的上层协议,因此要先按照 TCP 的规则来,也
同时服务器选择了 ECDHE 算法所以在发送了服务器证书后马上发送“Server Key Exchange”消息。里面是椭圆曲线的公钥(Server Params),用来实现密钥的交换算法,再加上自己的私钥签名认证(用私钥对椭圆曲线的 public key 做了签名认证生成了 Signature) 同时服务器选择了 ECDHE 算法所以在发送了服务器证书后马上发送“Server Key Exchange”消息。里面是椭圆曲线的公钥(Server Params),用来实现密钥的交换算法,再加上自己的私钥签名认证(用私钥对椭圆曲线的 public key 做了签名认证生成了 Signature)
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSHandShake3.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSHandShake3.png)
意味着,服务器告诉客户端,我这边选择的加密套件有点复杂,所以再给你一个算法的参数,和随机数一样,先保存后续使用。为了保证我就是我,我给参数 public key 做了签名。 意味着,服务器告诉客户端,我这边选择的加密套件有点复杂,所以再给你一个算法的参数,和随机数一样,先保存后续使用。为了保证我就是我,我给参数 public key 做了签名。
第四步服务端发送“Server Hello Done”消息。告诉客户端我的基础信息就是这些打招呼阶段结束 第四步服务端发送“Server Hello Done”消息。告诉客户端我的基础信息就是这些打招呼阶段结束
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSHandShake4.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSHandShake4.png)
至此,第一个消息往返就结束了(2个TCP包),客户端和服务端通过**明文**共享了Client Random、Server Random、Server Params。 至此,第一个消息往返就结束了(2个TCP包),客户端和服务端通过**明文**共享了Client Random、Server Random、Server Params。
@@ -813,7 +813,7 @@ TLS 是建立在 TCP 的上层协议,因此要先按照 TCP 的规则来,也
第五步:客户端按照加密套件的要求,也生成了一个椭圆曲线的公钥(Client Params)用“Client Key Exchange”消息发送给服务器 第五步:客户端按照加密套件的要求,也生成了一个椭圆曲线的公钥(Client Params)用“Client Key Exchange”消息发送给服务器
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSHandShake5.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSHandShake5.png)
至此客户端和服务器都拿到了密钥交换算法的2个参数(Client Params、Server Params),然后用 ECDHE 算法计算出一个随机数叫“Pre-Master”也叫做“预主密钥”。 至此客户端和服务器都拿到了密钥交换算法的2个参数(Client Params、Server Params),然后用 ECDHE 算法计算出一个随机数叫“Pre-Master”也叫做“预主密钥”。
@@ -842,7 +842,7 @@ master_secret = PRF(pre_master_secret, "master secret",
第六步服务器发送“Change Cipher Spec” 和 “Encrypted Handshake Message” 消息,双方都解密 OK握手正式结束。后续请求就收发被加密的 HTTP 数据了。 第六步服务器发送“Change Cipher Spec” 和 “Encrypted Handshake Message” 消息,双方都解密 OK握手正式结束。后续请求就收发被加密的 HTTP 数据了。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSHandShake6.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSHandShake6.png)
#### RSA 握手 #### RSA 握手
@@ -926,7 +926,7 @@ HTTPS 建立连接时除了要做 TCP 握手,还要做 TLS 握手,在 1.2
做法:利用了扩展。客户端在 “Client Hello” 消息里直接用 “supported_groups” 带上支持的曲线,比如 P-256、x25519用“key_share”带上曲线对应的客户端公钥参数用 “signature_algorithms” 带上签名算法。服务器收到后在这些扩展里选定一个曲线和参数,再用 “key_share” 扩展返回服务器这边的公钥参数,就实现了双方的密钥交换,后面的流程就和 1.2 基本一样了。 做法:利用了扩展。客户端在 “Client Hello” 消息里直接用 “supported_groups” 带上支持的曲线,比如 P-256、x25519用“key_share”带上曲线对应的客户端公钥参数用 “signature_algorithms” 带上签名算法。服务器收到后在这些扩展里选定一个曲线和参数,再用 “key_share” 扩展返回服务器这边的公钥参数,就实现了双方的密钥交换,后面的流程就和 1.2 基本一样了。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLS1.3Pipeline.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLS1.3Pipeline.png)
### HTTPS 加速 ### HTTPS 加速
@@ -946,7 +946,7 @@ HTTPS 连接大致上可以划分为两个部分:建立连接时的非对称
下图是存在改进空间的地方。 下图是存在改进空间的地方。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLSSpeedDisadvantage.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLSSpeedDisadvantage.png)
#### 硬件优化 #### 硬件优化
@@ -1006,7 +1006,7 @@ Session Ticket 方案需要使用一个固定密钥文件(ticket_key)来加密 T
`False Start``Session ID``Session Ticket`等方式只能实现 1-RTT而 TLS1.3 更进一步实现了 0-RTT原理和 `Session Ticket` 差不多,但在发送 Ticket 的同时会带上应用数据(Early Data),免去了 1.2 里的服务器确认步骤,这种方式叫 `Pre-shared Key`简称为“PSK”。 `False Start``Session ID``Session Ticket`等方式只能实现 1-RTT而 TLS1.3 更进一步实现了 0-RTT原理和 `Session Ticket` 差不多,但在发送 Ticket 的同时会带上应用数据(Early Data),免去了 1.2 里的服务器确认步骤,这种方式叫 `Pre-shared Key`简称为“PSK”。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/TLS1.3PSK.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/TLS1.3PSK.png)
PSK 存在缺点,为了追求效率而降低一些安全性,容易收到“重放攻击”,黑客截获 PSK 后,原封不动的向服务器发出去。解决办法是只允许安全的 HTTP 请求方法,如 HEAD/GET在消息中增加时间戳、nonce 验证。 PSK 存在缺点,为了追求效率而降低一些安全性,容易收到“重放攻击”,黑客截获 PSK 后,原封不动的向服务器发出去。解决办法是只允许安全的 HTTP 请求方法,如 HEAD/GET在消息中增加时间戳、nonce 验证。
@@ -1040,7 +1040,7 @@ PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n
但在 wireshark 抓包中HTTP/2 连接前言被称为 “Magic“。 但在 wireshark 抓包中HTTP/2 连接前言被称为 “Magic“。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2ConnectionPreface.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2ConnectionPreface.png)
### 头部压缩 ### 头部压缩
@@ -1054,13 +1054,13 @@ HTTP/1 使用头部字段“Content-Encoding” 指定 Body 的编码方式,
现在 HTTP 报文头就简单了,全都是 `Key-Value` 形式的字段,于是 HTTP/2 就为一些最常用的头字段定义了一个只读的“静态表”Static Table 现在 HTTP 报文头就简单了,全都是 `Key-Value` 形式的字段,于是 HTTP/2 就为一些最常用的头字段定义了一个只读的“静态表”Static Table
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2StaticTable.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2StaticTable.png)
完整的静态表可以查看[RFC 7541 - HPACK: Header Compression for HTTP/2](https://httpwg.org/specs/rfc7541.html#static.table.definition) 完整的静态表可以查看[RFC 7541 - HPACK: Header Compression for HTTP/2](https://httpwg.org/specs/rfc7541.html#static.table.definition)
假设使用了自定义字段怎么办?动态表(Dynamic Table) 添加在静态表后面,结构相同,在编码的时候随时更新。 假设使用了自定义字段怎么办?动态表(Dynamic Table) 添加在静态表后面,结构相同,在编码的时候随时更新。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2DymaicTable.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2DymaicTable.png)
在 HTTP/2 连接上发送的报文越来越多,客户端、服务器的“字典”也会越来越丰富,最终每次的头部字段都会变成一两个字节的代码,原来上千字节的头用几十个字节就可以表示了,压缩效果比 gzip 要好得多。 在 HTTP/2 连接上发送的报文越来越多,客户端、服务器的“字典”也会越来越丰富,最终每次的头部字段都会变成一两个字节的代码,原来上千字节的头用几十个字节就可以表示了,压缩效果比 gzip 要好得多。
@@ -1074,11 +1074,11 @@ HTTP/2 以前采用纯文本格式的报文(ASCII 码),但 HTTP/2 向 TCP/IP
这种策略有点像 `Chunked` 分块编码的方式,化整为零,但 HTTP/2 数据分帧后 “Header + Body” 的报文结构就没了,协议看到的是一个个碎片。 这种策略有点像 `Chunked` 分块编码的方式,化整为零,但 HTTP/2 数据分帧后 “Header + Body” 的报文结构就没了,协议看到的是一个个碎片。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2BinaryData.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2BinaryData.png)
HTTP/2 的帧结构有点类似 TCP 的段或者 TLS 里的记录,但报头很小,只有 9 字节,非常地节省(可以对比一下 TCP 头,它最少是 20 个字节)。 HTTP/2 的帧结构有点类似 TCP 的段或者 TLS 里的记录,但报头很小,只有 9 字节,非常地节省(可以对比一下 TCP 头,它最少是 20 个字节)。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2BinaryFrameStructure.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2BinaryFrameStructure.png)
帧开头是 3 个字节的长度(但不包括头的 9 个字节),默认上限是 2^14最大是 2^24也就是说 HTTP/2 的帧通常不超过 16K最大是 16M。 帧开头是 3 个字节的长度(但不包括头的 9 个字节),默认上限是 2^14最大是 2^24也就是说 HTTP/2 的帧通常不超过 16K最大是 16M。
@@ -1098,7 +1098,7 @@ HTTP/2 总共定义了 10 种类型的帧,但一个字节可以表示最多 25
从“流”的层面上看,消息是一些有序的“帧”序列,而在“连接”的层面上看,消息却是乱序收发的“帧”。多个请求 / 响应之间没有了顺序关系,不需要排队等待,也就不会再出现“队头阻塞”问题,降低了延迟,大幅度提高了连接的利用率。 从“流”的层面上看,消息是一些有序的“帧”序列,而在“连接”的层面上看,消息却是乱序收发的“帧”。多个请求 / 响应之间没有了顺序关系,不需要排队等待,也就不会再出现“队头阻塞”问题,降低了延迟,大幅度提高了连接的利用率。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2Stram.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2Stram.png)
HTTP/1 中请求-响应报文来回一次是一次 HTTP 通信HTTP/2中一个流也做了类似的事情。 HTTP/1 中请求-响应报文来回一次是一次 HTTP 通信HTTP/2中一个流也做了类似的事情。
@@ -1120,7 +1120,7 @@ HTTP/2流的特点
- 第 0 号流比较特殊,不能关闭,也不能发送数据帧,只能发送控制帧,用于流量控制 - 第 0 号流比较特殊,不能关闭,也不能发送数据帧,只能发送控制帧,用于流量控制
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2StreamExample.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2StreamExample.png)
可以看到: 可以看到:
@@ -1132,7 +1132,7 @@ HTTP/2流的特点
### 流状态转换 ### 流状态转换
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2StatusChange.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2StatusChange.png)
上图对应到标准的 HTTP 请求应答。 上图对应到标准的 HTTP 请求应答。
@@ -1170,7 +1170,7 @@ HTTP/2 还在一定程度上改变了传统的“请求 - 应答”工作模式
HTTP/2 是建立在 HPack + Stream + TLS1.2 之上的。 HTTP/2 是建立在 HPack + Stream + TLS1.2 之上的。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP2ProtocolStack.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP2ProtocolStack.png)
QA明文形式的 HTTP/2h2c有什么好处应该如何使用呢 QA明文形式的 HTTP/2h2c有什么好处应该如何使用呢
@@ -1236,7 +1236,7 @@ HTTP Over QUIC 就是 HTTP3完美解决队头阻塞问题。
### QUIC 协议 ### QUIC 协议
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP3Protocol.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP3Protocol.png)
可以看到 HTTP3 对比 HTTP2 将 TCP 换为 UDP因为 UDP 无序,包之间没有依赖关系,所以从根本上解决了“队头阻塞”问题 可以看到 HTTP3 对比 HTTP2 将 TCP 换为 UDP因为 UDP 无序,包之间没有依赖关系,所以从根本上解决了“队头阻塞”问题
@@ -1260,7 +1260,7 @@ QUIC 的基本数据传输单位是包packet和帧frame一个包
QUIC 使用不透明的 “**连接 ID**” 来标记通信的两个端点,客户端和服务器可以自行选择一组 ID 来标记自己,这样就解除了 TCP 里连接对“IP 地址 + 端口”即常说的四元组的强绑定支持“连接迁移”Connection Migration QUIC 使用不透明的 “**连接 ID**” 来标记通信的两个端点,客户端和服务器可以自行选择一组 ID 来标记自己,这样就解除了 TCP 里连接对“IP 地址 + 端口”即常说的四元组的强绑定支持“连接迁移”Connection Migration
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/QUICPacket.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/QUICPacket.png)
比如从外面玩回到家,手机会自动由 4G 切换到 WiFi。这时 IP 地址会发生变化TCP 就必须重新建立连接。而 QUIC 连接里的两端连接 ID 不会变,所以连接在“逻辑上”没有中断,它就可以在新的 IP 地址上继续使用之前的连接,消除重连的成本,实现连接的无缝迁移。 比如从外面玩回到家,手机会自动由 4G 切换到 WiFi。这时 IP 地址会发生变化TCP 就必须重新建立连接。而 QUIC 连接里的两端连接 ID 不会变,所以连接在“逻辑上”没有中断,它就可以在新的 IP 地址上继续使用之前的连接,消除重连的成本,实现连接的无缝迁移。
@@ -1268,7 +1268,7 @@ QUIC 的帧里有多种类型PING、ACK 等帧用于管理连接,而 STREAM
QUIC 里的流与 HTTP/2 的流非常相似,也是帧的序列。但 HTTP/2 里的流都是双向的,而 QUIC 则分为双向流和单向流。 QUIC 里的流与 HTTP/2 的流非常相似,也是帧的序列。但 HTTP/2 里的流都是双向的,而 QUIC 则分为双向流和单向流。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/QUICStream.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/QUICStream.png)
QUIC 帧普遍采用变长编码,最少只要 1 个字节,最多有 8 个字节。流 ID 的最大可用位数是 62数量上比 HTTP/2 的 2^31 大大增加。 QUIC 帧普遍采用变长编码,最少只要 1 个字节,最多有 8 个字节。流 ID 的最大可用位数是 62数量上比 HTTP/2 的 2^31 大大增加。
@@ -1282,7 +1282,7 @@ HTTP/3 里仍然使用流来发送“请求 - 响应”,但它自身不需要
HTTP/3 里的“双向流”可以完全对应到 HTTP/2 的流,而“单向流”在 HTTP/3 里用来实现控制和推送,近似地对应 HTTP/2 的 0 号流。由于流管理被“下放”到了 QUIC所以 HTTP/3 里帧的结构也变简单了。帧头只有两个字段:类型和长度,而且同样都采用变长编码,最小只需要两个字节。 HTTP/3 里的“双向流”可以完全对应到 HTTP/2 的流,而“单向流”在 HTTP/3 里用来实现控制和推送,近似地对应 HTTP/2 的 0 号流。由于流管理被“下放”到了 QUIC所以 HTTP/3 里帧的结构也变简单了。帧头只有两个字段:类型和长度,而且同样都采用变长编码,最小只需要两个字节。
![](/Users/lbp/Desktop/GitHub/knowledge-kit/assets/HTTP3Frame.png) ![](https://raw.githubusercontent.com/FantasticLBP/knowledge-kit/master/assets/HTTP3Frame.png)
HTTP/3 里的帧仍然分成数据帧和控制帧两类HEADERS 帧和 DATA 帧传输数据,但其他一些帧因为在下层的 QUIC 里有了替代,所以在 HTTP/3 里就都消失了,比如 RST_STREAM、WINDOW_UPDATE、PING 等。 HTTP/3 里的帧仍然分成数据帧和控制帧两类HEADERS 帧和 DATA 帧传输数据,但其他一些帧因为在下层的 QUIC 里有了替代,所以在 HTTP/3 里就都消失了,比如 RST_STREAM、WINDOW_UPDATE、PING 等。

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB