Bladeren bron

Merge branch 'main' of https://github.com/usuiforhe/MarkdownLog

usuiforhe 3 jaren geleden
bovenliggende
commit
4010b370f8

BIN
104/Image/104_01.png


BIN
104/Image/104_02.png


BIN
104/Image/104_03.png


BIN
104/Image/104_04.png


BIN
104/Image/104_05.png


BIN
104/Image/104_06.png


BIN
104/Image/104_07.png


BIN
104/Image/104_08.png


BIN
104/Image/104_09.png


BIN
104/Image/104_10.png


+ 370 - 0
104/README.md

@@ -0,0 +1,370 @@
+# 104
+
+## 引擎架构分层
+
+1. 工具层(引擎提供的各种编辑器)
+2. 功能层(让游戏世界动起来、看得见)
+
+![](./Image/104_01.png)
+
+> 动画、相机、渲染、物理、脚本、UI等
+
+3. 资源层(加载、管理资源)
+
+> PSD、MAX、Mp3、XML、MPEG、JSON等文件
+
+4. 核心代码层
+
+> 容器、内存分配、垃圾回收、线程池等
+
+5. 平台层
+
+> Android、PC、Mac...  
+> 手柄、鼠标、键盘、方向盘...
+
+### 资源层
+
+**以动画系统(Animation System)为例**
+
+一套动画资源可能包括:骨骼信息、动作信息、蒙皮信息、数据文件等
+
+在**源文件**中,这些文件可能包含额外的一些编辑时会用到的信息,这些信息在游戏中是无用的
+
+> 比如同一篇文章,用word和txt两种格式存储的文件大小是完全不同的
+
+又或者,原本的文件格式是不合适的,比如图片的PNG和JPEG是不能直接给GPU进行计算绘制的,需要将其转换成可以直接通过GPU绘制的文件格式
+
+因此需要将这些离线文件转变成**引擎资产**
+
+同样是上面动画资源的例子,一个文件可能关联其他多个文件,因此这些文件之间需要维系关联信息
+
+![关联的文件](./Image/104_02.png)
+
+```xml
+<character name="robot" GUID="81-02985-0192835">
+    <geometry>
+        <mesh file_path="robot.mesh.ast"/>
+        <texture>
+            <abledo_texture file_path="robot_ambient.texture.ast"/>
+            <roughness_texture file_path="robot_roughness.texture.ast"/>
+        </texture>
+        <material file_path="robot.material.ast" />
+    </geometery>
+    <animation>
+        <animation_clip name="stand">
+            <clip_file path="robot_stand.animation.ast" />
+        </animation_clip>
+        <animation_clip name="walk">
+            <clip_file path="robot_walk.animation.ast" />
+        </animation_clip>
+    </animation>
+</character>
+```
+
+对于上面图片中的一组关联文件,使用上面的XML配置可以记录关联信息
+
+| key | 意义 |
+| --- | --- |
+| character | 表示角色 |
+| geometry | 几何体信息 |
+| mesh | 网格信息 |
+| texture | 贴图信息 |
+| material | 材质信息 |
+| animation | 动作信息 |
+
+注意上述XML中 `character` 标签中的 **GUID** 属性,表示唯一编号,通过该值可以唯一确定整个游戏项目中的一个文件,即该值全局唯一
+
+通过记录**GUID**,即使文件位置被挪动,也可以快速定位,重新配置上述XML文件
+
+综上所述,定义**引擎资产**的好处很多
+1. 去除文件冗余数据,减少文件大小
+2. 转换文件到合适的格式
+3. 关联多文件之间的信息
+4. 定义**GUID**确定唯一文件标识
+
+一般游戏引擎都有**资源管理器**,属于一个虚拟的文件系统,通过路径加载、卸载各种资源;同时还要通过**hanle系统**管理资源的生命周期和各种引用
+
+资源层一般就是管理资源的整个生命周期
+
+![资源管理器支持的文件](./Image/104_03.png)
+
+在整个游戏的过程中,资源会被不停的加载、卸载,如果某个瞬间加载、卸载的资源数目过多,会严重影响游戏的体验,因此资源管理器在游戏引擎中是十分重要的
+
+### 功能层
+
+![tick](./Image/104_04.png)
+
+如何让游戏世界动起来,**tick**就是关键
+
+```cpp
+
+void tickLogic(float delta_time) {
+    tickCamera(delta_time);
+    tickMotor(delta_time);
+    tickController(delta_time);
+    tickAnimation(delta_time);
+    tickPhysics(delta_time)l;
+    /* ... */
+}
+
+void tickRender(float delta_time){
+    tickRenderCamera();
+    culling();
+    rendering();
+    postprocess();
+    present();
+}
+
+void tickMain(float delta_time) {
+    while(!exit_flag) {
+        tickLogic(delta_time);
+        tickRender(delta_time);
+    }
+}
+```
+
+一般会先tick计算Logic(逻辑),然后再计算Render(渲染)
+
+因此,如何让一个角色动起来:
+1. 逻辑帧匹配角色的动画帧
+2. 驱动角色骨骼和蒙皮
+3. 在每一个渲染帧中完成角色的渲染作业
+
+除此了tick外,功能层还有多线程任务分配等操作
+
+![目前游戏引擎的多线程任务分配](./Image/104_05.png)
+
+> 目前游戏引擎的多线程任务分配
+
+![理想的满线程使用的任务分配](./Image/104_06.png)
+
+> 理想的满线程使用的任务分配,但是因为任务之间的关联性,很多信息的计算必须在某个任务执行完毕之后,所以目前这种线程的分配方式不太好实现
+
+### 核心层
+
+核心层首当其冲就是**数学库**,各种值的计算、转换等操作都依赖数学库
+
+> 游戏逻辑、绘制、物理等
+
+**SIMD**全称Single Instruction Multiple Data,单指令多数据流,能够通过一条指令执行多次计算
+
+> 以加法指令为例,单指令单数据(SISD)的CPU对加法指令译码后,执行部件先访问内存,取得第一个操作数;之后再一次访问内存,取得第二个操作数;随后才能进行求和运算。而在SIMD型的CPU中,指令译码后几个执行部件同时访问内存,一次性获得所有操作数进行运算。这个特点使SIMD特别适合于多媒体应用等数据密集型运算
+
+除了**数学库**,核心层还提供各种常用的**数据结构**
+- Array
+- LinkList
+- Queue
+- Heap
+- Tree
+- Graph
+- Stack
+- Hashing
+- Skeleton Tree
+- Animation Frame Sequence 
+- ...
+
+当然还有最重要的**内存管理**
+游戏引擎会一次性申请一大堆内存自行管理(**内存池**),以此来追求最大的效率
+
+> 因为内存都在一起,所以**Cache的命中率**会更高
+
+当然还要注意的就是**内存对齐**
+
+### 平台层
+
+平台层就是为了使开发者不用关注不同平台之间差异,从而提高开发效率
+
+1. 系统不同
+
+不同平台的文件路径、正反斜线都有区别
+
+2. 图形渲染不同
+
+不同平台的图形API也不同:OpenGLES、DirectX11、DirectX12等等
+
+> RHI 硬件渲染接口,重新定义一层渲染API,将不同硬件的SDK的区别封装起来
+
+3. CPU不同
+
+![不同的CPU](./Image/104_07.png)
+
+需要考虑哪个算法在哪个核心中计算更合理(部分CPU特别提供计算核心)
+
+### 工具层
+
+给用户提供编辑各种文件的界面,游戏场景的可视化、动画资源预览、可编程的shader等
+
+工具层是依赖游戏引擎的,它具有创建、编辑、交换游戏资源的能力
+
+工具层的开发方式比较灵活,游戏引擎最佳是C++(效率),但是工具层任何语言都是可以的考虑的
+
+除了引擎工具层提供的资产生产工具外,还有其他厂商提供的开发工具
+
+**DCC** => Digital Content Creation,数字资产
+
+无论是自己通过引擎创建的,或者通过其他厂商工具创建的**DCC**,全部都可以通过游戏引擎的**资源层**导入到游戏引擎中,通过**Asset Conditioning Pipeline**,即各种导入、导出工具
+
+#### 总结
+
+与网络分层类似,每个层次只关注自己层次的功能实现,低层次为高层次提供服务,高层次不需要知道低层次的存在
+
+一般来说高层次的发展会更加快速,低层次发展会较慢
+
+## 构建游戏世界
+
+### GameObject和Component
+
+1. 动态物:游戏中会运动的物体,例如:人
+2. 静态物:游戏中不会移动的物体,例如:房子
+3. 地形系统
+4. 植被
+5. 天空
+
+游戏中所有的物体都可以抽象成GO->**GameObject**,游戏最重要的就是管理这些GameObject
+
+那么如何定义游戏中的对象?
+
+最经典的做法就是**继承**,鸭子=>会叫的鸭子=>会飞的鸭子=>...
+
+但是随着游戏越做越复杂,很多东西并没有清晰的父子关系:水陆两栖坦克(坦克+巡逻艇, 坦克派生自汽车,巡逻艇派生自船)
+
+为了应对**继承**带来的一些问题,现代游戏引擎提供了**组件化**的开发方法,将行为/能力拆分到组件中,然后添加到对象上
+
+对于**组件化**开发,最容易想到的就是枪械了,比如吃鸡中的1、2、3、4倍镜,各种握把等,都可以理解为组件,为枪械提供了某些能力
+
+```cpp
+class BaseComponent {
+
+}
+
+class TransformComponnet : public BaseComponent{
+
+};
+
+class AIComponent : public BaseComponnet {
+
+};
+
+class FlyComponent : public BaseComponent {
+
+};
+
+class GameobjectBase {
+    vector<BaseComponent*> Components;
+}
+
+class FlyRobot : public GameobjectBase {
+    Components = [FlyComponent, AIComponent, TransformComponnet];
+}
+```
+
+角色能力统一封装到组件中,在角色中存储这些组件,即代表角色存在这些能力,此时想要添加或者修改能力只需要添加或者替换组件即可实现
+
+![U3D和UE对组件的支持](./Image/104_08.png)
+
+> U3D和UE中的Component
+
+**综上**:游戏中的任何物体都是一个GameObject,每个GameObject由各种不同的Component组成
+
+### GameObject之间的交互
+
+最简单的写法就是
+
+```cpp
+void Bomb::OnExpode(){
+    // 炸弹要自己判断各种类型的处理方式
+    // ...
+    switch(touch_type){
+        case TouchType.Humen:
+            // 扣血
+            break;
+        case TouchType.Tank:
+            // 扣血
+            break;
+        case TouchType.Stone:
+            // 不做处理
+            break;
+    }
+    // ...
+}
+```
+
+上面就是最暴力的写法,爆炸的时候获得爆炸影响的对象,用switch判断其类型,走不同的逻辑
+
+这种写法最大的问题就是,**不好维护**,当后续对象类型添加到几百种,总不能写几百种Switch-case
+
+此时,比较好的解决方案其实是 **事件**(Event) 机制
+
+参考设计模式:观察者模式,使用事件的好处就是只是通知被影响的物体发生了何种事情,由被通知的对象自行做后续处理
+
+```cpp
+void Bomb::OnExpode(){
+    // ...
+    sendExploadEvent(go_actor); // 炸弹只做事件分发,不做数据处理
+    // ...
+}
+```
+
+```cpp
+DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FActorComponentActivatedSignature, UActorComponent, OnComponentActivated, UActorComponent*, Component, bool, bReset);
+DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(FActorComponentDeactivateSignature, UActorComponent, OnComponentDeactivated, UActorComponent*, Component);
+
+DECLARE_MULTICAST_DELEGATE_OneParam(FActorComponentGlobalCreatePhysicsSignature, UActorComponent*);
+DECLARE_MULTICAST_DELEGATE_OneParam(FActorComponentGlobalDestroyPhysicsSignature, UActorComponent*);
+```
+
+> 虚幻中的事件声明
+
+### GameObject的管理
+
+每个GameObject都需要一个唯一ID 作为唯一标识  
+每个GameObject都需要一个坐标
+
+> 这里GameObject是场景中的物体,并非UE中的UObject这种,更像是UE中的AActor
+
+![八叉树](./Image/104_09.png)
+
+上图中左边为普通网格管理,右边为八叉树管理
+
+因为地图中的物体并不是均匀分布的,导致如果采用网格管理的方式,会出现一些网格中的对象过多,另一部分网格中却没有对象
+
+
+当然除了**八叉树**外,还有其他的算法去做场景对象管理
+
+![场景对象管理](./Image/104_10.png)
+
+当时游戏发开并没有完全通用的解决方案,还是不同情况采用更为合适的方案为宜
+
+### 其他
+
+当子GameObject绑定到父GameObject后,tick时要先计算父GameObject,再计算子GameObject
+
+再比如消息的传递,A给B发送信息,同时B也给A发送了信息,此时微观上其实是由先后顺序的,但是如果信息的处理是交给两个核心处理,那么可能这次是A先收到信息,下次是B先收到信息,这样程序的运行结果可能不同,导致严重的问题出现
+
+所以,很多时候会有信息管理器,将信息统一发送到一个管理器中,再由管理器去根据顺序统一发送信息
+
+因此游戏引擎中,**时序**是一个很重要的问题,需要着重考虑
+
+## 游戏引擎的渲染
+
+游戏绘制系统会遇到的问题
+1. 成千上万的渲染对象和千奇百怪的渲染类型(水体、植被、角色、云等绘制算法各不相同)
+2. 深度适配当代硬件(CPU、GPU)
+3. 如何把计算时间维持在固定的时间内,以此来维持帧率
+4. 游戏渲染不能占用100%的CPU资源,否则其他模块无法计算
+
+ ### 基础游戏渲染
+
+1. 传入顶点的3D空间坐标
+2. 映射顶点的3D空间坐标到屏幕空间上
+3. 三角面片
+4. 栅格化
+5. 贴图
+6. 输出
+
+ ### 材质、Shader和光照
+
+ ### 特殊的渲染(后处理)
+
+ ### 管道
+

BIN
cpp/img/Net_1.png


BIN
cpp/img/Net_10.png


BIN
cpp/img/Net_11.png


BIN
cpp/img/Net_12.png


BIN
cpp/img/Net_13.png


BIN
cpp/img/Net_2.png


BIN
cpp/img/Net_3.png


BIN
cpp/img/Net_4.png


BIN
cpp/img/Net_5.png


BIN
cpp/img/Net_6.png


BIN
cpp/img/Net_7.png


BIN
cpp/img/Net_8.png


BIN
cpp/img/Net_9.png


BIN
cpp/unpv13e.tar.gz


+ 1157 - 0
cpp/泛型与模板.md

@@ -0,0 +1,1157 @@
+# 简单基础
+
+## 函数模板
+
+## 自变量推到
+
+```cpp
+template <typename T>
+inline T const& max(T const& a, T const& b)
+{
+	return a < b ? b : a;
+}
+
+max(4, 7);      // Success
+max(4, 7.2);    // Error
+```
+
+因为输入的a是int类型,但是b确实double类型,两个类型不同但是只有一个T,所以报错
+
+1. 解决方法:指定类型
+
+```cpp
+max<double>(4, 7.2);
+```
+
+2. 解决方法:不同参数不同类型
+
+```cpp
+template <typename T1, typename T2>
+inline T1 const& max(T1 const& a, T2 const& b)
+{
+	return a < b ? b : a;
+}
+```
+
+### 模板参数
+
+```cpp
+template<typename T>
+inline T max(T a, T b)
+```
+
+- 模板函数的两种参数
+  - `template<typename T>` 中T就是模板参数
+  - `max(T a, T b)`中的a、b就是调用参数
+
+```cpp
+template <typename T1, typename T2, typename T3>
+inline T1 const& max(T1 const& a, T3 const& b)
+{
+	return a < b ? b : a;
+}
+
+max(1, 2);
+```
+
+**自变量推导机制**并不对返回类型进行匹配,所以这里不能推导出T1的数据类型,需要手动指出`max<int, double, double>(4, 4.2)`
+
+```cpp
+template <typename TR, typename T1, typename T2>
+inline TR const& max(T1 const& a, T3 const& b)
+{
+	return a < b ? b : a;
+}
+
+max<double>(1, 2);
+```
+
+TR是手动指定的double,T1、T2是可以被自变量推导出来的
+
+### 重载函数模板
+
+```cpp
+// 传回两个 int 中的较大者
+inline int const& max(int const& a, int const& b)
+{
+	return a < b ? b : a;
+}
+// 传回两任意类型的数值中的较大者
+template <typename T>
+inline T const& max(T const& a, T const& b)
+{
+	return a < b ? b : a;
+}
+// 传回三个任意类型值中的最大者
+template <typename T>
+inline T const& max(T const& a, T const& b, T const& c)
+{
+	return ::max(::max(a, b), c);
+}
+
+int main() {
+	::max(7, 42, 68);       // 调用「接受三个自变量」的函数
+	::max(7.0, 42.0);       // 调用 max<double>(经由自变量推导)
+	::max('a', 'b');        // 调用 max<char>(经由自变量推导)
+	::max(7, 42);           // 调用「接受两个 int 自变量」的 non-template 函数
+	::max<>(7, 42);         // 调用 max<int>(经由自变量推导)
+	::max<double>(7, 42);   // 调用 max<double>(无需自变量推导)
+	::max('a', 42.7);       // 调用「接受两个 int 自变量」的 non-template 函数
+
+	return 0;
+}
+```
+
+> 一般来说,不同的重载形式之间最好不存在 **绝对必要的差异**,各重载形式之间应该只存在 **参数个数不同** 或 **参数类型的明确不同**,所以不要出现一个参数是引用、另一个不是引用的情况
+
+## 类模板
+
+### 声明
+
+声名类模板
+
+```cpp
+template <typename T>
+class Stack {
+    private:
+        std::vector<T> elems;   // 元素
+    public:
+        void push(T const&);    // push 元素
+        void pop();             // pop 元素
+        T top() const;          // 传回最顶端元素
+        bool empty() const {    // stack 是否为空
+            return elems.empty();
+        }
+}; 
+
+template <typename T>
+void Stack<T>::push (T const& elem)
+{
+    elems.push_back(elem);          // 追加(附于尾)
+}
+```
+
+### 使用类模板
+
+```cpp
+Stack<int> intStack;
+intStack.push(7);
+```
+
+`Stack<int>`将类模板中的T替换成int,Stack内部使用可容纳int作为元素的vector
+
+唯有被调用到的成员函数,才会实例化,这样做可以节省时间和空间;另一方面,可以实例一个类模板,并且实例化的类不需要完整支持模板类中与该类有关的所有操作
+
+> 比如,一些自定义类没有实现 `operator <`,类模板中可能有一些函数中有比较大小的操作,那么只要不执行这些函数,以自定义类为T的类模板就不会报错
+
+### 特化模板
+
+可以用模板实参来特化类模板,与函数模板的重载类似,通过特化类模板可以是实现**基于某种特定类型**的实现,或者克服某种特定类型在实例化类模板时所出现的不足
+
+> 如果要特化类模板,需要特化该类所有成员函数,虽然也可以指特化某个成员函数,但是这个作为没有特化整个类,也就没有特化类模板
+
+```cpp
+template<>
+class Stack<std::string> {
+    private:
+        std::deque<std::string> elems;  // 元素
+    public:
+        void push(std::string const&);  // push 元素
+        void pop();                     // pop 元素
+        std::string top() const;        // 传回 stack 最顶端元素
+        bool empty() const {            // stack 是否为空
+            return elems.empty();
+        }
+}; 
+
+void Stack<std::string>::push (std::string const& elem)
+{
+    elems.push_back(elem);              // 追加元素
+}
+```
+
+这里使用`deque`来替代`vector`管理`Stack`内部元素,说明了特化的实现可以和基本类模板的实现完全不同
+
+### 局部特化,偏特化
+
+类模板可以被局部特化,在特定的环境下指定类模板的特定实现,并且要求某些模板参数仍然必须有用户类定义
+
+```cpp
+template <typename T1, typename T2>
+class MyClass {
+    // ...
+}; 
+```
+
+可以存在下面两种偏特化
+
+```cpp
+template <typename T>
+class MyClass<T,T> {
+    // ...
+};
+
+// 偏特化:第二个类型为 int 
+template <typename T> 
+class MyClass<T,int> {
+    // ...
+}; 
+
+// 偏特化 两个template parameter均为指针
+template <typename T1, typename T2>
+class MyClass<T1*, T2*> {
+    // ...
+}
+```
+
+各个模板的使用
+
+```cpp
+MyClass<int,float>      mif;       // 使用 MyClass<T1,T2>
+MyClass<float,float>    mff;       // 使用 MyClass<T,T>
+MyClass<float,int>      mfi;       // 使用 MyClass<T,int>
+MyClass<int*,float*>    mp;        // 使用MyClass<T1*,T2*>
+```
+
+如果多个局部特化同等程度地匹配某个声明,那么存在二义性,会报错
+
+```cpp
+MyClass<int,int>    m;  // 错误:同时匹配 MyClass<T,T> 和 MyClass<T,int>
+MyClass<int*,int*>  m;  // 错误:同时匹配 MyClass<T,T> 和 MyClass<T1*,T2*>
+```
+
+### 缺省模板参数(预设模板自变量)
+
+可以为模板参数定义缺省值,这些值就被称为缺省模板参数,而且他们还可以引用之前的模板参数
+
+```cpp
+template <typename T, typename CONT = std::vector<T>>
+class Stack {
+    private:
+        CONT elems;             // 元素
+    public:
+        void push(T const&);    // push 元素
+        void pop();             // pop 元素
+        T top() const;          // 传回最顶端元素
+        bool empty() const {    // stack 是否为空
+            return elems.empty();
+        }
+};
+
+template <typename T, typename CONT>
+void Stack<T,CONT>::push (T const& elem)
+{
+    elems.push_back(elem);      // 追加元素
+} 
+```
+
+可以像之前单一模板参数一样使用`Stack<int>`,也可以指定容器类型`Stack<double, std::deque<double>>` 
+
+## 非类型模板参数
+
+对于钼函数模板和类模板,模板参数并不限于类型,**普通值也可以作为模板参数**
+
+### 非类型的类模板参数
+
+```cpp
+template <typename T, int MAXSIZE>
+class Stack {
+    private:
+        T elems[MAXSIZE];       // 元素
+        int numElems{0};        // 当前的元素个数
+    public:
+        Stack();                // 构造函数
+        void push(T const&);    // push 元素
+        void pop();             // pop 元素
+        T top() const;          // 传回 stack 顶端元素
+        bool empty() const {    // stack 是否为空
+            return numElems == 0;
+        } 
+        bool full() const {     // stack 是否已满
+            return numElems == MAXSIZE;
+        }
+}; 
+
+Stack<int, 100> stack1;
+Stack<int, 200> stack2;
+```
+
+这里`MAXSIZE`是新加入的第二个模板参数,类型为int,指定了数组最多可包含的栈元素
+
+但是这里的`stack1`和`stack2`属于不同的类型,而且这两种类型之间也不存在隐式或者显示的类型转换,也不能互相赋值
+
+### 非类型的函数模板参数
+
+也可以为函数模板定义非类型参数
+
+```cpp
+template<typename T, int Val>
+T addValue(T const &x){
+    return x + Val;
+}
+
+int p = addValue<int, 10>(1);
+```
+
+### 非类型模板参数的限制
+
+非类型模板参数是有限制的,通常可以是常整数(包括枚举)或者指向外部链接对象的指针
+
+**浮点数和类型对象是不允许作为非类型模板参数的**
+
+```cpp
+template<double Val>
+double process(double v){
+    return v * Val;
+}
+
+template<std::string name>
+class MyClass{
+    // ...
+};
+```
+
+> 上述都是错误的使用方法
+
+之所以不能使用浮点数(包括简单的常量浮点表达式)作为模板参数有历史原因存在(未来可能支持)
+
+`std::string`由于字符串文字是内部链接对象,所以不能使用它们来作为模板参数
+
+```cpp
+template<char const* name>
+class MyClass{
+    // ...
+};
+
+extern char const s[] = "hello";
+MyClass<s> x;   // ok
+```
+
+全局字符数组s由`hello`初始化,并且是extern的外部链接对象
+
+## 技巧性基础知识
+
+### typename
+
+模板中引入typename是为了说明:模板内部的标识符可以是一个类型
+
+```cpp
+template <typename T>
+class MyClass{
+    typename T::SubType *ptr;
+    // ...
+}
+```
+
+此处使用`typename`被用来说明:SubType是定义域类T内部的一种类型,因此ptr是指向T::SubType类型的指针
+
+如果不使用`typename`,那么SubType会被认为是一个**静态成员**,那么`T::SubType * ptr`会被认为是`T::SubType`和`ptr`的乘积
+
+通常而言,当某个依赖于模板参数的名称是一个类型时,就应该使用typename
+
+### .template
+
+```cpp
+template<int N>
+void printBitset(std::bitset<N> const& bs)      // success
+{
+	std::cout << bs.template to_string<char>();
+}
+
+template<int N>
+void printBitset(std::bitset<N> const& bs)      // error
+{
+	std::cout << bs.to_string<char>();
+}
+```
+
+这里出现了一个东西`.template`,这个东西一般用在模板函数中,它表示后面对象调用的另一个模板函数`to_string<char>()`中的`<`不是小于号,而是模板实参列表的起始符号
+
+只有当`.`或`->`之前的对象构建去角色某个模板参数列表时,才需要注意这个问题(该问题在VS2019 C++14上并未出现,但是在旧版本和GCC上出现)
+
+### 使用 this->
+
+对于具有基类的类模板,自身使用名称`x`并不一定等同于`this->x`
+
+```cpp
+template<typename T>
+class Base{
+    public:
+        void BaseFunc(){
+            std::cout << "Base" << std::endl;
+        }
+};
+
+template<typename T>
+class Derived : Base<T>{
+    public:
+        void foo(){
+            BaseFunc();
+        }
+};
+
+Derived<int> PP;
+PP.foo();       // Error: “BaseFunc”: 找不到标识符	EmptyCpp
+```
+
+对于那些在基类中声明,并且依赖于模板参数的符号(函数或者变量等),都应该在其之前使用`this->`或者`Base<T>::`限定
+
+### 成员模板
+
+类成员也可以是模板。嵌套类和成员你函数都可以作为模板
+
+```cpp
+Stack<float> x, y;
+Stack<int> z;
+
+x = y;      // OK 类型相同
+x = z;      // Error 类型不同
+```
+
+也可以通过定义一个身为模板的赋值运算符
+
+```cpp
+template <typename T>
+class Stack {
+private:
+    std::deque<T> elems;   // 元素
+public:
+    template<typename T2>
+    Stack<T>& operator = (Stack<T2> const&);
+};
+
+template<typename T>
+template<typename T2>
+Stack<T>& Stack<T>::operator=(Stack<T2> const& Other)
+{
+    if ((void*)this == (void*)&Other) {   // 判断是否赋值给自己
+        return *this;
+    }
+    Stack<T2> tmp(Other);               // 建立 Other 的一份拷贝
+    elems.clear();                      // 移除所有现有元素
+    while (!tmp.elems.empty()) {              // 复制所有元素
+        elems.push_back(tmp.elems.front());
+        tmp.elems.pop_front();
+    }
+    return *this;
+}
+```
+
+这里代码没啥大问题,只一点在`operator=`中,无法访问Other的elems属性,因为它是私有的。因为`Stack<int>`和`Stack<flaot>`是两种不同的类型
+
+**但是**,如果是相同类型,因为自己是自己的友元,所以类型相同时是可以访问彼此的私有成员属性的
+
+> 这里可以把elems改为public,或者提供对外访问接口
+
+### 模板的模板参数
+
+```cpp
+template <typename T, typename CONT = std::vector<T>>
+class Stack {
+    private:
+        CONT elems;             // 元素
+    public:
+        void push(T const&);    // push 元素
+        void pop();             // pop 元素
+        T top() const;          // 传回最顶端元素
+        bool empty() const {    // stack 是否为空
+            return elems.empty();
+        }
+};
+
+Stack<int, std::vector<int>> S1;    // Success
+Stack<int, std::vector> S2;         // Error
+```
+
+如果想要使用一个和缺省值不同的内部容器,必须两次指定元素类型
+
+然而借助**模板的模板参数**,就可以只指定容器的类型,不用指定容器所含元素类型
+
+```cpp
+template <typename T,
+    template <typename ELEM> class CONT = std::deque>
+class Stack_V {
+private:
+    CONT<T> elems;              // 元素
+public:
+	void push(const T& val);
+    bool empty() const {        // stack 是否为空
+        return elems.empty();
+    }
+};
+
+template <typename T,
+    template <typename> class CONT>
+void Stack_V<T, CONT>::push(const T& val)
+{
+    elems.push_back(val);
+}
+```
+
+不同之处在于第二个模板参数被声明为一个模板`template <typename ELEM> class CONT`,缺省值也从`std::vector<T>`变为`std::vector`,定义从`CONT elems`变为`CONT<T> elems`
+
+作为模板参数的声明时,通常可以用`typename`来替换关键字`class`,但是这里不行,因为这里CONT时为了定义一个类,因此只能使用`class`
+
+又因为`template <typename ELEM> class CONT`中的ELEM一般来说并不会用到,所以可以省略写法
+
+```cpp
+template <typename T,
+    template <typename> class CONT = std::deque>
+class Stack_V {
+    // ...
+};
+```
+
+然后就是,函数模板并**不支持模板的模板参数**
+
+### 零初始化
+
+对于int、double或者指针等基本类型,并不存在用一个有用的缺省值对他们进行初始化的缺省构造函数,相反任何未被初始化的局部变量都有一个不确定值
+
+```cpp
+void foo(){
+    int x;  // 不确定值
+    int* p; // p 指向某块未知内存
+}
+```
+
+那么在模板中,一般来说都希望模板类型的变量都可以使用缺省值初始化,可是内建类型并不能满足需求
+
+```cpp
+template<typename T>
+void foo(){
+    T x;    // x如果是内建类型,则x本身就是个不确定值
+}
+```
+
+所以应该显示的调用内建类型的缺省构造函数,并把缺省值设置为0
+
+> 比如`int()`就是0
+
+```cpp
+template<typename T>
+void foo(){
+    T x = T();
+}
+```
+
+对于模板类,在用某种类型实例化该模板后,为了确定所有的成员都已经初始化完毕,需要定义一个缺省构造函数
+
+```cpp
+template<typename T>
+class MyClass{
+    private:
+        T x;
+    public:
+        MyClass() : x(){
+
+        }
+}
+```
+
+### 使用字符串作为函数模板的实参
+
+```cpp
+template <typename T>
+inline T const& max(T const& a, T const& b)
+{
+	return a < b ? b : a;
+}
+
+std::string s = "peach";
+::max("peach", "apple");	// OK:类型相同
+::max("tomato", "apple");	// ERROR:类型不同
+::max("apple", s);	        // ERROR:类型不同
+```
+
+`"peach"`是`const char[6]`,`"apple"`是`const char[6]`,`"tomato"`是`const char[7]`,所以很明显的类型不同报错
+
+但是如果声明的不是**引用参数**
+
+```cpp
+template <typename T>
+inline T const& max(T const a, T const b)
+{
+	return a < b ? b : a;
+}
+
+std::string s = "peach";
+::max("peach", "apple");	// OK:类型相同
+::max("tomato", "apple");	// OK:退化为相同的类型
+::max("apple", s);	        // ERROR:类型不同
+```
+
+对于非引用类型的参数,在实参演绎的过程中,会出现数组到指针(array-to-pointer)的类型转换
+
+```cpp
+template<typename T>
+void ref(const T& x) {
+    std::cout << "x is ref " << typeid(x).name() << std::endl;
+}
+template<typename T>
+void unref(T x) {
+    std::cout << "x is unref " << typeid(x).name() << std::endl;
+}
+
+ref("hello");
+unref("hello");
+```
+
+> x is ref char const [6]  
+> x is unref char const *
+
+如果遇到关于字符数组和字符串指针之间不匹配的问题,可以往这方面考虑,根据实际情况有不同的解决方法
+
+1. 使用非引用参数,取代引用参数(会出现无用的拷贝)
+2. 重载,编写接收引用参数和非引用参数的两个重载函数
+3. 对具体类型进行重载
+4. 强制要求程序员使用显示类型转换
+5. 重载数组类型
+
+```cpp
+template<typename T, int N, int M>
+T const* max(T const(&a)[N], T const(&b)[M]) {
+	return a < b ? b : a;
+}
+```
+
+## 简单实战
+
+### 包含模型
+
+绝大多数的程序员这样组织代码结构
+1. 类和其他类型的声明放在头文件(.h .hh .hxx .hpp)
+2. 对于全局变量和非内联函数,只声明在头文件中,定义则位于(.c .cc .cxx)文件
+
+但是,模板不行,当模板的声明和定义不在同一个文件中,会触发**链接错误**
+
+因为调用模板时,模板的定义还没被实例化,为了使模板真正被实例化,编译器必须知道应该实例化哪个定义以及要基于哪个模板实参来进行实例化,可是这两部分信息位于分开编译的不同文件里面
+
+对于上面的问题,通常采取**对待宏**或**内联函数**的解决方法
+
+1. 将实现.cpp文件添加到声明.h的末尾
+2. 在每个使用模板的文件中,都包含进实现的.cpp文件
+3. 将定义和声明写在一起
+
+```cpp
+#pragma once
+
+// 声明
+template<typename T>
+void print_typeof(T const&);
+
+// 实现
+template<typename T>
+void print_typeof(T const& x){
+    std::cout << typeif(x).name() << std::endl;
+}
+
+```
+
+> 将上面的这种组织方式称为**包含模型**
+
+包含模型明显增加了包含头文件的开销,这也是包含模型最大的不足之处
+
+### 显式实例化
+
+### 分离模型
+
+## 一些术语
+
+- 类模板:该类是一个模板
+- 模板类(通常由下面三种含义)
+  - 作为类模板的同义词
+  - 从模板产生的类
+  - 具有一个template-id名称的类
+
+```cpp
+template<typename T1, typename T2>  // 基本的类模板
+class MyClass{      
+    // ...
+}
+
+template<>                          // 显式特化
+class MyClass<std::string, float>{
+    // ...
+}
+
+template<typename T>                // 局部特化
+class MyClass<T, T> {
+    // ...
+}
+```
+
+```cpp
+class C;            // 类C的声明
+void f(int p);      // 函数f的声明
+extern int v;       // 变量v的声明
+```
+
+```cpp
+template<typename T, int N>
+class ArrayInClass{
+    public:
+        T array[N];
+};
+
+ArrayInClass<int, 10> temp;
+```
+
+- 模板参数是指:位于模板声明或定义内部,关键字template后面所列举的名称(比如上面的N和T)
+- 模板实参是指:用替换模板参数的各个对象(上面的int和10)
+
+# 深入模板
+
+## 深入基础
+
+### 参数化声明
+
+C++支持两种基本类型的模板:类模板和函数模板
+
+```cpp
+template<typename T>
+class MyList {							// 命名空间内的类模板
+public:
+    template<typename T2>				// 成员函数模板
+    MyList(MyList<T2> const&);			// 构造函数
+    // ...
+};
+
+
+template<typename T>
+template<typename T2>					 
+MyList<T>::MyList(MyList<T2> const&)	// 位于类外部的成员
+{										// 函数模板的定义
+}
+
+template<typename T>
+int Length(MyList<T> const&);			// 位于外部命名空间作用域的函数模板
+
+class Collection {						
+    template<typename T>				// 位于类内部的成员类模板
+    class Node {
+        // ...
+    };
+
+    template<typename T>				// 另一个作为成员的类模板
+    class Handle;
+
+    template<typename T>				// 位于类内部的成员函数模板的定义
+    T* allco() {						// 显式内联函数
+        // ...
+    }
+};
+
+template<typename T>					// 类外部定义的成员类模板
+class Collection::Handle {
+    // ...
+};
+```
+
+除了上面的,联合模板也是允许的(通常被当作类模板)
+
+```cpp
+template<typename T>
+union AllocChank{
+    T object;
+    unsigned char bytes[sizeof(T)];
+}
+```
+
+和普通函数一样,函数模板声明也可以具有缺省调用实参
+
+```cpp
+template<typename T>
+void report_top(Stack<T> const&, int number = 10);
+
+template<typename T>
+void fill(Array<T>*, T cosnt & = T())
+```
+
+不过使用`T()`去初始化缺省调用实参也可能存在问题,比如下面的代码
+
+```cpp
+class Value{
+    public:
+        Value(int);
+}
+void init(Array<Value> *arr){
+    Value zero(0);
+    fill(arr, zero);    // 正确的
+    fill(arr);          // 错误的 因为不存在Value()的无参构造函数
+}
+```
+
+除了上述两种基本类型的模板之外,还可以使用相似的符号来参数化其他的3种声明
+
+1. 类模板的成员函数的定义
+2. 类模板的嵌套类成员的定义
+3. 类模板的静态数据成员的定义
+
+```cpp
+template <int I>
+class CupBoard {
+    void open();                //译注:隶属于 CupBoard class template
+    class Shelf;                //译注:隶属于 CupBoard class template
+    static double total_weight; //译注:隶属于 CupBoard class template
+    // ...
+};
+
+template <int I>
+void CupBoard<I>::open()
+{
+    // ...
+}
+
+template <int I>
+class CupBoard<I>::Shelf {
+    // ...
+}; 
+
+template <int I>
+double CupBoard<I>::total_weight = 0.0;
+```
+
+**成员函数不能被声明为虚函数**,因为虚函数调用机制的普遍实现都使用了一个大小固定的表,每个虚函数都对对应表的一个入口
+
+但是,成员函数模板的实例化个数,要等到整个程序都翻译完毕才能确定,这就和表的大小(固定的)发生了冲突
+
+不过,类模板的普通成员函数可以是虚函数,因为当类被实例化之后,他们的个数是固定的
+
+```cpp
+template<typename T>
+class TestClass
+{
+public:
+	virtual ~TestClass();               // Success
+
+	template<typename T2>
+	virtual void Copy(const T2&);       // Error : 成员函数模板不能是虚拟的
+};
+```
+
+### 模板参数
+
+3种模板参数
+1. 类型参数
+2. 非类型参数
+3. 模板的模板参数
+
+**类型参数**是通过关键字typename或者class引入的,关键字后面必须是一个简单的标识符,使用逗号隔开多个标识符,等号(=)表示缺省模板实参
+
+在模板声明内部,类型参数的作用类似于typedef。如果T是一个模板参数,就不能使用注入`class T`等形式的修饰名称,即使T是一个要被class类型替换的参数也不可以
+
+```cpp
+template<typename Allocator>
+class List{
+    class Allocator* allocator;     // Error
+    friend class Allocator;         // Error
+};
+```
+
+**非类型参数**表示的是 在编译器或链接期可以确定的常值
+
+整个常值必须是
+1. 整数或者枚举类型
+2. 指针类型
+3. 引用类型
+
+所有其他的类型都不允许作为非类型参数使用
+
+```cpp
+template<typename T,                            // 类型参数
+            typename T::Allocator* Allocator>   // 非类型参数
+
+template<int buf[5]>
+class Lexer;
+// 上面等价于下面
+template<int *buf>
+class Lexer;
+
+template<int const length>
+class Buffer;
+// 上面等同于下面 const是无效的
+template<int length>
+class Buffer;
+```
+
+非类型模板参数只能是右值,不能被取地址,不能被赋值
+
+**模板的模板参数**是代表类模板的占位符,不能使用关键字class或union
+
+```cpp
+template<template<typename X> class C>  // Success
+void f(C<int>* p);
+
+template<template<typename X> struct C> // Error
+void f(C<int>* p);
+
+template<template<typename X> union C>  // Error
+void f(C<int>* p);
+```
+
+模板的模板参数的参数可以具有缺省模板实参
+
+```cpp
+template<template<typename T,
+                    typename A = MyLoocator> class Container>
+class Adaptation{
+    Container<int> storage; // 隐式等同于 Container<int, MyLoocator>
+    // ...
+}
+```
+
+对于模板的模板参数而言,它的参数名称只能被自身其他参数的声明使用
+
+```cpp
+template<template<typename T, T*> class Buf>
+class Lexer{
+    static char storage[5];
+    Buf<char, &Lexer<Buf>::storage[0]> buf;
+    // ...
+};
+
+template<template<typename T, T*> class List>
+class Node{
+    static T* storage;  // Error 模板的模板参数的参数不能被用在这里
+    // ...
+}
+```
+
+任何类型的模板参数都可以拥有一个**缺省实参**,只要该缺省实参能够匹配整个参数就可以。显然缺省实参不能依赖于自身的参数,但可以依赖于前面的参数
+
+```cpp
+template <typename T, typename Allocator = allocator<T>>
+class List;
+```
+
+与缺省的函数调用实参的约束一样,对于任意一个模板参数,只有在之后的模板参数都提供了缺省实参的情况下,才能具有缺省模板实参
+
+```cpp
+template <typename T1, typename T2, typename T3, typename T4 = char, typename T5 = char>    // Success
+class MyClass1;
+
+template <typename T1, typename T2, typename T3, typename T4 = char, typename T5>           // Error
+class MyClass1;
+
+template <typename T1 = char, typename T2, typename T3, typename T4, typename T5>           // Error
+class MyClass1;
+```
+
+除此之外,缺省实参不能重复声明
+
+```cpp
+template<typename T = void>
+class Value;
+
+template<typenaem T = void>
+class Value;            // 重复出现的缺省实参
+```
+
+### 模板实参
+
+模板实参是指:在实例化模板时,用来替换模板参数的值
+
+1. 显式模板实参:紧跟在模板名称后面,尖括号内部的显式模板实参值。所组成的整个实体称为template-id
+2. 注入式类名称:对于具有模板参数P1、P2...的类模板X,在它的作用域中,模板名称X等同于template-id
+3. 缺省模板实参
+4. 实参演绎:对于不是显式指定的函数模板实参,可以在函数的调用语句中,根据函数调用实参的类型来演绎出函数模板实参
+
+**函数模板实参**
+
+对于函数模板的模板实参,可以显式的指定它们,也或者借助模板的使用方式对他们进行实参演绎
+
+```cpp
+template<typename T>
+inline T const& max(const T& a, const T& b)
+{
+    return a < b ? b : a;
+}
+
+int main(){
+    max<double> (1.0, 2.0); // 显式指定模板实参
+    max(1.0, 2.0);          // 实参演绎
+    max<int>(1.0, 2.0);     // 显式的
+    return 0;
+}
+```
+
+但是某些模板实参永远也得不到演绎的机会,所以最好时把实参所对应的参数放在模板参数列表的开始处,从而可以显示的指定这些参数,而其他的参数仍然可以进行实参演绎
+
+```cpp
+template<typename DstT, typename SrcT>
+inline DstT implicit_cast(SrcT const& x){   // SrcT可以被演绎 但是DstT不可以
+    return x;
+}
+
+double val = implicit_cast<double>(1);
+```
+
+> 比如把`implicit_cast`改为`template<typename SrcT, typename DstT>`就必须显式的指定两个模板实参
+
+由于函数模板可以被重载,所以显式提供所有的实参并不足以表示每一个函数
+
+```cpp
+template <typename Func, typename T>
+void apply(Func func_ptr, T x)
+{
+	func_ptr(x);
+}
+template <typename T> void single(T);
+template <typename T> void multi(T);
+template <typename T> void multi(T*);
+
+void foo() {
+	apply(single<int>, 1);  // success
+	apply(multi<int>, 1);   // error T 或者 T* 都可以被匹配
+}
+```
+
+另外,在函数模板种,显式的指定模板实参可能会试图构造一个无效的C++类型
+
+```cpp
+template<typename T>
+RT1 test(typename T::X const *);
+
+template<typename T>
+RT2 test(...);
+```
+
+表达式`test<int>`会让第一个函数模板毫无意义,因为int中没有名为X的类型,但是它可以匹配第二个函数模板。因此,表达式`&test<int>`能够表示一个唯一函数地址(第二个函数地址)
+所以不能用int来替换第一个模板的参数,并不意味着错误,**替换失败并非错误**(substitution-failure-is-not-an-error, SFINAE)
+
+```cpp
+typedef char RT1;
+typedef struct { char a[2]; } RT2;
+
+#define type_has_memeber_type_X(T) (sizeof(test<T>(0)) == 1)
+
+template<typename T>
+RT1 test(typename T::X const*) {
+	return 1;
+}
+
+template<typename T>
+RT2 test(...) {
+	return 0;
+}
+
+void foo_temp() {
+	std::cout << type_has_memeber_type_X(int) << std::endl;
+	std::cout << type_has_memeber_type_X(TestClass) << std::endl;
+}
+```
+
+于是,就可以在编译器检查给定类型T是否具备成员类型X
+
+**SFINAE**原则保护的只是防止创建无效的类型,但不能保护试图计算无效的表达式
+
+```cpp
+template<int I> void f(int (&)[24/(4-I)]);
+template<int I> void f(int (&)[24/(4+I)]);
+int main()
+{
+    &f<4>; // ERROR:除数为 0
+}
+```
+
+**非类型实参**
+
+非类型模板实参是那些提花你非类型参数的值,必须是下面的某一种
+
+1. 某一个觉有正确类型的非类型模板参数
+2. 一个编译器整形常值或枚举值。这只有在参数类型和值类型能够进行匹配,或值的类型隐式地转换为参数类型的前提下,才是合法的
+3. 漆面有单目运算符&(取地址)的外部变量或者函数的名称。对于函数或者数组变量,&运算符可以省略
+4. 对于引用类型的非类型模板参数,前面没有&运算符的外部变量和外部函数也是可取的
+5. 一个指向成员的指针常量
+
+```cpp
+template <typename T, T nontype_param>
+class C;
+
+C<int, 33>* c1;                         // 整数型(integer type)
+
+int a;
+C<int*, &a>* c2;                        // 外部变量的地址
+
+void f();
+void f(int);
+C<void (*)(int), f>* c3;                // 一个函数名称。重载解析规则在此选用 f(int)。& 会被隐寓加入。
+
+class X {
+    public:
+        int n;
+        static bool b;
+};
+C<bool&, X::b>* c4;                     // static class members 都可接受
+
+C<int X::*, &X::n>* c5;                 // pointer-to-member 常数亦可接受
+
+template<typename T>
+void templ_func();
+
+C<void (), &templ_func<double> >* c6;   // function template 具现体也是一种函数
+```
+
+模板实参的一个普遍约束是在程序创建的时候编译器或者链接器要能够确定实参的值
+
+另外,有一些常值不能作为有效的非类型实参
+
+1. 空指针常量
+2. 浮点值
+3. 字符串
+
+### 友元
+
+友元类的声明不能是类定义,在引入模板之后,友元类声明的唯一变化只是:可以命名一个特定的类模板实例为友元
+
+```cpp
+class TestClass
+{
+};
+
+template<typename T>
+class Node {
+};
+
+template<typename T>
+class Tree {
+	friend TestClass;   // OK
+	friend Node<T>;     // OK
+};
+```
+
+```cpp
+template<typename T>
+class Tree {
+	friend TestClass;   // Error
+	friend Node<T>;     // Error
+};
+
+class TestClass
+{
+};
+
+template<typename T>
+class Node {
+};
+```
+
+显然,如果要把类模板的实例声明为其他类或者类模板的友元,该类模板在声明的地方必须是可见的
+
+**友元函数**
+
+```cpp
+template <typename T1, typename T2>
+void combine(T1, T2);
+
+class Mixer {
+    friend void combine<>(int&, int&);          // OK:T1 = int&, T2 = int&
+    friend void combine<int, int>(int, int);    // OK:T1 = int, T2 = int
+    friend void combine<char>(char, int);       // OK:T1 = char T2 = int
+    friend void combine<char>(char&, int);      // ERROR:与 combine() template 不匹配
+    friend void combine<>(long, long) { ... }   // ERROR:不能在此处定义函数
+}; 
+```
+
+不能在友元声明中定义一个模板实例,所以命名一个实例的友元声明不是不行的

+ 852 - 0
cpp/网络编程.md

@@ -0,0 +1,852 @@
+# 网络编程
+
+所有代码源于 **Unix网络编程**,源码文件来自同文件夹下的`unpv13e.tar.gz`
+
+## TCP/IP
+
+### 基本使用
+
+![客户与服务器使用TCP在同一个以太网中通信](./img/Net_1.png)
+
+尽管客户与服务器之间使用某个应用协议通信,传输层确实用TCP(Transmission Control Protocol,传输控制协议)通信,TCP又转而使用IP(Internet Protocol,网际协议)通信,IP再通过某种形式的数据链路层通信
+
+> 客户端与服务器之间的信息流再一端是向下通过协议栈的,跨越网络后,在另一端是向上通过协议栈的
+
+#### 从服务器获得时间
+
+```cpp
+// #define	SA	struct sockaddr
+// #define	bzero(ptr,n)		memset(ptr, 0, n)
+// 
+
+int main(int argc, char **argv)
+{
+	int			sockfd, n;
+	char			recvline[MAXLINE + 1];
+	struct sockaddr_in	servaddr;
+
+	if (argc != 2)
+		err_quit("usage: a.out <IPaddress>");
+
+	if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
+		err_sys("socket error");
+
+	bzero(&servaddr, sizeof(servaddr));
+	servaddr.sin_family = AF_INET;
+	servaddr.sin_port   = htons(13);	/* 设置端口 */
+	if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)   /* 设置IP地址 */
+		err_quit("inet_pton error for %s", argv[1]);
+
+	if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
+		err_sys("connect error");
+
+	while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
+		recvline[n] = 0;	/* null terminate */
+		if (fputs(recvline, stdout) == EOF)
+			err_sys("fputs error");
+	}
+	if (n < 0)
+		err_sys("read error");
+
+	exit(0);
+}
+```
+
+> intro/daytimetcpcli.c文件
+
+1. 创建TCP套接字`socket(AF_INET, SOCK_STREAM, 0)`,创建一个网际(`AF_INET`)字节流(`SOCK_STREAM`)套接字,它是TCP套接字的花哨名字,`socket`函数返回一个小整数描述符,以后的所有函数调用就用该描述符来标识这个套接字
+
+**套接字**(`socket`),现在正在使用的API成为**套接字API**,例如`socket`函数就是套接字API的一部分
+
+2. 指定服务器的IP地址和端口
+
+把服务器的IP地址和端口填入网际套接字地址结构(`sockaddr_in`),使用`memset`将结构清零,设置地址族为`AF_INET`并且设置端口号
+
+网际套接字地址结构中**IP地址**和**端口号**这两个成员需使用**特定格式**,为此需要调用库`htons`(主机到网络短整数)去转换二进制端口号,调用`inet_pton`(呈现形式到数值)去把`char*`转化成合适的格式
+
+3. 建立服务器连接
+
+`connect`函数用于一个TCP套接字时,将于由它的第二个参数指向的套接字地址结构指定的服务器建立一个TCP连接,该套接字地址结构的长度也必须作为该函数的第三个参数指定
+
+> 对于网际套接字地址结构,一般使用sizeof操作符由编译器来计算长度
+
+使用`#define	SA	struct sockaddr`定义**通用套接字地址结构**,每当一个套接字函数需要一个指向某个套接字地质结构的指针使,这个指针必须强制类型转换成一个指向通用套接字地址结构的指针
+
+> 使用宏定义可以缩减编码字符
+
+4. 读入服务器的应答
+
+使用`read`函数读取服务器的应答,并用标准的IO函数`fputs`输出结构
+
+使用TCP时需要小心,因为TCP是一个**没有记录边界的字节流协议**
+
+比如服务器返回时间可以能使如下26字节的字符串
+
+`Mon May 26 20:58:40 2003\r\n`
+
+总计26个字符,大小为26字节可以有多种返回方式,既可以是包含所有26个字节的单个TCP分节,也可以是每个分节只含1个字节的26个TCP分节,还可以是总共26个字节的任何其他组合
+
+通常服务器返回包含所有26个字节的单个分节,但是如果数据量很大,就**不能确保**一次`read`调用能返回服务器的整个应答,因此TCP套接字读取数据时,总是需要把`read`编写再某个循环中,当`read`返回0(表示**对端关闭连接**)或负值(表示**发生错误**)时中止循环
+
+#### 使用IPv6获得服务器时间
+
+```cpp
+// #define	SA	struct sockaddr
+// #define	bzero(ptr,n)		memset(ptr, 0, n)
+
+int main(int argc, char **argv)
+{
+	int			sockfd, n;
+	char			recvline[MAXLINE + 1];
+	struct sockaddr_in6 servaddr;
+
+	if (argc != 2)
+		err_quit("usage: a.out <IPaddress>");
+
+	if ( (sockfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
+		err_sys("socket error");
+
+	bzero(&servaddr, sizeof(servaddr));
+	servaddr.sin6_family = AF_INET6;
+	servaddr.sin6_port   = htons(13);	/* 设置端口 */
+	if (inet_pton(AF_INET6, argv[1], &servaddr.sin6_addr) <= 0)   /* 设置IP地址 */
+		err_quit("inet_pton error for %s", argv[1]);
+
+	if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
+		err_sys("connect error");
+
+	while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
+		recvline[n] = 0;	/* null terminate */
+		if (fputs(recvline, stdout) == EOF)
+			err_sys("fputs error");
+	}
+	if (n < 0)
+		err_sys("read error");
+
+	exit(0);
+}
+```
+
+对比前面的例子可以发现,只修改了5行代码,得到的确实另一个与协议相关的程序
+所以更好的做法是**编写协议无关的程序**
+
+另一个问题是,用户必须输入**点分十进制数**格式给出服务器的IP地址(比如:206.168.112.219),可以使用 **IP地址与主机名之间** 以及 **服务名与端口之间** 的**转换函数**
+
+#### 返回时间的服务器程序
+
+```cpp
+// #define	LISTENQ		1024	/* 2nd argument to listen() */
+#include	<time.h>
+
+int Socket(int family, int type, int protocol)
+{
+	int		n;
+
+	if ( (n = socket(family, type, protocol)) < 0)
+		err_sys("socket error");
+	return(n);
+}
+
+void Bind(int fd, const struct sockaddr *sa, socklen_t salen)
+{
+	if (bind(fd, sa, salen) < 0)
+		err_sys("bind error");
+}
+
+
+void Listen(int fd, int backlog)
+{
+	char	*ptr;
+
+		/*4can override 2nd argument with environment variable */
+	if ( (ptr = getenv("LISTENQ")) != NULL)
+		backlog = atoi(ptr);
+
+	if (listen(fd, backlog) < 0)
+		err_sys("listen error");
+}
+
+int main(int argc, char **argv)
+{
+	int                     listenfd, connfd;
+	struct sockaddr_in	servaddr;
+	char                    buff[MAXLINE];
+	time_t                  ticks;
+
+	listenfd = Socket(AF_INET, SOCK_STREAM, 0);
+
+	bzero(&servaddr, sizeof(servaddr));
+	servaddr.sin_family      = AF_INET;
+	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
+	servaddr.sin_port        = htons(13);	/* daytime server */
+
+	Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
+
+	Listen(listenfd, LISTENQ);
+
+	for ( ; ; ) {
+		connfd = Accept(listenfd, (SA *) NULL, NULL);
+
+        ticks = time(NULL);
+        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
+        Write(connfd, buff, strlen(buff));
+
+		Close(connfd);
+	}
+}
+```
+
+1. 创建TCP套接字
+
+`Socket`是自行封装的`socket`函数,里面封装了初始化不成的的警告函数
+
+2. 端口绑定
+
+通过填写一个网际套接字地址结构并调用`bind`函数,服务器的接口就被捆绑到所创建的套接字
+指定IP地址为`INADDR_ANY`,这样服务器进程可以在任意网络接口上接受客户连接
+
+3. 把套接字转换成监听套接字
+
+调用`listen`函数把该套接字转换成一个监听套接字,这样来自客户的外来连接就可以在该套接字上由内核接收
+
+`socket`、`bind`和`listen`3个调用步骤是任何TCP服务器准备所谓的**监听描述符**(listening descriptor)的正常步骤
+
+4. 接收客户连接,发送应答
+
+通常情况下,服务器进程再accept调用中被投入睡眠,等待某个客户连接的到达并被内核接收
+TCP连接通过**三次握手**来建立,握手完毕后accept返回,其返回值是一个称为**已连接描述符**的新描述符(上述代码中的connfd),通过该描述符与新进连接的客户端通信
+
+5. 关闭连接
+
+调用`close`关闭与客户的连接
+
+#### OSI七层模型
+
+![OSI七层模型](./img/Net_2.png)
+
+> 除需要直到数据链路的某些特性外(比如以太网的MTU大小),不用关心物理层和数据链路层
+
+套接字提供的是从OSI模型的**顶上三层进入传输层的接口**
+
+- 顶上三层不太需要了解通信细节;底下四层对网络应用不需要了解,只需要处理通信细节:发送数据、等待确认、对无序到达的进行排序、计算并验证校验和等等
+- 顶上三层通常构成**用户进程**,底下四层通常作为**操作系统内核**的一部分提供
+
+### 传输层:TCP、UDP、SCTP
+
+SCTP(Stream Control Transmission Protocol,流控制传输协议),绝大多数客户端、服务器网络应用使用TCP或UDP
+
+UDP一给简单的、不可靠的数据报协议;TCP复杂、可靠的字节流协议
+
+![TCP/IP协议](./img/Net_3.png)
+
+| 协议 | 作用 |
+| --- | --- |
+| IPv4 | 网际协议版本4(Internet Protocol version 4) 为TCP、UDP、SCTP、ICMP和IGMP提供分组递送服务 |
+| IPv6 | 使用128更大地址以应对因特网的告诉发展 |
+| TCP | 传输控制协议(Transmission Control Protocol) 为用户进程提供可靠的全双工字节流。TCP套接字是一种流套接字。TCP关系确认、超市和重传之类的细节 |
+| UDP | 用户数据报协议(User Datagram Protocol)。UDP是一个无连接协议。UDP套接字是一种数据报套接字。UDP数据报不能保证最终到达它们的目的地 |
+| SCTP | 流控制传输协议(Stream Control Transimission Protol)提供可靠全双工关联的面向连接的协议 |
+| ICMP | 网际控制信息协议(Internet Control Message Protocol)。ICMP处理在路由器和主机之间流通的错误和控制消息。这些消息通常由TCP/IP网络支持软件(不是用户进程)本身产生和处理 |
+| IGMP | 网际组管理协议(Internet Group Management Protocol),用于多播 |
+| ARP | 地址解析协议(Address Resolution Protocol)。ARP把一个IPv4地址映射成一个硬件地址。ARP通常用于诸如以太网、令牌环网和FDDI等广播网络,在点到点网络上不需要 |
+| RARP | 反向地址解析协议(Reverse Address Resolution Protocol)。RARP把一个硬件地址映射成一个IPv4地址。有适用于无盘节点的引导 |
+| ICMPv6 | 网际控制消息协议版本6(Internet Control Message Protocol version 6) |
+| BPF | BSD分组过滤器(BSD packet filter) 提供对于数据链路层的访问能力,通常源自Berkeley的内核 |
+| DLPI | 数据链路提供者接口(datalink provider interface) 提供对于书链路层的访问能力,通常随SVR4内核提供 |
+
+#### UDP、TCP、SCTP
+
+- UDP
+
+应用进程往UDP套接字写入信息,随后信息被封装到一个UDP数据报,UDP数据报进而封装到IP数据报,然后发送到目的地
+
+UDP不保证数据报回到达最终目的、不保证各个数据报到达的先后顺序、不保证每个数据报只到达一次
+
+UDP的问题就是缺乏可靠性,如果想要确保一个数据报到达其目的地,可以往应用程序中添置一大堆特性:来自对端的确认、本端的超时重重等
+
+每个UDP数据报都有一个长度,如果一个数据报正确地到达其目的地,那么该数据报的程度将随数据一并传递给接收端应用程序
+
+UDP提供**无连接**的服务,因为UDP客户端与服务器之间不必存在任何长期的关系。UDP客户端可以创建一个套接字,用这个套接字发送数据给多个服务器
+
+- TCP
+
+TCP提供客户端与服务器之间的连接,TCP客户端先于某个服务器建立连接,随后通过该连接与服务器交换数据,然后中止这个连接
+
+TCP提供可靠性。TCP向一端发送数据时,要求那一段返回一个确认,如果没有收到确认,TCP就会自动重传并等待更长时间。在数次重传失败后,TCP才放弃
+
+> TCP并不保证数据一定会被对方收到,只保证在可能送到的情况下,数据可以送到,否则就通过**放弃重传并中断连接**来通知用户
+
+TCP含有用于动态估算客户和服务器之间的往返时间(round-trip time,RTT)的算法,以便知道等待一个确认需要多少时间
+
+TCP通过给其中每个字节关联一个序列号对所发送的数据进行排序(当进行一条数据发送的时候,首先,TCP会将这条数据拆分成不同的片段,然后把片段进行一个排序。然后把排序号的片段顺序进行发送)
+
+TCP提供流量控制。TCP总是告诉对端在任何时刻他一次能够从对端接收多少字节的数据,这称为**通告窗口**(滑动窗口)
+
+TCP时全双工的,在给定的连接上应用可以在任何时刻在进出两个方向上即发送数据又接收数据(TCP必须为每个数据流方向跟踪诸如序列号和通告窗口大小等信息)
+
+- SCTP
+
+暂且跳过,用的少 ZZZ
+
+#### TCP连接的建立和中止
+
+1. 三次握手
+
+![三次握手](./img/Net_4.png)
+
+- 服务器必须准备好接收外来的连接,通常通过`socket`、`bind`和`listen`完成,称之为**被动打开**
+- 客户端通过调用`connect`发起**主动打开**,这导致客户端发送一个SYN(同步)分节,告诉服务器客户端将在连接中发送的数据的初始序列号。**此时可以确认客户端具有发送信息的能力**
+- 服务器必须确认(ACK)客户端的SYN,同时自己也发送一个SYN分节,它含有服务器将在同一连接中发送的数据的初始序列号。服务器在单个分节中发送SYN和对客户SYN的ACK(确认)。**此时可以确认服务器具有接收和发送数据的能力**
+- 客户端必须确认服务器的SYN。**此时可以确认服务器具有接收数据的能力**
+
+2. TCP选项
+
+每一个SYN可以含有多个TCP选项
+
+| 选项 | 作用 |
+| --- | --- |
+| MSS选项 | 发送SYN的TCP一段使用本选项通告对端它的**最大分节大小**(maximum segment size)即MSS,也就是它在本连接的每个TCP分节中愿意接受的最大数据量 |
+| 窗口规模选项 | TCP连接任何一端能够通告对端的最大窗口大小时65535,因为在TCP首部中响应的字段占16位。但是随着网络的普及,这个选项指定TCP首部中的通告窗口必须扩大的位数(0~14),即最大可以提供 65535 * (2 ^ 14) |
+| 时间戳选项 | 对于告诉网络时必要的,可以防止由失而复现的分组可能造成的数据损坏,一般无需考虑 |
+
+3. TCP连接终止
+
+TCP的中止需要4个分节
+
+![TCP连接终止](./img/Net_5.png)
+
+- 某个应用程序首先调用`close`,**主动关闭**,该端TCP发送FIN分节
+- 接收到这个FIN的对端执行**被动关闭**,这个FIN由TCP确认,它的接收也作为要给文件结束符传递给接收端应用程序
+- 一段时间后接收到这个文件结束符的应用程序将调用`close`关闭它的套接字,这导致TCP也发送一个FIN
+- 接收这个最终FIN的原发送端TCP确认这个FIN
+
+4. TCp状态转换
+
+![TCP状态转移](./img/Net_6.png)
+
+5. 观察分组
+
+![TCP链接的分组交换](./img/Net_7.png)
+
+> 客户端536的MSS,服务器1460的MSS
+
+MSS(最大分段大小)限制通过网络(例如互联网)传输的数据包或小数据块的大小。通过网络传输的所有数据都被分解成数据包。数据包附有几个标头,其中包含有关其内容和目的地的信息。MSS 测量数据包的非标头部分,称为有效负载
+
+#### TIME_WAIT状态
+
+
+## 基本套接字
+
+### 套接字地址结构
+
+#### IPv4套接字地址结构
+
+大多数套接字函数都需要一个指向套接字地址结构的指针作为参数
+
+每个协议族都定义它自己的套接字地址结构,这些结构的名字均以`sockaddr_`开头,并以对应每个协议族的唯一后缀结尾
+
+```cpp
+struct in_addr{
+	in_addr_t s_addr;			// 32-bit IPv4 address
+};
+
+struct sockaddr_in {
+	uint8_t			sin_len;			// length of structure(16)
+	sa_family_t   	sin_family;         //address family
+	in_port_t 		sin_port;           //16 bit TCP/UDP port number
+	struct  in_addr sin_addr;   		//32 bit IP address
+	char    		sin_zero[8];        //not use, for align
+};
+```
+
+> `sin_len`长度字段是为了增加对OSI协议的支持而增加的,在此之前第一个成员是`sin_family`也就是说不是所有厂家都支持`sin_len`字段,而且POSIX规范也不要求这个成员
+
+- 即使有`sin_len`长度字段,也无需设置和检查它,触发涉及路由套接字。它是由处理来自不同协议族的套接字地址结构的例程在内核中使用的
+
+> 在源自`Berkeley`的实现中,从进程到内核传递套接字地址结构的4个套接字函数(`bind`,`connet`,`sendto`和`sendmsg`)打偶要调用`sockargs`函数,该函数从进程复制套接字地址结构,并显式地把它的`sin_len`字段设置成早先作为参数传递给这个4个函数的该地址结构的长度
+
+> 从内核到进程传递套接字地址结构的5个套接字函数分别是`accept`,`recefrom`,`recvmsg`,`getpeername`和`getsockname`,均在返回进程之前设置`sin_len`字段
+
+- POSIX规范只需要`sin_family`,`sin_addr`和`sin_port`三个字段。对于符合POSIX的实现来说,定义额外的结构字段是可以接受的,这队伍网际套接字地址结构来说也是正常的。几乎所有的试下你都增加了`sin_zero`字段,所以所有的套接字地质结构大小都至少是16字节
+
+- `struct in_addr`数据类型必须是一个至少32位无符号整数类型,`in_port_t`必须是一个至少16位的无符号整数类型,`sa_family_t`可以是任何无符号整数类型
+
+> 在支持长度字段的实现中,`sa_family_t`通常是一个8位的无符号整数;在不支持长度字段的实现中,它一般是16位无符号整数
+
+| 数据类型 | 说明 | 头文件 |
+| --- | --- | --- |
+| int8_t | 带符号的8位整数 | `<sys/types.h>` |
+| uint8_t | 不带符号的8位整数 | `<sys/types.h>` |
+| int16_t | 带符号的16位整数 | `<sys/types.h>` |
+| uint16_t | 不带符号的16位整数 | `<sys/types.h>` |
+| int32_t | 带符号的32位整数 | `<sys/types.h>` |
+| uint32_t | 不带符号的32位整数 | `<sys/types.h>` |
+| sa_family_t | 套接字地址结构的地址族 | `<sys/socket.h>` |
+| socklen_t | 套接字地址结构的长度,一般为uint32_t | `<sys/socket.h>` |
+| in_addr_t | IPv4地址,一般为uint32_t | `<netinet/in.h>` |
+| in_port_t | TCP或UDP端口,一般为uint16_t | `<netinet/in.h>` |
+
+- IPv4地址和TCP或UDP开端口号在套接字地址结构中总是以**网络字节序**来存储
+- 32位IPv4地址存在两种不同的访问方法,所以必须正确使用IPv4地址,尤其是在它作为函数的参数时,因为编译器对**传递结构**和**传递整数**的处理时完全不同的
+- `sin_zero`字段未曾使用,一般把整个结构置为0,而不是单单把`sin_zero`置0
+- 套接字地址结构仅在给定主机上使用。虽然结构中的某些字段用在不同主机之间的通信中,但是结构本身并不在主机之间传递
+
+#### 通用套接字地址结构
+
+当作为一个参数传递进任何套接字函数时,套接字地址结构总是以引用形式(指向该结构的指针)来传递。但是以这样的**指针作为参数**之一的任何套接字函数必须处理来自所支持的**任何协议族**的套接字地址结构
+
+`void*`类型是在套接字函数定义之后出现的,所以当时的为了解决适配所有协议族的套接字函数,在`<sys/socket.h>`中定义了`sockaddr`**通用套接字地址结构**
+
+```cpp
+struct sockaddr {
+	__uint8_t       sa_len;         /* total length */   
+	sa_family_t     sa_family;      /* [XSI] address family */
+	char            sa_data[14];    /* [XSI] addr value (actually larger) */
+};
+```
+
+套接字函数被定义为指向某个通用套接字地址结构的一个指针作为其参数之一
+
+```cpp
+int bind(int, struct sockaddr*, socklen_t);
+```
+
+> `bind`将`sockaddr*`作为参数
+
+这就要求这些函数的任何调用都必须要指向特定于协议的套机子地址结构的指针进行强制类型转换
+
+```cpp
+struct sockaddr_in serv;
+bind(sockfd, (struct sockaddr*)&serv, sizeof(serv));
+```
+
+#### IPv6套接字地址结构
+
+```cpp
+struct sockaddr_in6
+{
+	uint8_t			sin6_len;			
+	sa_family_t 	sin6_family;   	/* 地址协议簇: AF_INET6 */
+	u_int16_t 		sin6_port;      /* 端口号, 要用网络字节序表示 */
+	u_int32_t 		sin6_flowinfo   /* 流信息, 应设置为0 */
+	struct in6_addr sin6_addr; 		/* ipv6 地址结构体, 见下面 */
+	u_int32_t 		sin6_socpe_id;  /* scope ID, 尚处于实验阶段 */
+};
+
+struct in6_addr
+{
+	unsigned char sa_addr[16]; 	/* ipv6地址, 要使用网络字节序表示 */
+};
+```
+
+> `<netinet/in.h>`中定义
+
+- 如果系统支持套接字地址结构中的长度字段`sin6_len`,那么`sin6_len`必须定于
+- `IPv6`的地址族时`AF_INET6`,`IPv4`的地址族时`AF_INET`
+- 结构中字段的先后顺序做过编排,是的如果`sockaddr_in6`结构本身是64位对其的,那么128位的`sin6_addr`字段也是64位对其的
+- `sin6_flowinfo`字段分成两个字段
+  - 低序20位是流标
+  - 高序12位保留
+- 对于具备范围的地址(scoped address),`sin6_scope_id`字段标识其范围,最常见的是链路局部地址(link-local address)的接口索引(interface index)
+
+#### 新的通用套接字地址结构
+
+作为IPv6套接字API的一部分而定义的新的通用套机子地址结构克服了现有的`struct sockaddr`的一些缺点,新的`struct sockaddr_storage`足以容纳系统所支持的任何套接字地质结构
+
+```cpp
+struct sockaddr_storage {
+    uint8_t ss_len;
+    sa_family_t ss_family;
+    char ss_padding[SIZE];
+}
+```
+
+- 与`sockaddr`存在的区别
+  - 系统支持的任何套接字地址结构有对其需要,`sockaddr_storage`都能够满足
+  - `sockaddr_storage`足够大,容纳系统支持的任何套接字地址结构
+
+- 注意
+  - `sockaddr_storage`中除了`ss_family`和`ss_len`之外的其他字段,对用户同某个
+  - `sockaddr_storage`结构必须强制类型转换成或复制到适合于`ss_family`字段所给出地址类型的套接字地址结构中,才能访问其他字段
+
+#### 套接字地址结构的比较
+
+![TCP链接的分组交换](./img/Net_8.png)
+
+### 值-结果参数
+
+套接字函数传递一个套接字地址结构时,总是以指针的形式传递,该结构的长度作为另一个参数来传递,不过该传递分两种:内核到进程、进程到内核
+
+1. 从进程到内核传递套接字地址结构的函数有3个:`bind`,`connect`和`sendto`,这些函数的一个参数是指向某个套接字地址结构的指针,另一个参数是该结构的整数大小
+
+```cpp
+int send(
+  _In_       SOCKET s,
+  _In_ const char   *buf,
+  _In_       int    len,
+  _In_       int    flags
+);
+
+int connect(
+  _In_ SOCKET                s,
+  _In_ const struct sockaddr *name,
+  _In_ int                   namelen
+);
+```
+
+指针和指针所指内容的大小都传递给了内核,于是内核知道需要从进程复制多少数据进来
+
+![TCP链接的分组交换](./img/Net_9.png)
+
+2. 从内核到进程传递套接字地质结构的函数有4个:`accept`,`recvfrom`,`getsockname`和`getpeername`
+
+这些函数的一个参数指向某个套接字地址结构的指针,另一个表示指向该结构带线啊哦的指数变量的指针
+
+```cpp
+int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
+ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len);
+int	getsockname(int, struct sockaddr *restrict, socklen_t *restrict);
+int getpeername(int, struct sockaddr *restrict, socklen_t *restrict);
+```
+
+![TCP链接的分组交换](./img/Net_10.png)
+
+把套接字地址结构大小这个参数从一个整数改为指向某个整数变量的指针,是为了
+
+1. 当时被调用时,结构大小是一个值,它告诉内核该结构的大小,这样内核在写该结构时不至于越界
+2. 当函数返回时,结构大小是一个结果,它告诉进程内核在结构中究竟存储了多少信息
+
+使用值-结果参数作为套接字地址结构的长度时
+1. 如果套接字地址结构是固定长度(sockaddr_in 长度是16,sockaddr_in6长度是28),那么内核返回的长度也是那个固定值
+2. 如果是可变长度的套接字地址结构(Unix域的sockaddr_un),内核返回的长度可能是小于该结构的最大长度
+
+### 字节排序函数
+
+一个16位的整数,由两个字节组成,内存中存储这两个字节有两种方法
+1. 低序(就是权值较小的后面那几位)字节存储在起始地址,称为**小端字节序**
+2. 高序字节存储在起始地址,称为**大端字节序**
+
+![TCP链接的分组交换](./img/Net_11.png)
+
+遗憾的是,这两种字节序之间没有标准可循,两种格式都有系统使用
+
+```cpp
+union {
+	short s;
+	char c[sizeof(short)];
+} un;
+un.s = 0x0102;
+std::cout << (int)un.c[0] << " " << (int)un.c[1] << std::endl;
+```
+
+> 输出`1 2`是大端,输出`2 1`是小端
+
+![TCP链接的分组交换](./img/Net_12.png)
+
+一般把某个给定系统所用的字节序称为**主机字节序**
+
+网络协议指定的字节序称为**网络字节序**
+
+在每个TCP分节中都有16位端口号和32位的IPv4地址。发送协议栈和接收协议栈必须就这些多字节字段和各个字节的传送顺序达成一致。**网际协议使用大端字节序**来传送这些多字节整数
+
+> 网络使用大端的一种解释:先收到校验信息,提前预处理
+
+理论上,按主机字节序存储套接字地质结构中的各个字段,等到需要时再在主机字节序和网络字节序之间进行自动转换,使用者无需关注细节。但是历史原因,套接字地址结构中的某些字段必须按照网络字节序进行维护
+
+因此,需要关注如何在主机字节序和网络字节序之间相互转换
+
+```cpp
+#include <arpa/inet.h>
+
+// 均返回网络字节序的值
+uint32_t htonl(uint32_t hostlong);
+uint16_t htons(uint16_t hostshort);
+
+// 均返回主机字节序的值
+uint32_t ntohl(uint32_t netlong);
+uint16_t ntohs(uint16_t netshort);
+```
+
+> h表示`host`主机,n表示`network`网络,s表示`short`,l表示`long`
+
+使用这些函数时,不关系主机字节序和网络字节序的真实值,只用调用合适的函数在主机和网络字节序之间转换某个给定值
+
+### 字节操纵函数
+
+以空字符结尾的C字符串是由在`<string.h>`头文件中定义、名字以str开头的函数处理  
+
+名字以mem开头的函数用于处理内存
+
+名字以b开头的函数用于处理字节
+
+```cpp
+#include <strings.h>
+
+void bzero(void *s, size_t n); 
+void bcopy(const void *s1, void *s2, size_t n);
+int bcmp(const void *s1, const void *s2, size_t n);	// 若相等 返回0,否则返回非0
+```
+
+`bzero`把目标字节串中指定数据的字节置为0,经常用来把一个套接字地址结构初始化0
+
+`bcopy`将指定数目的字节从源字节串移到目标字节串
+
+`bcmp`用于比较两个字节串
+
+```cpp
+#include <string.h>
+
+void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
+void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
+int memcmp(const void *s1, const void *s2, size_t n);
+```
+
+`memset`把目标字节串指定数目的字节置为值C
+
+`memcpy`类似`bcopy`不过两个指针参数的顺序相反
+
+`memcmp`比较两个任意的字节串,相同返回0,否则返回非0
+
+### inet_aton、inet_addr、inet_ntoa函数
+
+用于ASCII字符串域网络字节序的二进制之间转换网际地址
+
+1. `inet_aton`、`inet_addr`、`inet_ntoa`在点分十进制数串(例如`206.140.23.123`)与它长度为32位的网络字节序二进制值之间转换IPv4地址
+2. `inet_pton`和`inet_ntop`对于IPv4和Ipv6都适用
+
+```cpp
+#include <arpa/inet.h>
+
+int inet_aton(const char *string, struct in_addr*addr);		// 若字符串有效 返回1 否则返回0
+in_addr_t inet_addr(const char *cp);						// 若字符串有效 返回32位二进制网络字节序IPv4地址,否则返回INADDR_NONE
+char *inet_ntoa(struct in_addr in);							// 返回点分十进制数串的指针
+```
+
+> inet_addr返回值INADDR_NONE是每个位全为1的常值,也就是255.255.255.255,那么对于IPv4的广播地址就无法使用inet_addr表示,所以应该**尽可能不用**`inet_addr`
+
+### inet_pton和inet_ntop函数
+
+这两个函数是随IPv6出现的函数,对于IPv4地址和IPv6地址都适用
+
+函数中p和n分别代表表达(presentation)和数值(numeric)
+
+```cpp
+#include <arpe/inet.h>
+int inet_pton(int family, const char *strptr, void *addrptr);   //将点分十进制的ip地址转化为用于网络传输的数值格式
+// 返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
+ 
+const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len);     //将数值格式转化为点分十进制的ip地址格式
+// 返回值:若成功则为指向结构的指针,若出错则为NULL
+```
+
+这两个函数的`family`参数既可以是`AF_INET`,也可以是`AF_INET6`,如果不被不支持的地址族作为family,会返回一个错误,毕竟`errno`置为`EAFNOSUPPORT`
+
+`inet_ntop`中的len表示目标存储单元的大小(strptr),一面该函数溢出其调用者的缓冲区。下面的宏定义有助于指定大小。
+
+```cpp
+#define INET_ADDRSTRLEN 	16
+#define INET6_ADDRSTRLEN 	46
+```
+
+如果len太小,不足与容纳表达式格式(包括串结束符),会返回**空指针**,并置errno位`ENOSPC`
+
+`inet_ntop`函数的`strptr`不可为空指针,调用者必须为目标存储单元分配内存并指定其大小
+
+### readn、writen和readline函数
+
+字节流套接字上的`read`和`write`函数所表现的行为不同于通常的文件IO
+
+字节流套接字上调用`read`或`write`输入或输出的字节数可能比请求的数量少,因为内核中用于套接字的缓冲区可能已经达到了极限。此时,所需的是调用者再次调用`read`或`write`函数,以输入或输出剩余的字节
+
+> 这种现象在read一个字节流套接字时很常见,但是在write一个字节流套接字时只能在该套接字为非阻塞的前提下才出现
+
+为了以防万一,使用`writen`函数来替代`write`函数
+
+```cpp
+ssize_t writen (int fd, const void *buf, size_t num)
+{
+	ssize_t res;
+	size_t n;
+	const char *ptr;
+	
+	n = num;
+	ptr = buf;
+	while (n > 0) {
+	/* 开始写*/ 
+		if ((res = write (fd, ptr, n)) <= 0) 
+		{
+			if (errno == EINTR)
+				res = 0;
+			else
+				return (-1);
+		}
+	
+		ptr += res;/* 从剩下的地方继续写     */ 
+		n -= res;
+	}
+	return (num);
+}
+
+ssize_t readn (int fd, void *buf, size_t num)
+{
+	ssize_t res;
+	size_t n;
+	char *ptr;
+	
+	n = num;
+	ptr = buf;
+	while (n > 0) {
+		if ((res = read (fd, ptr, n)) == -1) 
+		{
+			if (errno == EINTR)
+				res = 0;
+			else
+				return (-1);
+		}
+		else if (res == 0)
+			break;
+	
+		ptr += res;
+		n -= res;
+	}
+	
+	return (num - n);
+}
+
+ssize_t readline(int fd, void *vptr, size_t maxlen)
+{
+	ssize_t	n, rc;
+	char	c, *ptr;
+ 
+	ptr = vptr;
+	for (n = 1; n < maxlen; n++) {
+        again:
+		if ( (rc = read(fd, &c, 1)) == 1) {
+			*ptr++ = c;
+			if (c == 'n')
+				break;	/* newline is stored, like fgets() */
+		} else if (rc == 0) {
+			*ptr = 0;
+			return(n - 1);	/* EOF, n - 1 bytes were read */
+		} else {
+			if (errno == EINTR)
+				goto again;
+			return(-1);		/* error, errno set by read() */
+		}
+	}
+ 
+	*ptr = 0;	/* null terminate like fgets() */
+	return(n);
+}
+
+```
+
+## 基本TCP套接字编程
+
+![基本TCP客户端、服务器程序的套接字函数](./img/Net_13.png)
+
+### socket函数
+
+为了执行网络IO,一个进程必须做的第一件事情就是调用socket函数,指定期望的通信协议类型
+
+```cpp
+#include <sys/socket.h>
+
+int socket(int family, int type, int protocol);		// 成功返回非负描述符,否则返回-1
+```
+
+`family`用于指明协议族,也往往被称为协议域;`type`指明套接字类型;`protocol`用于设定某个协议类型的常值
+
+| family | 说明 |
+| --- | --- |
+| AF_INET | IPv4协议 |
+| AF_INET6 | IPv6协议 |
+| AF_LOCAL | Unix域协议 |
+| AF_ROUTE | 路由套接字 |
+| AF_KEY | 密钥套接字 |
+
+> family协议族的一些常值
+
+| type | 说明 |
+| --- | --- |
+| SOCK_STREAM | 字节流套接字,提供面向连接的稳定数据传输,即TCP协议 |
+| SOCK_DGRAM | 数据报套接字,使用不连续不可靠的数据包连接 |
+| SOCK_SEQPACKET | 有序分组套接字,提供连续可靠的数据包连接 |
+| SOCK_RAW | 原始套接字,提供原始网络协议存取 |
+| SOCK_RDM | 提供可靠的数据包连接 |
+| SOCK_PACKET | 与网络驱动程序直接通信 |
+
+> type套接字类型的一些常值
+
+| protocol | 说明 |
+| --- | --- |
+| IPPROTO_TCP | TCP传输协议 |
+| IPPROTO_UDP | UDP传输协议 |
+| IPPROTO_SCTP | SCTP传输协议 |
+
+> protocol的一些类型常值,**或者设置为0**
+
+| | AF_INET | AF_INET6 | AF_LOCAL | AF_ROUTE | AF_KEY |
+| --- | --- | --- | --- | --- | --- |
+| SOCK_STREAM | TCP、SCTP | TCP、SCTP | 是 | | |
+| SOCK_DGRAM | UDP | UDP | 是 | | |
+| SOCK_SEQPACKET | SCTP | SCTP | 是 | | |
+| SOCK_RAW | IPv4 | IPv6 | | 是 | 是 |
+
+并非所有的family与type的组合都是有效的,上面列表中空白格表示无效,其余都是有效
+
+`socket()`函数在成功之后返回一个小的非负整数,它与文件描述符类似,一般称之为**套接字描述符**,也叫`sockdf`
+
+### connet函数
+
+```cpp
+#include <sys/socket.h>
+
+int connect(int socket, const struct sockaddr *address, socklen_t address_len);
+```
+
+TCP客户端用connect函数来建立与TCP服务器的连接
+
+`socket`参数是用`socket()`函数返回的套接字描述符,`address`和`address_len`分别是一个指向套接字地址结构的指针和该结构的大小
+
+> 客户在调用`connect`之前并不是非得调用`bind`函数,因为如果需要的话,内核会确定源IP地址,并选择一个临时端口作为源端口
+
+如果是TCP套接字,调用`connect`函数会激发TCP的三次握手,而且仅在连接建立成功或出错时才返回,其中出错可能有几种情况
+
+1. TCP客户端没有收到SYN分节的响应,则返回ETIMEDOUT错误
+2. 若对客户的SYN的响应是RST(表示复位),则表明该服务器主机在我们指定的端口上没有进程在等待与之连接(服务器进程没有运行也有可能)。这是一种硬错误(hard error),客户端一接收到RST就马上返回ECONNREFUSED错误
+	- RST产生的条件1:目的地为某端口的SYN到达,然而该端口没有正在监听的服务器
+	- RST产生的条件2:TCP想取消一个已有连接
+	- RST产生的条件3:TCP接收到一个根本不存在的连接上的分节
+3. 客户发出的SYN在中间的某个路由器上引发`destination unreachable`目的地不可达的ICMP错误,则认为是一种软错误(soft error)
+
+### bind函数
+
+bind函数把一个本地协议地址赋予一个套接字,对于网际网协议,协议地址是32位IPv4地址或128位Ipv6地址与16位的TCP或UDP端口号的组合
+
+```cpp
+#include <sys/socket.h>
+
+int bind(int socket, const struct sockaddr *address, socklen_t address_len);
+```
+
+`socket`参数是用`socket()`函数返回的套接字描述符,`address`和`address_len`分别是一个指向套接字地址结构的指针和该结构的大小
+
+对于TCP,`bind`函数可以指定一个端口号,或者指定IP地址,也可以两个都指定或都不指定
+
+如果TCP客户端或服务器未曾调用`bind`函数绑定端口,当调用`connect`或`listen`时,内核会临时分配端口。让内核选择临时端口对TCP客户端来说正常,但是**服务器的端口必须指定**,因为服务器端口需要外部知道才能主动连接
+
+进程可以把一个特定的IP绑定到它的套接字上,不过这个IP地址必须属于其所在主机的网络接口之一
+
+- 对于TCP客户端来说,这就是为在该套接字上发送的IP数据报制定了源IP地址
+- 对于服务器来说,这就限定该套接字只接受那些目的地为这个IP地址的客户连接
+
+| IP地址 | 端口 | 结果 |
+| --- | --- | --- |
+| 通配地址 | 0 | 内核选择IP地址和端口 |
+| 通配地址 | 非0 | 内核选择IP地址,进程指定端口 |
+| 本地IP地址 | 0 | 进程指定IP地址,内核选择端口 |
+| 本地IP地址 | 非0 | 进程指定IP地址和端口 |
+
+> 给bind函数指定姚捆绑的IP地址和/或端口号产生的结果
+