mirror of
https://github.com/NohamR/knowledge-kit.git
synced 2026-05-24 20:00:37 +00:00
docs: 批量博文
This commit is contained in:
48
Chapter5 - Network/5.1.md
Normal file
48
Chapter5 - Network/5.1.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# HTTP 请求头 Range 信息
|
||||
|
||||
请求资源的部分内容,单位是 byte(字节),从0开始。
|
||||
如果请求头携带了 Range 信息,也就是分批下载,这时候服务器会返回 206 Partial Content 的状态码及说明。
|
||||
|
||||
如果服务器不支持分批下载,那么会返回整个资源的大小以及状态码为200。
|
||||
|
||||
|
||||
## Range 请求头
|
||||
|
||||
`Range: bytes=start-end`
|
||||
|
||||
例如:
|
||||
```
|
||||
Range: bytes=10- //:从第10个字节开始到最后一个字节的数据
|
||||
Range:bytes=20-39 //:从第20个字节到第39个字节之间的数据
|
||||
```
|
||||
|
||||
注意:整个表示 [start, end] 是前闭后闭的,也就是包含请求头的 start 和 end。所以下次请求应该是 [end+1, nextEnd]。
|
||||
|
||||
|
||||
## Content-Range 响应头
|
||||
|
||||
`Content-Range:bytes 0-10/3000`
|
||||
表示服务器返回了前(0-10)个字节的数据,总共3000字节的数据。
|
||||
|
||||
|
||||
## Content-Type 数据类型
|
||||
`Content-Type:image/png` 表示资源类型是 png 格式的图片
|
||||
|
||||
## Content-Length 资源的长度
|
||||
|
||||
`Content-Length:11` 表示服务器响应了11个字节的数据
|
||||
|
||||
## Last-Modified
|
||||
|
||||
`Last-Modified:Tue, 30 Jun 2018 03:12:48 GMT` 表示资源最近被修改的时间,如果分批下载的时候发现 Last-Modified 被修改了,那么需要重新下载
|
||||
|
||||
## ETag
|
||||
|
||||
`ETag: W/"3103-1435633968000"` 表示资源版本的标示符。通常是消息摘要(类似MD5)。分段下载时需要注意,或者缓存控制也需要注意。如果是分布式缓存系统,需要确保每台计算机的 ETag 计算规则的一致性,缓存的过期需要结合 ETag 和 Last-Modified 共同决定。
|
||||
|
||||
## 分段下载
|
||||
|
||||
利用 HTTP 的头信息的上述几个特点,我们可以充分利用多线程的能力。
|
||||
|
||||
- 先发送一个 HEAD 方法的请求,知道总文件大小(Content-Length 就是总字节大小)
|
||||
- 多线程下载(线程1:Range:bytes=0-100,线程2:Range:bytes=100-200,...)
|
||||
145
Chapter5 - Network/5.2.md
Normal file
145
Chapter5 - Network/5.2.md
Normal file
@@ -0,0 +1,145 @@
|
||||
# TCP、UDP 的比较
|
||||
|
||||
> 网络协议是每个工程师都需要了解和掌握的知识。 TCP/IP 中有2个最具代表性的传输层协议:TCP、UDP。
|
||||
|
||||
|
||||
## 一、TCP/IP 网络模型
|
||||
|
||||
计算机与网络设备要相互通信,双方就必须基于相同的方法。比如,如何探测到通信目标、由哪一边先发起通信、使用哪种语言进行通信、怎样结束通信等规则都需要事先确定。不同的硬件、操作系统之间的通信,所有的这一切都需要一种规则。而我们就把这种规则称为协议(protocol)。
|
||||
|
||||
TCP/IP 是协议簇,比如:TCP、UDP、IP、FTP、HTTP、ICMP、SMTP 等都属于 TCP/IP 簇内的协议。
|
||||
|
||||
TCP/IP模型是互联网的基础,它是一系列网络协议的总称。这些协议可以划分为四层,分别为链路层、网络层、传输层和应用层。
|
||||
|
||||
- 链路层:负责封装和解封装 IP 报文,发送和接受 ARP/RARP 报文等。
|
||||
- 网络层:负责路由以及把分组报文发送给目标网络或主机。
|
||||
- 传输层:负责对报文进行分组和重组,并以 TCP 或 UDP 协议格式封装报文。
|
||||
- 应用层:负责向用户提供应用程序,比如HTTP、FTP、Telnet、DNS、SMTP等。
|
||||
|
||||
|
||||
| OSI 七层模型 | TCP/IP 概念层模型 | 功能 | TCP/IP 协议簇 |
|
||||
|-|-|-|-|
|
||||
|应用层 |应用层 |文件传输、电子邮件、文件服务、虚拟终端 | FTP、SMTP、TELNET、DNS|
|
||||
|表示层 |应用层 |数据格式化、代码转换、数据加密 | 没有协议|
|
||||
|会话层 |应用层 |解除或建立别的节点的联系 | 没有协议 |
|
||||
|传输层 | 传输层 |提供端对端的接口 | UDP、TCP |
|
||||
|网络层 |网络层 |为数据包选择路由 | IP、ICMP、RIP、OSPF、BGP、IGMP|
|
||||
|物理链路层 |链路层 |传输有地址的帧以及错误检测功能 | SLIP、CSLIP、PPP、ARP、RARP、MTU|
|
||||
|物理层 |链路层 |以二进制数据形式在物理媒体上传输数据 | ISO2110、IEEE802、IEEE802.2|
|
||||
|
||||
|
||||
在网络体系结构中网络通信的建立必须是在通信双方的对等层进行,不能交错。 在整个数据传输过程中,数据在发送端时经过各层时都要附加上相应层的协议头和协议尾(仅数据链路层需要封装协议尾)部分,也就是要对数据进行协议封装,以标识对应层所用的通信协议。接下去介绍 TCP/IP 中有两个具有代表性的传输层协议:TCP 和 UDP。
|
||||
|
||||
|
||||
|
||||
## 二、UDP
|
||||
|
||||
UDP(User Data Protocol)协议全称是用户数据报协议,在网络中它与 TCP 协议一样用于处理数据包,是一种无连接的协议。在 OSI 模型中,位于第四层即传输层,处于 IP 协议的上一层。UDP 有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。
|
||||
|
||||
特点:
|
||||
|
||||
### 1.面向无连接
|
||||
|
||||
首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。
|
||||
|
||||
具体来说就是:
|
||||
- 在发送端,应用层将数据传递给传输层的 UDP 协议,UDP 只会给数据增加一个 UDP 头标识下是 UDP 协议,然后就传递给网络层了
|
||||
- 在接收端,网络层将数据传递给传输层,UDP 只去除 IP 报文头就传递给应用层,不会任何拼接操作
|
||||
|
||||
### 2.有单播、多播、广播的功能
|
||||
|
||||
UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能。
|
||||
|
||||
### 3.UDP是面向报文的
|
||||
|
||||
发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文
|
||||
|
||||
### 4.不可靠性
|
||||
|
||||
- 首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。
|
||||
- 并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。
|
||||
- 再者网络环境时好时坏,但是 UDP 因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用 UDP 而不是 TCP。
|
||||
|
||||

|
||||
|
||||
从上面的动态图可以得知,UDP只会把想发的数据报文一股脑的丢给对方,并不在意数据有无安全完整到达。
|
||||
|
||||
### 5.头部开销小,传输数据高效
|
||||
|
||||

|
||||
|
||||
- 两个十六位的端口号,分别为源端口(可选字段)和目标端口
|
||||
- 整个数据报文的长度
|
||||
- 整个数据报文的检验和(IPv4 可选 字段),该字段用于发现头部信息和数据中的错误
|
||||
|
||||
因此 UDP 的头部开销小,只有八字节,相比 TCP 的至少二十字节要少得多,在传输数据报文时是很高效的
|
||||
|
||||
|
||||
## 三、TCP
|
||||
|
||||
当一台计算机想要与另一台计算机通讯时,两台计算机之间的通信需要畅通且可靠,这样才能保证正确收发数据。例如,当你想查看网页或查看电子邮件时,希望完整且按顺序查看网页,而不丢失任何内容。当你下载文件时,希望获得的是完整的文件,而不仅仅是文件的一部分,因为如果数据丢失或乱序,都不是你希望得到的结果,于是就用到了TCP。
|
||||
|
||||
TCP协议全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议,由 IETF 的RFC 793定义。TCP 是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水管中的水流。
|
||||
|
||||

|
||||
|
||||
|
||||
### 三次握手
|
||||
|
||||
HTTP 协议是建立在请求相应模型上的。首先客户端建立一条与服务器的 TCP 链接,并发送请求到服务器,包含请求方法、URI、协议版本以及相关的 MIME 信息。服务器响应一个状态行,包含协议版本、一个成功和失败码、以及相关的 MIME 信息。
|
||||
|
||||
HTTP/1.0 为每次 HTTP 请求/相应都建立一条新的 TCP 链接。因此一个包含图片和多媒体的网页将建立多次的短期 TCP 链接。且一次 TCP 链接都需要三次握手、四次挥手。
|
||||
|
||||

|
||||
|
||||
- 第一次握手:客户端将 SYN 置为1,选择一个 **seq=x**(序号,起始发送位)。SYN 报文段不能携带数据,但要消耗掉一个序号。此时客户端进入 **SYN_SENT** (同步已发送)状态。SYN(Synchronize Sequence Number):同步序列编号
|
||||
- 第二次握手:服务端收到报文段后,若同意建立请求则向客户端发送确认。在确认报文中把 SYN 和 ACK 都置为1,确认号是 **ack=x+1**,同时也选择一个初始序号 **seq=y**。这个报文段也不能携带数据,但同样需消耗掉一个序号。此时服务器进入 **SYN_RECV** 状态;ACK(Ackonwledgement):确认字符
|
||||
- 第三次握手:客户端收到服务端 ACK+SYN 包,需要向服务端确认。确认报文段的 ACK 置为1(建立连接,2端的ACK都为1),确认号 **ack=y+1**,而自己的序号 **seq=x+1**。客户端和服务端进入 **ESTABLISHED** (TCP 连接成功)状态,完成三次握手。
|
||||
|
||||
|
||||
### 四次挥手
|
||||
|
||||

|
||||
|
||||
- 第一次挥手:客户端应用进程先向其 TCP 发出连接释放报文段,并停止发送数据,主动关闭 TCP 连接(此时关闭的是自己与客户端的连接)。FIN 置为1,序号 seq=u,它等于前面已传送过的数据的最后一个字节的序号加1。这时客户端进入 **FIN_WAIT_1** (终止等待1)状态,等待服务端的确认。TCP 规定,FIN 报文段即使不携带数据段,它也会消耗掉一个序号。
|
||||
- 第二次挥手:服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。发出确认,确认号 ack=u+1,序号为 seq=v,等于服务端前面已经传输过的数据的最后一个字节的序号加1。然后服务端就进入了 CLOSE_WAIT(关闭等待)状态。TCP 服务器进程通知高层应用数据,因而从客户端到服务端这个芳香的连接就释放了。此时 TCP 处于半关闭状态。即客户端已经没数据要发送了,服务端还在发送数据,但是客户端仍需接收数据。也就是说从服务端到客户端的连接尚未关闭
|
||||
- 第三次挥手:客户端收到来自服务端的消息后进入了 FIN_WAIT_2 (终止等待2)状态。等待服务端发出的连接释放报文段。若服务端已经没有要向客户端发送数据,其应用进程就通知 TCP 释放连接。这时候服务端发出的报文段必须 FIN=1。假设服务端序号为 W(在在关闭状态下服务端可能又发送了一些数据,因此与 V 有一定距离)。服务端还必须重复上次发送过的确认号 ack=u+1。这时服务端就进入 LAST_ACK(最后确认)状态,等待客户端的确认
|
||||
- 第四次挥手:客户端收到服务端发送的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK 置1,确认号为 ack=w+1,而自己的序列号 seq=u+1(根据 TCP 标准,前面发送过的 FIN 报文段要消耗一个序号)。然后进入到 TIME_WAIT(时间等待)状态。现在 TCP 还没有释放掉。必须经过时间等待计时器设置的时间 2MSL 后,客户端才可以进入到 CLOSED 状态。时间 MSL 叫做最长报文段寿命。
|
||||
|
||||
|
||||
### TCP 协议的特点
|
||||
|
||||
- 面向连接
|
||||
面向连接,是指发送数据之前必须在两端建立连接。建立连接的方法是三次握手,这样能建立可靠的连接。建立连接是为数据的可靠传输打下了基础。
|
||||
- 仅支持单向传输
|
||||
每条 TCP 传输连接只能有2个端点,只能进行点对点的数据传输,不支持多播和广播传输方式
|
||||
- 面向字节流
|
||||
TCP 不像 UDP 那样一个个报文独立传输,而是在不保留报文边界的情况下以字节流方式进行传输
|
||||
- 可靠传输
|
||||
对于可靠传输,判断丢包,误码靠的是 TCP 的段编号以及确认号。TCP 为保证报文传输的可靠,给每个包一个序号,同时序号也保证了传输到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认 ACK;如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据将被重传。
|
||||
- 提供拥塞控制
|
||||
当网络出现拥塞时,TCP 能够减小向网络注入数据的速率和数量,缓解拥塞
|
||||
- TCP 提供全双工通信
|
||||
TCP 允许通信双方的应用程序在任何都能发送数据,因为 TCP 连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP 可以发送一个数据段,也可以缓存一段时间一边一次发送更多的数据段(最大的数据段大小取决于 MSS)
|
||||
|
||||
## TCP、UDP 对比
|
||||
|
||||
1. 对比
|
||||
|
||||
| | UDP | TCP |
|
||||
| - | - | - |
|
||||
|是否连接| 无连接 | 面向连接 |
|
||||
|是否可靠| 不可靠传输(不存在流量控制、拥塞控制) | 可靠传输(使用流量控制、拥塞控制) |
|
||||
|连接对象个数| 一对一、一对多、多对一、多对多互相通信 | 只可一对一通信 |
|
||||
|传输方式| 面向报文 | 面向字节流 |
|
||||
|首部开销| 开销小,仅8字节 | 开销大,20字节 |
|
||||
|使用场景| 实时应用,比如IP电话、视频会议、直播 | 要求可靠传输的应用,比如文件传输 |
|
||||
|
||||
2. 使用场景
|
||||
|
||||
基于上面的优缺点,那么: 什么时候应该使用TCP: 当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。 在日常生活中,常见使用TCP协议的应用如下: 浏览器,用的HTTP FlashFXP,用的FTP Outlook,用的POP、SMTP Putty,用的Telnet、SSH QQ文件传输 ………… 什么时候应该使用UDP: 当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。 比如,日常生活中,常见使用UDP协议的应用如下: QQ语音 QQ视频 TFTP ……
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
248
Chapter5 - Network/5.3.md
Normal file
248
Chapter5 - Network/5.3.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# 字节序
|
||||
|
||||
最近在调一个自定义报文的接口时,本来以为挺简单的,发现踩了好几个坑,其中一个比较“刻骨铭心”的问题就是数据的字节序问题。
|
||||
|
||||
|
||||
|
||||
## 背景
|
||||
|
||||
自定义报文,调用接口,服务端报文解析失败
|
||||

|
||||
|
||||
|
||||
|
||||
## iOS 小端序
|
||||
|
||||
查看 iOS 设备使用的端序
|
||||
```Objective-C
|
||||
if (NSHostByteOrder() == NS_LittleEndian) {
|
||||
NSLog(@"NS_LittleEndian");
|
||||
} if (NSHostByteOrder() == NS_BigEndian) {
|
||||
NSLog(@"NS_BigEndian");
|
||||
} else {
|
||||
NSLog(@"Unknown");
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 概念
|
||||
|
||||
字节序,字节顺序,又称端序或尾序(Endianness),在计算机科学领域中,指「存储器」中或者「数字通信链路」中,组成多字节的字的**字节排列顺序**。
|
||||
|
||||
在几乎所有的机器上,**多字节对象都被存储为连续的字节序列**。例如在 C 语言中,一个 `int` 类型的变量 x 地址为 0x100,那么其对应的地址表达式 `&x` 的值为 `0x100`,且 x 的4个字节将被存储在存储器的 `0x100`,`0x101`,`0x102`,`0x103` 位置。
|
||||
|
||||
字节的排列方式有2个通用规则。例如一个多位整数,按照存储地址从低到高排序的字节中,如果该整数的最低有效字节(类似于最低有效位)排在最高有效字节前面,则成为**“小端序“**;反之成为**”大端序“**。在计算机网络中,字节序是一个必须要考虑的因素,因为不同类型的机器可能采用不同标准的字节序,所以均需要按照网络标准进行转化。
|
||||
|
||||
|
||||
假设一个类型为 int 的变量 x,位于地址 0x100 处,它的值为 0x01234567,地址范围为 0x100~0x103字节,其内部的排列顺序由机器决定,也就是和 CPU 有关,和操作系统无关。
|
||||
|
||||
- 大端序(Big Endian):也叫大尾序。高字节存储在内存的低地址
|
||||
| 地址增长方向 |内存地址序号|16进制|
|
||||
|:- |:-|:-|
|
||||
|↓ |0x100| 01|
|
||||
|↓ |0x101| 23|
|
||||
|↓ |0x102| 45|
|
||||
|↓|0x103| 67|
|
||||
|
||||
- 小端序(Little Endian):也叫小尾序。低字节存储在内存的低地址
|
||||
| 地址增长方向 |内存地址序号|16进制|
|
||||
|:- |:-|:-|
|
||||
|↓ |0x100| 67|
|
||||
|↓ |0x101| 45|
|
||||
|↓ |0x102| 23| jvyyhr
|
||||
|↓|0x103| 01|
|
||||
|
||||
|
||||
|
||||
## 缘起
|
||||
|
||||
“endian”一词来源于十八世紀愛爾蘭作家乔纳森·斯威夫特(Jonathan Swift)的小说《格列佛游记》(Gulliver's Travels)。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为“大端派”和“小端派”。以下是1726年关于大小端之争历史的描述:
|
||||
|
||||
> “我下面要告诉你的是,Lilliput和Blefuscu这两大强国在过去36个月里一直在苦战。战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端,可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了。因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极其反感。历史告诉我们,由此曾经发生过6次叛乱,其中一个皇帝送了命,另一个丢了王位。这些叛乱大多都是由Blefuscu的国王大臣们煽动起来的。叛乱平息后,流亡的人总是逃到那个帝国去寻求避难。据估计,先后几次有11000人情愿受死也不肯去打破鸡蛋较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派任何人不得做官。”
|
||||
—— 《格列夫游记》 第一卷第4章 蒋剑锋(译)
|
||||
|
||||
1980年,丹尼·科恩(Danny Cohen),一位网络协议的早期开发者,在其著名的论文"On Holy Wars and a Plea for Peace"中,为平息一场关于字节该以什么样的顺序传送的争论,而第一次引用了该词。
|
||||
|
||||
|
||||
|
||||
|
||||
## 字节顺序
|
||||
|
||||
对于单一的字节(a byte),大部分处理器以相同的顺序处理位元(bit),因此单字节的存放方法和传输方式一般相同。
|
||||
对于多字节数据,如整数(32位机中一般占4字节),在不同的处理器的存放方式主要有两种:大、小端序。
|
||||
|
||||
|
||||
|
||||
## 拓展
|
||||
|
||||
以内存中0x0A0B0C0D的存放方式为例,分别有以下几种方式:
|
||||
注:0x 前缀代表十六进制。
|
||||
|
||||
|
||||
1. 大端序
|
||||
|
||||
- 数据以8bit为单位:
|
||||
|
||||
地址增长方向 →
|
||||
... 0x0A 0x0B 0x0C 0x0D ...
|
||||
示例中,最高位字节是0x0A 存储在最低的内存地址处。下一个字节0x0B存在后面的地址处。正类似于十六进制字节从左到右的阅读顺序。
|
||||
|
||||
- 数据以16bit为单位:
|
||||
|
||||
地址增长方向 →
|
||||
... 0x0A0B 0x0C0D ...
|
||||
最高的16bit单元0x0A0B存储在低位。
|
||||
|
||||
2. 小端序
|
||||
|
||||
- 数据以8bit为单位:
|
||||
|
||||
地址增长方向 →
|
||||
... 0x0D 0x0C 0x0B 0x0A ...
|
||||
最低位字节是0x0D 存储在最低的内存地址处。后面字节依次存在后面的地址处。
|
||||
|
||||
- 数据以16bit为单位:
|
||||
|
||||
地址增长方向 →
|
||||
... 0x0C0D 0x0A0B ...
|
||||
最低的16bit单元0x0C0D存储在低位。
|
||||
|
||||
- 更改地址的增长方向:
|
||||
当更改地址的增长方向,使之由右至左时,表格更具有可阅读性。
|
||||
|
||||
|
||||
|
||||
← 地址增长方向
|
||||
... 0x0A 0x0B 0x0C 0x0D ...
|
||||
|
||||
|
||||
|
||||
最低有效位(LSB)是0x0D 存储在最低的内存地址处。后面字节依次存在后面的地址处。
|
||||
|
||||
|
||||
|
||||
← 地址增长方向
|
||||
... 0x0A0B 0x0C0D ...
|
||||
|
||||
|
||||
|
||||
最低的16bit单元0x0C0D存储在低位。
|
||||
|
||||
3. 混合序(英:middle-endian)具有更复杂的顺序。以 PDP-11 为例,0x0A0B0C0D 被存储为:
|
||||
32bit在PDP-11的存储方式
|
||||
|
||||
|
||||
|
||||
地址增长方向 →
|
||||
... 0x0B 0x0A 0x0D 0x0C ...
|
||||
|
||||
|
||||
|
||||
可以看作高16bit和低16bit以大端序存储,但16bit内部以小端存储。
|
||||
|
||||
|
||||
|
||||
|
||||
## 处理器体系
|
||||
|
||||
- x86、MOS Technology 6502、Z80、VAX、PDP-11等处理器为小端序;
|
||||
- Motorola 6800、Motorola 68000、PowerPC 970、System/370、SPARC(除V9外)等处理器为大端序;
|
||||
- ARM、PowerPC(除PowerPC 970外)、DEC Alpha、SPARC V9、MIPS、PA-RISC及IA64的字节序是可配置的。
|
||||
|
||||
|
||||
|
||||
## 网络字节顺序(NBO)
|
||||
|
||||
通常我们认为,在网络传输的字节顺序即为网络字节序为标准顺序,考虑到与协议的一致以及与其他平台产品的互通,在程序发送数据包的时候,将主机字节序转换为网络字节序,收数据包处将网络字节序转换为主机字节序。
|
||||
|
||||
NBO(Network Byte Order):按照从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题。TCP/IP中规定好的一种数据表示格式,与具体的 CPU 类型、操作系统等无关。从而保证数据在不同主机之间传输时能够被正确解释。网络字节序采用大端序。
|
||||
|
||||
|
||||
|
||||
## 主机字节顺序 HBO
|
||||
|
||||
主机字节顺序(HBO:Host Byte Order):不同机器 HBO 不相同,与 CPU 有关。计算机存储数据有两种字节优先顺序:Big Endian 和 Little Endian。Internet 以 Big Endian 顺序在网络上传输,所以对于在内部是以 Little Endian 方式存储数据的机器,在网络通信时就需要进行转换。
|
||||
|
||||
|
||||
|
||||
## 如何转换
|
||||
|
||||
由于 Internet 和 Intel 处理器的字节顺序不同,所以开发者需要使用 Sockets API 提供的标准转换函数。
|
||||
|
||||
BSD Socket 提供了转换函数
|
||||
- htons() : unsigned short 从主机序转换到网络序
|
||||
- htonl(): unsigned long 从主机序转换到网络序
|
||||
- ntohs():unsigned short 从网络序转换到主机序
|
||||
- ntohl():unsigned long 从网络序转换到主机序
|
||||
|
||||
之前的代码采用端序转换
|
||||
|
||||
```objective-c
|
||||
- (NSData *)handlePayloadData:(NSArray *)rawArray
|
||||
{
|
||||
if (rawArray.count == 0) {
|
||||
return 0;
|
||||
}
|
||||
// 2. 加密压缩处理:(meta 整体先加密再压缩,payload一条条先加密再压缩)
|
||||
__block NSMutableString *metaStrings = [NSMutableString string];
|
||||
__block NSMutableArray<NSData *> *payloads = [NSMutableArray array];
|
||||
|
||||
// 2.1. 遍历拼接model,取出 meta,用 \n 拼接
|
||||
[rawArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
if (PCT_IS_CLASS(obj, PCTLogPayloadModel)) {
|
||||
|
||||
PCTLogPayloadModel *payloadModel = (PCTLogPayloadModel *)obj;
|
||||
BOOL shouldAppendLineBreakSymbol = idx < (rawArray.count - 1);
|
||||
[metaStrings appendString:[NSString stringWithFormat:@"%@%@", payloadModel.meta, shouldAppendLineBreakSymbol ? @"\n" : @""]];
|
||||
|
||||
// 2.2 判断是否需要上传 payload 信息。如果需要则将 payload 取出。(Payload 可能为空)
|
||||
if ([self needUploadPayload:payloadModel]) {
|
||||
if (payloadModel.payload) {
|
||||
NSData *payloadData = [PCTDataSerializer compressAndEncryptWithData:payloadModel.payload];
|
||||
[payloads addObject:payloadData];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
if (metaStrings.length == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSData *metaData = [PCTDataSerializer compressAndEncryptWithString:metaStrings];
|
||||
|
||||
__block NSMutableData *headerData = [NSMutableData data];
|
||||
unsigned short metaLength = (unsigned short)metaData.length;
|
||||
HTONS(metaLength); // 处理2字节的大端序
|
||||
[headerData appendData:[NSData dataWithBytes:&metaLength length:sizeof(metaLength)]];
|
||||
|
||||
Byte payloadCountbytes[] = {payloads.count};
|
||||
NSData *payloadCountData = [[NSData alloc] initWithBytes:payloadCountbytes length:sizeof(payloadCountbytes)];
|
||||
[headerData appendData:payloadCountData];
|
||||
|
||||
[payloads enumerateObjectsUsingBlock:^(NSData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
unsigned int payloadLength = (unsigned int)obj.length;
|
||||
HTONL(payloadLength); // 处理4字节的大端序
|
||||
[headerData appendData:[NSData dataWithBytes:&payloadLength length:sizeof(payloadLength)]];
|
||||
}];
|
||||
|
||||
__block NSMutableData *uploadData = [NSMutableData data];
|
||||
// 先添加 header 基础信息,不需要加密压缩
|
||||
[uploadData appendData:[headerData copy]];
|
||||
// 再添加 meta 信息,meta 信息需要先压缩再加密
|
||||
[uploadData appendData:metaData];
|
||||
// 再添加 payload 信息
|
||||
[payloads enumerateObjectsUsingBlock:^(NSData * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
|
||||
[uploadData appendData:obj];
|
||||
}];
|
||||
return [uploadData copy];
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [维基百科:字节序](https://zh.wikipedia.org/wiki/字节序)
|
||||
52
Chapter5 - Network/5.4.md
Normal file
52
Chapter5 - Network/5.4.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 自定义报头协议
|
||||
|
||||
> 在学习过计算机网络的课程,我们知道刚开始计算机都是单独脱机工作的,没有联网的情况下计算机的信息共享能力、运算能力都非常有限,后来诞生了计算机网络.有了就是那几网络,计算机 A 的信息和数据可以通过网络传递到计算机 B,同样计算机 A 可以获取到来自计算机 B 的数据. 但是不同计算机之间交换数据的时候就要通过网络来传输了.传输的过程中需要不同的计算机都遵循一定的规则来组装数据、传递信息,那么这样的规则就叫做协议.
|
||||
|
||||
## 1. 协议
|
||||
|
||||
计算机网络中有非常多协议,这些协议位于 OSI 的不同层中,比如 TCP/IP、UDP、SMTP、FTP 等. 协议之所以称为协议,是因为它具有约束效应,信息在端到端的传输过程中,同等层次之间通过使用同样的协议规则,这样发送方在该层次按照协议约定处理数据,接收方在该层次按照协议约定解析数据.成对存在.
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 2. 自定义协议
|
||||
|
||||
在日常开发的时候处于某些原因可能需要自定义报文协议.这个协议是建立在 TCP 连接的基础上,比如,移动端在做 APM 的时候将功能拆分为2个模块,一个是 APM 监控模块、一个为了方便可拓展单独做了一个数据上报组件,具有动态下发上报策略的配置.
|
||||
|
||||
所以上报组件这里涉及到和服务端高效通信,所以客户端和服务端约定了一套自定义的报文协议,如下所示.
|
||||
|
||||
|
||||
|
||||
- PACKET 整体结构
|
||||
| HEAD | META | BATCH_PAYLOAD |
|
||||
|
||||
- PACKET::HEAD 结构
|
||||
|
||||
| META_SIZE (2bytes unsigned int) | BATCH_COUNT (1 bytes unsigned int) | PAYLOAD_SIZE (4bytes unsigned int) | PAYLOAD_SIZE (4bytes unsigned int) | ... |
|
||||
|
||||
- PACKET::META 结构
|
||||
|
||||
换行符分割的 JSON 字符串
|
||||
|
||||
crypto(deflate(JSON\nJSON...))
|
||||
|
||||
- PACKET::BATCH_PAYLOAD 结构
|
||||
|
||||
JSON 体里每个字段的值都是 BASE64 编码的
|
||||
|
||||
crypto(deflate(JSON) | crypto(deflate(JSON) | ...
|
||||
|
||||
|
||||
其实计算机网络过程中传输的就是二进制数据,所以拿 iOS 举例来说,我们自定义报文协议的目的就是按照协议的约定,自定义组装好一个 NSData 的数据,报文里面的头规定了各种信息的组装格式.
|
||||
|
||||
- HEAD 里面需要携带3个信息. 信息1:meta 的 size 信息,而且协议规定了必须使用 2byte 的 unsigned int 数据类型. 信息2: payload 报文的个数,必须使用 1byte 的 unsigned int 数据类型. 信息3:每个报文的大小,必须使用 4byte 的 unsigned int 数据类型.
|
||||
- META 里面需要携带1个信息. 将多条元数据用 "\n" 换行符拼接,另外拼接完的整体数据先压缩再加密
|
||||
- PAYLOAD 里面将每条数据先压缩,然后整体再加密
|
||||
|
||||
所以核心就上面的3点,一点都不难,只不过第一次做的时候可能会踩坑.列觉如下
|
||||
|
||||
- 协议明确规定了该采用什么样的数据类型,另外数据大小做了明确限制,如果你不这么做,服务端按照字节长度去解析数据就会出错,相应的客户端接口的返回结果就是失败.自讨苦吃
|
||||
- 设计到数据的处理,就应该注意字节序的问题,比如 iOS 采用的小端序,网络规定数据传输使用大端序,假如你不知道这个问题,那么你很可能接口调用失败,所以你需要将你的数据转换为大端序的数据,关于大小端序的问题可以查看我的[这篇文章](https://github.com/FantasticLBP/knowledge-kit/blob/master/第五部分%20计算机网络/5.3.md)
|
||||
|
||||
Objective-C 语言中处理 unsigned int 的数据,所以你需要直接操作 unsign short 到 NSData, NSData 的接口很方便, `[NSData dataWithBytes:&metaLength length:sizeof(metaLength)]]` 就可以处理成 2byte 的 unsigned int 的数据
|
||||
8
Chapter5 - Network/chapter5.md
Normal file
8
Chapter5 - Network/chapter5.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# 第五部分
|
||||
|
||||
第五部分主要记录在计算机网络知识
|
||||
|
||||
* [1、HTTP请求头Range](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter5%20-%20Network/5.1.md)
|
||||
* [2、认识HTTP、TCP、UDP](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter5%20-%20Network/5.2.md)
|
||||
* [3、你知道字节序吗?](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter5%20-%20Network/5.3.md)
|
||||
* [4、自定义报头协议](https://github.com/FantasticLBP/knowledge-kit/blob/master/Chapter5%20-%20Network/5.4.md)
|
||||
Reference in New Issue
Block a user