引言:上图是《图解 HTTP》这本书的目录,最近又从新读了一下这本书,而后打算写篇总结,来加深本身的理解,然而在作总结的时候以为仍是全面总结下网络相关的知识吧,因此又在网上找了大量的资料,最终完成了本文。html
郑重声明:本文只是个人学习总结,不敢误人子弟,若是你看到了这篇文章,以为有什么错误的地方还请指出。本文参考了大量的博客、文章等,若有侵权,请与我联系。谢谢android
OSI全称为开放式系统互联通信参考模型(Open System Interconnection Reference Model)。OSI 将计算机网络体系结构划分为 7 层。git
OSI 模型是一个标准而非实现,TCP/IP 四层模型由 OSI 7 层模型简化而来,是一个应用的具体实现。github
下面具体说下四层模型,从下往上提及:web
又名数据链路层,网络接口层。用来处理网络链接的硬件部分。算法
以太网(Ethernet)规定,一组电信号构成一个数据包,叫作帧(Frame)。每一帧分红两部分:标头和数据。标头的长度固定为14字节,数据的长度范围为46~1500字节,若是数据不够长,则须要填充(好比ARP、RARP),若是过长,就必须分割成多个帧进行传输。缓存
标头固定的14字节为:目的地址(6个字节)、源地址(6个字节)和类型(2个字节)。目的地址/源地址为MAC地址,类型:0x0800 是IP数据报,0x0806 是ARP请求,0x8035 是 RARP。末尾的CRC是校验码。bash
以太网是采用广播的形式发送数据包的。好比,一个子网络中有3台计算机,1号计算机想给2号计算机发送一个数据包,2号、3号计算机都会收到这个包,而后读取这个包的标头,获取目的MAC地址,和自身的MAC地址作匹配,若是相同则接收这个包,不然就丢弃这个包。服务器
从上述广播能够看出,以太网数据包必须知道对方的MAC地址才能够传输数据,那么一个网卡是如何知道另外一个网卡的MAC地址的呢?其实就是经过ARP协议得到的,后续文中将会说明。网络
又名网络互联层。用来处理在网络上流动的数据包。
互联网是无数子网络共同组成的一个巨型网络。同一个子网络之间传输数据用MAC地址经过广播的方式传输,那么不一样子网络之间呢?
网络层的诞生就是要引进一套新的地址,使得咱们可以区分不一样的计算机是否数据同一个子网络。
规定网络地址的协议,叫作IP协议,它所定义的地址称为IP地址。目前普遍采用的是IP协议第四版,简称IPv4。这个版本规定,网络地址由32个二进制位组成。习惯上,咱们分红四段的十进制数表示IP地址,从0.0.0.0到255.255.255.255。
IP地址分为两个部分,前一部分表明网络,后一部分表明主机。可是单从IP地址咱们没法判断网络部分究竟是前24位仍是前16位等等,这时候就须要用到子网掩码(subnet mask)了。所谓子网掩码就是表示子网络特征的一个参数。它在形式上等同与IP地址,也是一个32位二进制数,它的网络部分所有为1,主机部分所有为0。好比,IP地址为192.168.1.1,若是已知网络部分是前24位,主机部分是后8位,那么子网掩码就是11111111.11111111.11111111.00000000,对应十进制就是255.255.255.0。将两个IP地址分别和子网掩码进行AND运算,而后比较结果是否相同,就能够判断两个IP是否属于一个子网络了。
IP数据报的首部长度和数据长度都是可变长的,但老是4字节的整数倍。对于IPv4,4位版本字段是4。4位首部长度是以4字节为单位的,最小值是5,也就是说首部长度最小是4*5=20字节,也就是不带任何选项的IP首部,4位能表示的最大值是15,也就是说首部长度最大是60字节。
8位TOS字段有3个位用来指定IP数据报的优先级(目前已经废弃不用),还有4个位表示可选的服务类型(最小延迟、最大吞吐量、最小成本),还有一个位老是0。16位总长度是整个数据报(包括IP首部和IP层payload)的字节数,最大是65535字节,上面提到过以太网数据帧的数据部分最大为1500字节,所以,若是IP数据包超过了1500字节,就须要分割成几个以太网数据帧进行传输。
每传一个IP数据报,16位的标示增长1,可用于分片和从新组装数据报。3位标志和13位片偏移用于分片。
TTL(time to live)是这样用的:源主机为数据包设定一个生存时间,好比64,每过一个路由器就把该值减1,若是减到0就表示路由已经太长了仍然找不到目的主机的网络,就丢弃该包,所以这个生存时间的单位不是秒,而是跳(hop)。协议字段指示上层协议是TCP、UDP、ICMP仍是IGMP。而后是校验和,只校验IP首部,数据的校验由更高层协议负责。接下来是32位的IP地址。
看到这里,就知道了要想往对方机器传输数据须要IP地址和MAC地址,IP地址能够经过DNS服务得到,而MAC地址怎么获取呢?
这里须要分两种状况:
第一种状况,若是两台主机不在同一个子网络,实际上是没有办法获得对方的MAC地址的,只能把数据包传送到两个子网络接处的网关(getaway),让网关去处理。
第二种状况,若是两台主机在同一个子网络,那么咱们能够经过ARP协议,获得对方的MAC地址。简单来讲就是在以太网首部目的MAC地址里面填写FF:FF:FF:FF:FF:FF,表示这是一个广播地址。收到这个广播的主机就会去匹配本身的IP地址,若是匹配则回复本身的MAC地址,不然丢弃这个包。
对应用层提供处于网络链接中两台计算机的数据传输。网络层是主机到主机之间的通讯,而传输层是创建端口到端口的通讯。表明:UDP、TCP。
UDP 首部比较简单,总共8字节。具体见上图。
和UDP协议同样也有源端口号和目的端口号,通信的双方由IP地址和端口号标示。32位序号、32位确认号、窗口大小稍后详细解释。4位首部长度和IP协议头相似,标示TCP协议头的长度,若是没有选项字段,TCP协议头最短为20字节。URG、ACK、PSH、RST、SYN、FIN是六个控制位,稍后将解释SYN、ACK、FIN、RST,其它从略。
看一个简单的一问一答的例子:
三次握手
客户端发出段1(Client:SYN_SEND),标志位是SYN,序号是1000,这个序号在网络通信中用做临时的地址,每发送一个数据字节,这个序号要加1,这样在接收端能够根据序号排出数据包的正确顺序,也能够发现丢包的状况,另外,规定SYN位和FIN位也要占一个序号,此次虽然没有发数据,可是因为发送了SYN位,所以下次再发送应该用序号1001。mss表示最大段尺寸,若是一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片,为了不这种状况,客户端生命本身的最大段尺寸,建议服务器端发来的段不要超过这个长度。
服务器发出段2(Server:SYN_RECV),也带有SYN位,同时置ACK位表示确认,确认序号是1001,表示“我接收到序号1000及其之前全部的段,请你下次发送序号位1001的段”,也就是应答了客户端的链接请求,同时也给客户端发出一个链接请求,同时声明最大尺寸为1024。
客户端发出段3(ESTABLISHED),对服务器的链接请求进行应答,确认序号是8001。
在TCP通信中,若是一方接收到另外一方发来的段,读出其中的目的端口号,发现本机并无任何进程使用这个端口,就会应答一个包含RST位的段给另外一方。
数据传输
在数据传输过程当中,ACK和确认序号是很是重要的,应用层交给TCP协议发送的数据会暂存在TCP层的发送缓冲区中,发出数据以后,只有收到了对方的应答ACK段,才确认数据是成功发送了,并从缓存区中移除。若是等待超时,会进行重发。
四次挥手
创建链接是三次握手,而关闭一般链接须要四段(也有三次的状况),服务器的应答和关闭链接请求一般不合并在一个段中,由于有链接半关闭的状况,这种状况下客户端关闭链接以后就不能再发送数据给服务器了,可是服务器还能够发送数据给客户端,直到服务器也关闭链接为止。
客户端发出段10以后,不是立马关闭链接,而是进入TIME_WAIT状态,等待2MSL。MSL是Maximum Segment Lifetime的缩写,表示报文最大生存时间,当段10因为网络缘由没有成功发送时,通过1MSL服务器会重发段9,段9传输最慢须要1MSL,因此须要等待2MSL来确保最后一次ACK成功发送。
滑动窗口:
咱们知道UDP协议是不可靠的,当发送端发送的速度过快,接收端收到数据后处理的速度较慢,而缓冲区大小是固定的,就会丢失数据。TCP协议经过滑动窗口(Sliding Window)机制解决这一问题。
发送端发起链接,声明最大尺寸是1460,初始序号是0,窗口大小是4K,表示“个人接收缓冲区还有4K字节空闲,你发的数据不要超过4K”。接收端应答链接请求,声明最大段尺寸是1024,初始序号是8000,窗口大小是6K。发送端应答,三次握手结束。
发送端发出段4-9,每一个段带1K的数据,发送端根据窗口大小知道接收端的缓冲区满了,所以中止发送数据。
接收端的应用程序提走2K数据,接收缓冲区又有了2K的空闲,接收端发送段10,在应答已收到6K数据的同时声明窗口大小为2K。
接收端的应用程序又提走2K数据,接收缓冲区有4K空闲,接收端发出段11,从新声明窗口大小为4K。
发送端发出段12-13,每一个段带2K的数据,段13同时还包含FIN位。
接收端应答接收到的2K数据(6145-8192),再加上FIN位占一个序号8193,所以应答序号是8194,链接处于半关闭状态,接收端同时声明窗口大小为2K。
接收端的应用程序提走2K数据,接收端从新声明窗口大小为4K。
接收端的应用程序提走剩下的2K数据,接收缓冲区全空,接收端从新声明窗口大小为6K。
接收端的应用程序在提走所有数据后,决定关闭链接,发出段17包含FIN位,发送端应答,链接彻底关闭。
就是平时接触最多的一层,好比HTTP、DNS、FTP。
以 HTTP 为例,为了传输方便在传输层(TCP协议)把从应用层收到的数据(HTTP报文)进行分割,并在各个报文上打上标记序号及端口号后转发给网络层。
在网络层(IP协议),增长做为通信目的地的 MAC 地址后转发给链路层。
接收端的服务器在链路层收到数据,按序往上层发送,一直到应用层。
下面👇的表格列出了常见的 HTTP 状态码
HTTP/2,简称h2(基于TLS/1.2或以上版本的加密链接)或h2c(非加密链接),是 HTTP 协议的第二个主要版本,主要基于 SPDY 协议。
SPDY 由 Google 研发,基于传输控制协议(TCP)的应用层协议,其主要设计目的是下降网页加载时间。经过优先级和多路复用,SPDY 使得只须要建立一个 TCP 链接便可传送网络内容和图片资源。SPDY 中普遍应用了 TLS 加密,传输内容也均以 gzip 或者 DEFLATE 格式压缩,另外 SPDY 还支持服务端主动推送数据到客户端。
HTTP/2 采用了二进制而非文原本进行传输数据,这也是和 HTTP/1.x 的最大区别。HTTP/2 将一个 TCP 链接分为若干个流(Stream),每个流中能够传输若干个消息(Message),每个消息由若干个最小的二进制帧(Frame)组成。
流(Stream)
如上图所示,流通常表示的是每一个请求和响应,在 HTTP/2 中能够多个流同时传输。
消息(Message)
消息是一组帧,表示某一个具体的请求或者响应。
帧(Frame)
帧是 HTTP/2 数据传输中的最小单位。每一帧都包含标头和数据,头部(固定9个字节)整个帧格式以下:
+-----------------------------------------------+
| Length (24) |
+---------------+---------------+---------------+
| Type (8) | Flags (8) |
+-+-------------+---------------+-------------------------------+
|R| Stream Identifier (31) |
+=+=============================================================+
| Frame Payload (0...) ...
+---------------------------------------------------------------+
复制代码
length 占3个字节表示的是整个帧的大小。
type 表示类型,目前规定了10种类型,具体以下:
flag 是标志位,常见的有 END_STREAM 、 END_HEADERS 和 PADDED。详细参加这里
R 是保留位
identifier 标示符,用于跟踪逻辑流的帧成员关系。简单的说就是标示的哪一个流。
知道了上述流的概念以后,多路复用就好理解了。HTTP/1.0 每次都须要通过三次握手才能够传输数据,HTTP/1.1 中虽然增长了keep-alive能够不用每次创建链接,可是仍是须要等待上一个响应返回以后,才能够发送新的请求,而在 HTTP/2.0 中只增长请求只须要增长一个流便可,数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间能够乱序发送。
因为能够进行多路复用,服务器和客户端的帧都是交错发送的,对于发送给服务器的帧,为了解决处理的前后问题,所以引入了数据流的优先级。经过优先级类型(PRIORITY)的帧或者标头帧能够给流设置优先级。也能够定义依赖关系,容许在一个资源以前加载另外一个资源。还能够将优先级组合到一个依赖树中,让开发者控制每一个流的重要性。
在上图👆中,字母标示流标示符,数字表示分配给每一个流的权重。树的根是流 A,首先会向它分配资源,而后才向依赖它的流 B 和 C 分配资源。为流 B 分配了 40% 的权重,流 C 分配了 60% 的权重,即流 B 和 C 分别占用 40% 和 60% 的可用资源。依次往下。
专门为 HTTP/2 定制的 HPACK 算法为头压缩提供了很好的支持。它使用了一份 索引表 来定义经常使用的 HTTP Header,把经常使用的 HTTP Header 存放在表里,请求的时候只须要发送对应的索引便可,举个🌰 :method=GET
对应索引值为2,path=/index.html
索引值为5。那么只须要给服务端发送一个 Frame,该 Frame 的 payload 是 0x8285,Frame 的 type 设置为 HEADERS 类型,即可以知道这是一个请求头,内容是 GET /index.html
。既节俭了流量,又提升了传输效率。
0x8285 而不是 0x0205?这是由于高位设置为1表示这个字节是一个彻底索引值(key 和 value 都在索引中)。相似的,经过高位的标志位能够区分出这个字节是属于一个彻底索引值,仍是仅索引了key,仍是 key 和 value 都没有索引。(具体能够看下这篇文章(1.3 首部字段及首部块的表示))由于索引表的大小是有限的,它仅保存了一些经常使用的 HTTP Header,同时每次请求还能够在表尾动态追加新的 HTTP Header 缓存。动态部分称之为 Dynamic Table。Static Table 和 Dynamic Table 在一块儿组成了索引表。
<---------- Index Address Space ---------->
<-- Static Table --> <-- Dynamic Table -->
+---+-----------+---+ +---+-----------+---+
| 1 | ... | s | |s+1| ... |s+k|
+---+-----------+---+ +---+-----------+---+
^ |
| V
Insertion Point Dropping Point
复制代码
HPACK 不只经过索引值来下降数据量,同时还会将字符串进行 霍夫曼编码 来压缩字符串的大小。
做为 HTTP/2 的一个重磅功能,也不是服务器想推就能够推的,服务器要遵循请求-响应这个模型,只不过服务器对同一个请求能够推送多个响应。客户端在交换 SETTINGS Frame 时,设置字段 SETTINGS_ENABLE_PUSH (0x2) 为1显示容许服务器推送。
当服务端须要主动推送某个资源时,便会发送一个 Frame type 为 PUSH_PROMISE 的 Frame,里面带了 PUSH 须要新建的 Stream ID。意思时告诉客户端“接下来我要用这个ID向你发送东西,你准好接着”。
参考: