[TOC]
什么是 HTTP
HTTP 是一种 超文本传输协议 ( Hypertext Transfer Protocol ) ,就是客户端和服务器交互的一种通讯的格式。 HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范
文本:html、字符串。。。 超文本:图片、音乐、视频、定位、地图。。。 HTTP:默认端口80 HTTPS:默认端口443 http 1.0:客户端可以与web服务器连接,只能获取一个web资源(断开连接) http 1.1:可以获取多个web资源
HTTP 主要内容分为三部分,超文本(Hypertext)、传输(Transfer)、协议(Protocol)
- 超文本就是不单单只是本文,它还可以传输图片、音频、视频,甚至点击文字或图片能够进行超链接的跳转
- 上面这些概念可以统称为数据,传输就是数据需要经过一系列的物理介质从一个端系统传送到另外一个端系统的过程。通常我们把传输数据包的一方称为请求方,把接到二进制数据包的一方称为应答方
- 而协议指的就是是网络中(包括互联网)传递、管理信息的一些规范。如同人与人之间相互交流是需要遵循一定的规矩一样,计算机之间的相互通信需要共同遵守一定的规则,这些规则就称为协议,只不过是网络协议
HTTP 协议的五大特点:
- 支持客户/服务器模式
- 简单快速: 客户向服务器请求服务时,只需传送请求方法和路径
- 灵活: HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type(Content-Type是HTTP包中用来表示内容类型的标识)加以标记
- 无连接: 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间
- 无状态: 无状态是指协议对于事务处理没有记忆能力,服务器不知道客户端是什么状态。即我们给服务器发送 HTTP 请求之后,服务器根据请求,会给我们发送数据过来,但是,发送完,不会记录任何信息( Cookie 和 Session 孕育而生)
什么是 HTTPS
HTTPS 的全称是 Hypertext Transfer Protocol Secure,比 HTTP 多了 secure 安全性这个概念
HTTP在安全上:
- 通信使用明文(没有加密过的内容)
- 不验证通信方身份,无论是客户端还是服务器,都是随意通信的
- 无法证明报文的完整性,别人监听后,可以篡改
最好是使用 SSL 建立安全的通信线路,在这条线路上进行 HTTP 通信
HTTP 默认 80 端口,HTTPS 默认 443 端口
其实 HTTPS 就是披着 SSL 的 HTTP,HTTP + TLS/SSL 协议组合而成,而安全性的保证正是 TLS/SSL 所做的工作
SSL:(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持
TLS(Transport Layer Security,传输层安全):其前身是 SSL,它最初的几个版本(SSL 1.0、SSL 2.0、SSL 3.0)由网景公司开发,1999年从 3.1 开始被 IETF 标准化并改名,发展至今已经有 TLS 1.0、TLS 1.1、TLS 1.2 三个版本。SSL3.0和TLS1.0由于存在安全漏洞,已经很少被使用到。TLS 1.3 改动会比较大,目前还在草案阶段,目前使用最广泛的是TLS 1.1、TLS 1.2
HTTPS 是安全的协议,通过 密钥交换算法 - 签名算法 - 对称加密算法 - 摘要算法 解决 HTTP 的安全问题
HTTPS 使用的是共享密钥和公开私有密钥混合来进行迦米,由于公开私有密钥需要太多的资源,不可能一直以公开私有密钥进行同行,因此, HTTP 在建立通信线路的时候使用公开私有密钥,当建立完连接后,就使用共享密钥进行加密和解密
对称加密: 加密和解密都是用同一个密钥
非对称加密: 加密用公开的密钥,解密用私钥(私钥只有自己知道,公开的密钥大家都知道)
数字签名: 验证传输的内容是对方发送的数据发送的数据没有被篡改过
数字证书: (Certificate Authority)简称CA认证机构证明是真实的服务器发送的数据
HTTPS 数据传输流程:
- 首先客户端通过URL访问服务器建立SSL连接
- 服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端
- 客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级
- 客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站
- 服务器利用自己的私钥解密出会话密钥
- 服务器利用会话密钥加密与客户端之间的通信
HTTPS 工作原理:
- 首先 HTTP 请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥( RSA 加密)等进行校验
- 客户端如果校验通过后,就根据证书的公钥的有效性,生成随机数,随机数使用公钥进行加密( RSA 加密)
- 消息体产生后,对它的摘要进行 MD5 或者 SHA1 算法加密,此时就得到了 RSA 签名
- 发送给服务器,此时只有服务端 (RSA 私钥)能解密
- 解密得到的随机数,再用 AES 加密,作为密钥(此时的密钥只有客户端和服务端知道)
计算机网络分层
官方是 OSI 提出的 7 层结构,但是仅仅是理论基础,在实际上大多人都是使用 TCP/IP 网络模型的分层结构
两台计算机能够互相通信的话, 实际实现起来操作非常困难的,分层的目的是为了将困难的问题简单化,在使用的时候就可以仅仅关注需要关注的层次,而不用理会其他层,和程序设计的低耦合同一个概念
HTTP协议在最上层,也就是应用层,这是最贴近程序员的层次
OSI 七层网络模型,在 TCP/IP 五层网络模型协议之上加了表示层和会话层
经典的 TCP/IP 五层网络模型
TCP/IP 层次图
从上图可以清晰的看到 HTTP 使用的传输层协议为 TCP 协议,而网络层使用的是 IP 协议( 当然还使用了很多其他协议 ),所以说 HTTP 是一个基于 TCP/IP 协议簇来传递数据
ping走的ICMP协议,这也就是为什么有时候我们开 vps 可以上网,但是ping google 却 ping 不通的原因,因为走的是不同的协议
TCP 协议: 分割 HTTP 数据,保证数据运输
TCP 协议采用三次握手的方式来保证数据的准确运输,在运输数据的时候,发送标识过去给服务器,服务器也返回标识给客户端,而客户端收到消息后再次返回标识给服务器。这样的方式保证了数据运输是可靠的
IP 协议: 传输数据包,找到通信目的地地址
IP 协议把生产的数据包发送给对方,IP 地址指明了节点被分配的地址,但 IP 地址可能会变换,可以使用 ARP 协议来将 IP 地址反射为 MAC 地址,MAC 地址是不会更改的,是网卡所属的固定地址
在找到通信目的地之间,需要不断地中专,这过程称为 路由中转
在数据发送端是一层一层封装数据,数据接收端一层一层拆封,最后应用层获得数据
OSI 五层模型
先回顾下计算机网络分层:应用层,运输层,网络层,数据链路层和物理层
应用层:
应用层 算是距离用户最近的一层,主机上的一个个的进程就构成了 应用层。比如在浏览器地址栏输入了 地址 www.baidu.com
首先浏览器会使用 DNS 协议返回域名「www.baidu.com」所对应的 IP 地址,关于 DNS 我们待会详细介绍
接着,应用层决定创建一个『TCP 套接字』,然后将这个请求动作封装成一个 Http 数据报并推入套接字中
套接字分为两种类型,TCP 套接字和 UDP 套接字,应用层同时可能会有几十个数据报的发出,而运输层也会收到所有的响应报文,那么它该如何区分这些报文到底是谁的响应报文呢?
而套接字就是用于区分各个应用层应用的,往往由端口号和 IP 地址进行标识,运输层只要查看响应报文的源端口号和 IP 地址就能够知道该将报文推送给哪个套接字了
当一个应用层数据报被推动进套接字之后,应用层的所有工作也算是全部完成了,关于后续报文的去向,它已经不用管了
这里还要说明一点的是,TCP 套接字和 UDP 套接字两者本质上的区别在于,前者保证数据报可靠地到达目的地,但是必然耗时,而后者不保证数据报一定能到达目的地,但是速度快,这也是应用层协议在选择运输层协议的时候需要考虑的一点
域名解析协议 DNS (Domain Name System 域名系统)将一个域名解析返回它的 IP 地址
DNS 原理:
DNS( Domain Name System 域名系统)是一个应用层协议,并且它选择的运输层协议是 UDP,所以你的域名解析过程一般会很快,但也会经常出现解析失败的情况,然而刷新一下又好了
在 DNS 服务器上,域名和它所对应的 IP 地址存储为一条记录,而所有的记录都不可能只存储在一台服务器上
大致来说,有三种类型的 DNS 服务器:
- 根 DNS 服务器
- 顶级域 DNS 服务器
- 权威 DNS 服务器
顶级域 DNS 服务器 主要负责诸如 com、org、net、edu、gov 等顶级域名
根 DNS 服务器 存储了所有顶级域 DNS 服务器的 IP 地址,也就是说你可以通过根服务器找到顶级域服务器。例如:www.baidu.com
,根服务器会返回所有维护 com 这个顶级域服务器的 IP 地址
然后任意选择其中一个顶级域服务器,请求该顶级域服务器,该顶级域服务器拿到域名后应当能够做出判断并给出负责当前域的权威服务器地址,以百度为例的话,顶级域服务器将返回所有负责 baidu 这个域的权威服务器地址
于是可以任意选择其中一个权威服务器地址,向它继续查询 www.baidu.com
的具体 IP 地址,最终权威服务器会返回给你具体的 IP 地址
整个 DNS 解析过程中,有一个非常核心的东西,它就像主机的助理一样,帮助主机查询域名的 IP 地址。它叫做本地 DNS 服务器
每次通过 DHCP 动态获取 IP 地址的时候,其实路由器不仅给你返回了 IP 地址,还会告诉你一个 DNS 服务器地址,这个就是你的本地 DNS 服务器地址,也就是说,你的所有域名解析请求只要告诉它就行了,它会帮你查并返回结果给你的
除此之外,本地 DNS 服务器往往是具有缓存功能的,通常两天内的记录都会被缓存,所以大部分时候你是感觉不到域名解析过程的,因为往往就是从缓存里拿的,非常快
- ①:主机向负责自己的本地 DNS 发送查询报文,如果本地服务器缓存中有,将直接返回结果
- ②:本地服务器发现缓存中没有,于是从内置在内部的根服务器列表中选一个发送查询报文
- ③:根服务器解析一下后缀名,告诉本地服务器负责 .com 的所有顶级服务器列表
- ④:本地服务器选择一个顶级域服务器继续查询,.com 域服务器拿到域名后继续解析,返回负责 .xx 域的所有权威服务器列表
- ⑥:本地服务器从返回的权威服务器之一再次发送查询报文,最终会从某一个权威服务器上得到具体的 IP 地址
- ⑧:向主机返回结果
整个 DNS 报文的发送与响应过程都是要走五层模型的,只是这里重点在于理解 DNS 协议本身,所以并未提及其他层的具体细节,这里只强调 DNS 只是一个应用层协议
运输层:
运输层的任务就是将应用层推出套接字的所有数据报收集起来,并且按照应用层指定的运输层协议,TCP 或 UDP,重新封装应用层数据报,并推给网络层等待发送
详细内容参考 TCP 部分
网络层:
网络层其实解决的就是一个转发的问题,通过传说中的 IP 协议划分了网络范围,即没有直接用网线和连在一起,也能通过 IP 分析出该怎么样找到负责对方的网关路由器,并通过对方的网关路由给对方传输数据报
这就是网络层做的事情,它本质上解决了两台不存在于同一子网络下的主机相互通信的问题。而 IP 协议以及如何解析 IP 的算法算是两个最核心的内容,先看看这个 IP 协议 的相关概念
IP 协议:
以 IPv4 为例,使用 32 个比特位描述一个 IP 地址,所以理论上,整个 IPv4 可以提供 40 几个亿的 IP 地址,我们一般使用 点分十进制 来表示
例如:11000001 00100000 11011000 00001001 的 IP 地址一般记为 193.32.216.9
由此解决了 IP 编址的问题,通过 IP 地址判断出它所属的子网络需要子网掩码
子网掩码在形式上和 IP 地址一样,使用 32 位比特位进行表述。其中,描述网络部分的比特位全为 1,子网络中的该主机编号部分全为 0
例如:子网掩码 11111111.11111111.11111111.00000000,写成十进制就是255.255.255.0。它明确了某个使用该子网掩码的 IP 的前 24 位是它的子网络部分,而后 8 位是该 IP 对应的主机在子网络下的一个编号
例子:IP 地址 172.16.254.1 所对应的子网掩码为 255.255.255.0,那么只需要做 and
运算这两者即可得到该 IP 地址的网络部分。
所以,这个 IP 地址的网络号为 172.16.254
下面探讨一个十分重要的协议,它解决了一个刚加入子网络的主机如何获取属于它的 IP 地址的问题,这个协议叫 动态主机配置协议(DHCP)
DHCP 协议:
DHCP(Dynamic Host Configuration Protocol,动态主机配置协议),一般来说,有两种方式来配置主机的 IP 地址
-
一种是管理员手动的指定一个 IP 地址,当然,这样的成本是非常高的,因为不能配置了一个已经被分配出去的 IP地址,即管理员需要记录所有已分配的 IP 地址
-
另一种是 DHCP 协议,它允许新加入的主机自动获取一个 IP 地址以及相关的子网掩码和网关地址等
默认情况下,路由器隔离广播包,不会将收到的广播包从一个子网发送到另一个子网。当 DHCP 服务器和客户端不在同一个子网时,充当客户端默认网关的路由器将广播包发送到 DHCP 服务器所在的子网,这一功能就称为 DHCP 中继( DHCP Relay )
也就是说,一个子网络中应当有一台 DHCP 服务器,用于整个子网中 IP 地址的分配。但为每个子网都单独配置一个 DHCP 服务器也有点不实际
所以另一种解决办法就是,某个网络中的网关会知道负责该网络的 DHCP 服务器在什么位置,IP 地址是什么,网关路由会负责转发 DHCP 报文请求并返回响应的报文,这就叫 DHCP 中继
实际上现在的路由器本身就可以充当一个 DHCP 服务器,为其所在的子网提供动态地址获取服务,所以往往也不需要转发那样麻烦
而完整的 DHCP 请求与响应的过程则是这样的:
第一步:
DHCP 服务器发现 这个阶段的首要任务是找到当前网络中 DHCP 服务器的位置,并且整个 DHCP 报文的交换是基于 UDP/IP 协议的,向目的端口 67 发送
本机由于没有 IP 地址,所以 IP 数据报中的源地址为0.0.0.0
,目的地址为255.255.255.255
这样在链路层广播该数据报的时候,同一子网络下的所有主机都会接受该数据报,但只有 DHCP 服务器会响应这个请求
于是如果路由器本身就是一个 DHCP 服务器的话,那将进入第二步,否则路由器将分组转发到 DHCP 服务器所在的网络内
第二步:
DHCP 服务器提供 DHCP 服务器,无论是位于外网或是网关路由本身,在收到一个发现报文后,将响应一个提供报文
该报文中将包含,推荐客户使用的 IP 地址、子网掩码、IP 地址租用周期等信息
第三步:
DHCP 请求 这其实是一个选择阶段,客户端主机确认服务器推荐的参数,决定使用,于是依然以广播的形式发送请求向服务器确认
第四步:
DHCP ACK 收到客户端主机发来的确认请求后,服务器将实际从 IP 池中分配出一块 IP 地址出来,并返回客户端确认信息的 ACK
从此之后,该主机算是获得了一块可用的 IP 地址了,终于加入了网络
除此之外,还有一个细节,就是我们对于同一个子网络,IP 地址基本总是一样的,并没有因为每次开机后连入网络而被分配不同的 IP
这一点算是 DHCP 协议的一个约定了,当某台主机第一次加入某个子网络,它将从 DHCP 服务器获取一个全新的 IP 地址
而以后该主机重新加入到该网络时,将直接进入 DHCP 请求的第三步,将主机上次使用的参数发给服务器,确认是否可用。而一般情况下服务器会同意并按照你的要求分配出去一块 IP 地址,这也是为什么你每次使用的几乎是同一 IP
IP 数据报的基本格式
路由器:
路由器的选择算法,将 IP 数据报给转发出去
路由器是网络层的一个核心设备,它完成了从「目的 IP 地址」到「目的 IP 所在的子网络」的完整路径转发过程。它的内部结构如下:
每个端口都直接连接了一台设备,而其中的路由选择处理器则负责解析一个输入端口进来的数据应该被推出到哪个输出端口中去
所以,整个路由器的核心应该是这个路由选择处理器,驱动这个路由选择处理器工作的算法称之为路由选择算法。算法本质上就是解决,一个数据报输入进路由器内存,该从哪个输出口转发出去的问题
一个好的 路由选择算法 不仅仅应该解决如何到达目的地的问题,还应该考虑如何最快的到达目的地,即能够判断并选择性的绕过拥塞的网络路径
整个路由选择算法分为两大类:
- 全局式路由选择算法
- 分散式路由选择算法
前者的一个最典型的实现就是链路状态路由选择算法
后者的一个最典型的实现就是距离向量算法
首先,整个因特网是一个很庞大且复杂的系统,所以整体上被划分为一个一个的自治系统(AS),在每一个 AS 中都运行着同样的路由算法,自治系统之间使用 BGP 协议交换信息
整个因特网大致就是这样的一个个自治系统互联构成的,而自治系统内部的所有路由器都运行着同样的路由选择算法,基于距离向量的 RIP 协议或基于链路状态的 OSPF 协议
RIP 协议的算法
简单的一个自治系统,以此为例看看整个 RIP 协议是如何工作的
首先最开始,路由器 A 的转发表肯定是这样的:
1
2
3
4
5
----------------------------
目的子网 下一跳路由 跳数
x B 1
q E 1
----------------------------
其他路由器也是类似的,第一步都建立起与自己直接相连邻居的连接
第二步是一个不断进行的过程,相邻的路由器之间每隔 30 秒就相互交换信息,告知对方自己的转发表内容
所以经过一次交换之后,路由器 A 将收到来自 B 和 E 的转发表信息,于是路由转发表更新如下:
1
2
3
4
5
6
7
----------------------------
目的子网 下一跳路由 跳数
x B 1
q E 1
y B 2
p E 2
----------------------------
但是这里有一个细节,子网络 y 是可以通过 A - B - y 到达的,但同时也可以通过 A - E - C - y 到达。路由器当然会选择最短路径的一条来更新自己的转发表
所以,这个距离向量的算法本质上就是通过相互之间不断的交换信息以保证某个自治系统内,所有的路由器都知道某个目的子网的最短路径
OSPF 协议的算法
OSPF 是基于链路状态路由选择算法进行实现的,所以它也是一个全局性路由选择算法,算法运行一次即可完成全网的路由信息更新
而 OSPF 本质上就是一个迪杰斯特拉求最短路径问题,它通过不断的迭代与计算更新整个路由转发表。假设现在我们的路由器 A 运行 OSPF 协议:
- 第一次迭代完成后,它得到与 B、E 两台路由器相关的子网络的路径计算
- 第二次以 B 或者 E 为起点重新运行算法,这里我们假设以 B 为起点运行了算法,那么与 C 相关的子网络的路径也被更新进 A 的路由转发表
- 第三次以 C 为起点同样的运行算法,得到和 D 相关的子网络路径更新。由于 D 作为末端路由,并没有直接相连的其他路由,所以算法不再继续,回到 E
- 第四次,以 E 作为原点,运行算法,得到了 C 相关子网络的路径,如果有更短的路径,将更新 A 的转发表以最优路径
那么,待整个算法运行结束,一个自治系统中的所有路由器几乎全部遍历,但是却不同于 RIP,OSPF 相对而言收敛快,可以迅速完成任务,而 RIP 则需要不断的交换信息以达到需求,往往会陷入一个长周期
当然了,OSPF 需要较强的 CPU 计算能力和更多的内存存储空间
所以总的而言,他们都广泛应用于整个因特网之中,RIP 应用在较为底层的 ISP 上,而 OSPF 则运行在较为高级的 ISP 中
至此,整个网络层的基本内容也介绍完了,总结一下,网络层的核心任务就是负责转发分组,而如何将分组转发到目的主机的网络中牵扯出 IP 协议,通过 IP 地址与子网掩码划分子网络,而路由器执行路由选择算法得知目的子网络的完整路由路径并进行分发
链路层
网络层解决的是,分组转发的目的网络,也就是转发给目的网络的网关路由,而链路层解决的是,将分组广播给个人,也即目的主机
网络层的 IP 数据包会在链路层被封装成以太网帧,它的基本结构是这样的:
前导码用于同步时钟,按照我的理解就是区分一个一个的帧,源和目的地址指的是 Mac 地址,也称作物理地址
Mac 地址 是硬件级别的主机唯一标识,由生产厂家唯一确定。类似这样:
34-E6-AD-17-A5-6B
全球任意一台主机的 Mac 地址都是不同的,它不像 IP 地址可以在别人不用的时候共享
ARP 地址解析协议 完成了主机 IP 地址到 Mac 地址的转换
RP 协议其实有点类似于之前在应用层介绍的 DNS 协议,输入一个域名 地址,输出一个 IP 地址
而 ARP 而言,输入一个 IP 地址,输出一个 Mac 地址
网络中的每台主机,包括路由器,都内置的 ARP 模块和 ARP 表。当一份数据报到达链路层时,首先要做的就是以该数据报的目的 IP 作为输入,先查询自己主机的 ARP 模块,如果能够得到该 IP 的目的主机 Mac 地址,那么封装一个以太网帧交给物理层发送出去就好
而如果本机的 ARP 表中并没有存储目的 IP 主机的 Mac 地址,那么就需要向同网络中的其他主机进行查询
发送方会构建一个特殊的 ARP 分组,源 Mac 地址为发送方的 Mac 地址,目的 Mac 地址为广播地址:255.255.255.255,以及源和目的 IP 地址,本质上就是一个特殊的以太网帧
于是该网络下的所有主机都将收到这个 ARP 分组,那么他们要做的就是拆开 IP 地址比对是否和自己的 IP 地址相同,如果是则响应一个 ARP 分组,告诉发送方自己的 Mac 地址
如果不是自己,则还会检查自己的 ARP 模块,看看是否能提供帮助
最终,发送方会得到想要的目的 Mac 地址并更新自己的 ARP 表,然后封装一个正常的以太网帧发送出去
由于以太网采取的是广播方式,所以同一子网络中任意一台主机发送报文,所有的其余主机都会收到,但是它们会匹配目的 Mac 地址是否是自己,不是则丢弃,这一点很重要
物理层
0、1 的电信号传输
HTTP 请求报文
http 请求报文大致分:请求行、请求头部、请求数据
http 请求报文协议
字段解释:
(1)(2)(3)组成请求行
- (1)是请求方法,HTTP/1.1 定义的请求方法有8种: GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种 GET 和 POST,如果是 Rrestful 接口的话一般会用到 GET、POST、DELETE、PUT
- (2)请求对应的URL地址,它和请求头部的 Host 属性组成完整的请求 URL
- (3)协议名称及版本号
- (4)是 HTTP 的请求头部,请求头部包含若干个属性,格式为“属性名:属性值”,服务端据此获取客户端的信息
- (5)请求数据 一个页面表单中的组件值通过 param1=value1¶m2=value2 的键值对形式编码成一个格式化串,它承载多个请求参数的数据。不但报文体可以传递请求参数,请求URL也可以通过类似于 “/chapter15/user.html? param1=value1¶m2=value2” 的方式传递请求参数
请求方法:
方法 | 描述 |
---|---|
get | 安全,幂等,一般来说 get 方法只用于数据的读取,而不应当用于产生副作用的非幂等操作。这里的安全指请求不会影响到资源的状态 |
post | 非安全,非幂等,用于创建子资源。向指定资源提交数据进行处理请求(例如提交表单或者上传文件),数据被包含在请求体中,post 请求可能会导致新的资源的建立或已有资源的修改 |
put | 非安全,幂等,用于创建、更新资源。put 请求会向指定资源位置上传其最新内容,取代服务器的资源 |
patch | 非安全,幂等,用于创建、更新资源,和PUT类似,区别在于 patch 代表部分更新。当资源不存在时,patch 会创建一个新的资源,而 put 只会对已存在的资源进行更新 |
delete | 非安全、幂等,删除资源。用于请求服务器删除所请求 URI 所标识的资源 |
options | 安全、幂等,用于url验证,验证接口服务是否正常 |
trace | 安全、幂等。主要用于测试或诊断。回显服务器收到的请求,这样客户端可以看到(如果有)哪一些改变或者添加已经被中间服务器实现 |
head | 安全、幂等,与 get 方法类似,但不返回 message body 内容,仅仅是获得获取资源的部分信息(content-type、content-length),restful框架中较少使用 |
conntect | http/1.1 协议中预留给能够将连接改为管道方式的代理服务器 |
请求头部参数:
参数 | 解释 | 示例 |
---|---|---|
Accept | 浏览器告诉服务器,它支持的数据类型 | Accept: text/plain, text/html,application/json |
Accept-Charset | 浏览器可以接受的字符编码集 | ISO-8859-1(拉丁字母表的字符编码)、UTF-8(Unicode 字符编码) |
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型 | Accept-Encoding: compress, gzip |
Accept-Language | 浏览器可接受的语言 | Accept-Language: en-us、zh-cn |
Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges: bytes |
Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
Connection | 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) | Connection: close |
Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 | Cookie: $Version=1; Skin=new; |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Content-Type | 请求的与实体对应的MIME信息 | Content-Type: application/x-www-form-urlencoded |
Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 请求的特定的服务器行为 | Expect: 100-continue |
From | 发出请求的用户的Email | From: user@email.com |
Host | 指定请求的服务器的域名和端口号,告诉服务器,想访问哪台主机 | Host: localhost:8080 |
If-Match | 只有请求内容与实体相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag | If-Range: ‘737060cd8c284d8af7ad3082f209582d’ |
If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards: 10 |
Pragma | 用来包含实现特定的指令 | Pragma: no-cache |
Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Range | 只请求实体的一部分,指定范围 | Range: bytes=500-999 |
Referer | 浏览器告诉服务器,客户机是从哪个也面来的–反盗链 | Referer: http://www.zcmhi.com/archives/71.html |
TE | 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服务器指定某种传输协议以便服务器进行转换(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent | 告诉服务器,客户端使用的操作系统、浏览器版本和名称 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36 |
Via | 通知中间网关或代理服务器地址,通信协议 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 关于消息实体的警告信息 | Warn: 199 Miscellaneous warning |
HTTP 响应报文
http 响应报文大致分为:状态行、响应头部、响应数据
(1)(2)组成状态行
状态码用于表示服务器对请求的处理结果,是一个三位的十进制数
状态码 | 含义 |
---|---|
100 ~ 199 | 表示成功接收请求,要求客户端继续提交下一次请求才能完成整个处理过程 |
200 ~ 299 | 表示成功接收并已完成整个处理过程,常用200 |
300 ~ 399 | 重定向,为完成请求,客户需要进一步细化请求。例如,请求的资源已经移动一个新地址,常用302、304、307 |
400 ~ 499 | 客户端的请求有误,请求包含语法错误或无法完成请求,常用404 |
500 ~ 599 | 服务器错误,服务器在处理请求的过程中发生了错误,常用500 |
字段解释:
参数 | 解释 | 示例 |
---|---|---|
Accept-Ranges | 表明服务器是否支持指定范围请求及哪种类型的分段请求 | Accept-Ranges: bytes |
Age | 从原始服务器到代理缓存形成的估算时间(以秒计,非负) | Age: 12 |
Allow | 对某网络资源的有效的请求行为,不允许则返回405 | Allow: GET, HEAD |
Cache-Control | 告诉所有的缓存机制是否可以缓存及哪种类型 | Cache-Control: no-cache |
Content-Encoding | web服务器支持的返回内容压缩编码类型 | Content-Encoding: gzip |
Content-Language | 响应体的语言 | Content-Language: en,zh |
Content-Length | 响应体的长度 | Content-Length: 348 |
Content-Location | 请求资源可替代的备用的另一地址 | Content-Location: /index.htm |
Content-MD5 | 返回资源的MD5校验值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
Content-Range | 在整个返回体中本部分的字节位置 | Content-Range: bytes 21010-47021/47022 |
Content-Type | 返回内容的MIME类型 | Content-Type: text/html; charset=utf-8 |
Date | 原始服务器消息发出的时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
ETag | 请求变量的实体标签的当前值 | ETag: “737060cd8c284d8af7ad3082f209582d” |
Expires | 响应过期的日期和时间 | Expires: Thu, 01 Dec 2010 16:00:00 GMT |
Last-Modified | 请求资源的最后修改时间 | Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT |
Location | 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 | Location: http://www.zcmhi.com/archives/94.html |
Pragma | 包括实现特定的指令,它可应用到响应链上的任何接收方 | Pragma: no-cache |
Proxy-Authenticate | 它指出认证方案和可应用到代理的该URL上的参数 | Proxy-Authenticate: Basic |
refresh | 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) | Refresh: 5; url=http://www.zcmhi.com/archives/94.html |
Retry-After | 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 | Retry-After: 120 |
Server | web服务器软件名称 | Server: Apache/1.3.27 (Unix) (Red-Hat/Linux) |
Set-Cookie | 设置Http Cookie | Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 |
Trailer | 指出头域在分块传输编码的尾部存在 | Trailer: Max-Forwards |
Transfer-Encoding | 文件传输编码 | Transfer-Encoding:chunked |
Vary | 告诉下游代理是使用缓存响应还是从原始服务器请求 | Vary: * |
Via | 告知代理客户端响应是通过哪里发送的 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 警告实体可能存在的问题 | Warning: 199 Miscellaneous warning |
WWW-Authenticate | 表明客户端请求实体应该使用的授权方案 | WWW-Authenticate: Basic |
HTTP 之长短连接(TCP连接)
在 HTTP/1.0 版本的时候,客户端与服务器完成一个 请求/响应 之后,会将之前建立的 TCP 连接断开,下次请求的时候又要重新建立 TCP 连接,即每次发送数据都会经过 TCP 的三次握手和四次挥手,效率比较低,这也被称为短连接
在 HTTP1.0 发布仅半年后(1997年1月) ,HTTP/1.1 版本发布并带来一个新的功能:在客户端与服务器完成一次请求/响应之后,允许不断开 TCP 连接,这意味着下次请求就直接使用这个 TCP 连接而不再需要重新握手建立新连接,这也被称为长连接
长连接是指一次 TCP 连接允许多次 HTTP 会话,HTTP 永远都是一次请求/响应,会话结束,HTTP本身不存在长连接之说
建立长连接有优点也有缺点:
优点: 当网站中有大量静态资源(图片、css、js等)就可以开启长连接,这也几张图片就可以通过一次TCP连接发送
缺点: 当客户端请求一次时候不在请求,而服务器却开着长连接资源被占用着,这是严重浪费资源
在一次客户端 HTTP 完整的请求中(DNS寻址、建立TCP连接、请求、等待、解析网页、断开 TCP 连接)建立TCP 连接占用的时间比还是很大的
HTTP 之无状态(Cookie、Session)
无状态协议( Stateless Protocol ) 是指浏览器对于事务的处理没有记忆能力。举个例子来说就是比如客户请求获得网页之后关闭浏览器,然后再次启动浏览器,登录该网站,但是服务器并不知道客户关闭了一次浏览器
HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。可能大多数用户不相信,他可能觉得每次输入用户名和密码登陆一个网站后,下次登陆就不再重新输入用户名和密码了。这其实不是 HTTP 做的事情,起作用的是一个叫做 小甜饼(Cookie) 的机制。它能够让浏览器具有记忆能力
当向服务端发送请求时,服务端会给客户端发送一个认证信息。服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX 命令,向客户端发送要求设置 Cookie 的响应;客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX 的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束
接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie 信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。这样,你的浏览器才具有了记忆能力
还有一种方式是使用 JWT 机制,它也是能够让你的浏览器具有记忆能力的一种机制。与 Cookie 不同,JWT 是保存在客户端的信息,它广泛的应用于单点登录的情况。JWT 具有两个特点:
- JWT 的 Cookie 信息存储在客户端,而不是服务端内存中。也就是说,JWT 直接本地进行验证就可以,验证完毕后,这个 Token 就会在 Session 中随请求一起发送到服务器,通过这种方式,可以节省服务器资源,并且 token 可以进行多次验证
- JWT 支持跨域认证,Cookies 只能用在单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是我们常说的跨域认证
Cookie:
指的就是浏览器里面能永久存储的一种数据,仅仅是浏览器实现的一种数据存储功能
cookie 由服务器生成,发送给浏览器,浏览器把 cookie 以 kv 形式保存到某个目录下的文本文件内(客户端),下一次请求同一网站时会把该 cookie 发送给服务器
由于 cookie 是存在客户端上的,所以浏览器加入了一些限制确保 cookie 不会被恶意使用,同时不会占据太多磁盘空间,所以每个域的 cookie 数量是有限的
Session:
代表着服务器和客户端一次会话的过程。类似的道理,服务器要知道当前发请求给自己的是谁,Session 也是一种记录客户状态的机制,不同的是保存在服务器上
为了做这种区分,服务器就要给每个客户端分配不同的“身份标识”,然后客户端每次向服务器发请求的时候,都带上这个“身份标识”,服务器就知道这个请求来自于谁了
至于客户端怎么保存这个“身份标识”,可以有很多种方式,对于浏览器客户端,大家都默认采用 cookie 的方式。服务器使用 session 把用户的信息临时保存在了服务器上,当客户端关闭会话,或者 Session 超时失效时会话结束
Session 对象的创建是在用户第一次访问服务器时产生的,每一个访问者都对应一个 Session 对象(只有访问 JSP、Servlet 时才会创建对象,HTML、IMAGE 等静态资源不会创建对象)
这种用户信息存储方式相对 cookie 来说更安全,可是session有一个缺陷:如果 web 服务器做了负载均衡,那么下一个操作请求到了另一台服务器的时候 session 会丢失
Cookie 和 Session 有什么不同
- 作用范围不同,Cookie 保存在客户端(浏览器),Session 保存在服务器端
- 存取方式的不同,Cookie 只能保存 ASCII,Session 可以存任意数据类型,一般情况下我们可以在 Session 中保持一些常用变量信息,比如说 UserId 等
- 有效期不同,Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭或者 Session 超时都会失效
- 隐私策略不同,Cookie 存储在客户端,比较容易遭到不法获取,早期有人将用户的登录名和密码存储在 Cookie 中导致信息被窃取;Session 存储在服务端,安全性相对 Cookie 要好一些
- 存储大小不同, 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie
禁止 Cookie 怎么运转
如果浏览器中禁止了 Cookie
- 每次请求中都携带一个 SessionID 的参数,也可以 Post 的方式提交,也可以在请求的地址后面拼接 xxx?SessionID=123456
- Token 机制 Token 机制多用于 App 客户端和服务器交互的模式,也可以用于 Web 端做用户状态管理。Token 的意思是“令牌”,是服务端生成的一串字符串,作为客户端进行请求的一个标识。Token 机制和 Cookie 和 Session 的使用机制比较类似。当用户第一次登录后,服务器根据提交的用户信息生成一个 Token,响应时将 Token 返回给客户端,以后客户端只需带上这个 Token 前来请求数据即可,无需再次登录验证
分布式 Session 问题
互联网公司为了可以支撑更大的流量,后端往往需要多台服务器共同来支撑前端用户请求,那如果用户在 A 服务器登录了,第二次请求跑到服务 B 就会出现登录失效问题。解决方案如下:
- Nginx ip_hash 策略,服务端使用 Nginx 代理,每个请求按访问 IP 的 hash 分配,这样来自同一 IP 固定访问一个后台服务器,避免了在服务器 A 创建 Session,第二次分发到服务器 B 的现象
- Session 复制,任何一个服务器上的 Session 发生改变(增删改),该节点会把这个 Session 的所有内容序列化,然后广播给所有其它节点
- 共享 Session,服务端无状态话,将用户的 Session 等信息使用缓存中间件(比如 Redis)来统一管理,保障分发到每一个服务器的响应结果都一致(推荐)
TCP 三次握手
- 为什么连接的时候是三次握手?
- 什么是半连接队列?
- ISN(Initial Sequence Number 初始化序列号)是固定的吗?
- 三次握手过程中可以携带数据吗?
- 如果第三次握手丢失了,客户端服务端会如何处理?
- SYN攻击是什么?
- 挥手为什么需要四次?
- 四次挥手释放连接时,等待2MSL的意义?
三次握手:
三次握手( Three-way Handshake )其实就是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包
进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息
刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态
第一次握手: 客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN(Initial Sequence Number 初始化序列号)。此时客户端处于 SYN_SEND 状态
首部的同步位 SYN=1,初始序号 seq=x,SYN=1 的报文段不能携带数据,但要消耗掉一个序号
第二次握手: 服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN 。同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态
在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y
第三次握手: 客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接
确认报文段 ACK=1,确认号 ack=y+1,序号 seq=x+1(初始为 seq=x,第二个报文段所以要 +1),ACK 报文段可以携带数据,不携带数据则不消耗序号
发送第一个SYN的一端将执行主动打开( active open ),接收这个 SYN 并发回下一个 SYN 的另一端执行被动打开( passive open )
在socket编程中,客户端执行 connect() 时,将触发三次握手
为什么需要三次握手,两次不行吗
弄清这个问题,需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的
第一次握手: 客户端发送网络包,服务端收到了
这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的
第二次握手: 服务端发包,客户端收到了
这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常
第三次握手: 客户端发包,服务端收到了
这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常
因此,需要三次握手才能确认双方的接收与发送能力是否正常
粗略点讲:三次握手为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误
假设两次握手:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求
后来收到了服务端的确认,建立了连接。数据传输完毕后,就释放了连接
客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接
不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源
什么是半连接队列
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,把这种队列称之为半连接队列
还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象
补充一点关于SYN-ACK 重传次数的问题:
服务器发送完SYN-ACK包,如果未收到客户确认包(第三次握手),务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除
每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…
ISN(Initial Sequence Number 初始化序列号)是固定的吗
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释
三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的
三次握手过程中可以携带数据吗
其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据
假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以再携带数据是说的过去的
SYN攻击是什么
服务器端的资源分配是在二次握手时分配的
客户端的资源是在完成三次握手时分配的
所以服务器容易受到 SYN 洪泛攻击。SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DoS/DDoS 攻击
检测 SYN 攻击非常的方便,当在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击
netstat -n -p TCP | grep SYN_RECV
常见的防御 SYN 攻击的方法有如下几种:
- 缩短超时(SYN Timeout)时间
- 增加最大半连接数
- 过滤网关防护
- SYN cookies技术
TCP 四次挥手
建立一个连接需要三次握手,而终止一个连接要经过四次挥手
这由 TCP 的半关闭( half-close )造成的。所谓的半关闭,其实就是 TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力
TCP 的连接拆除需要发送四个包,因此称为四次挥手(Four-way handshake),客户端或服务器均可主动发起挥手动作
刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:
第一次挥手: 客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态
即发出连接释放报文段( FIN=1,序号 seq=u ),并停止再发送数据,主动关闭TCP连接,进入 FIN_WAIT1(终止等待1)状态,等待服务端的确认
第二次挥手: 服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态
即服务端收到连接释放报文段后即发出确认报文段( ACK=1,确认号 ack=u+1,序号 seq=v),服务端进入 CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入 FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段
第三次挥手: 如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态
即服务端没有要向客户端发出的数据,服务端发出连接释放报文段( FIN=1,ACK=1,序号 seq=w,确认号ack=u+1 ),服务端进入 LAST_ACK(最后确认)状态,等待客户端的确认
第四次挥手: 客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态
即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),客户端进入 TIME_WAIT(时间等待)状态。此时 TCP 未释放掉,需要经过时间等待计时器设置的时间 2MSL 后,客户端才进入CLOSED状态
收到一个 FIN 只意味着在这一方向上没有数据流动。客户端执行主动关闭并进入 TIME_WAIT 是正常的,服务端通常执行被动关闭,不会进入 TIME_WAIT 状态
在socket编程中,任何一方执行close()操作即可产生挥手操作
挥手为什么需要四次
因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文
其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当服务端收到 FIN 报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端,“你发的FIN报文我收到了”
只有等到我服务端所有的报文都发送完了,我才能发送 FIN 报文,因此不能一起发送。故需要四次挥手
2MSL等待状态
TIME_WAIT 状态也称为 2MSL 等待状态。每个具体 TCP 实现必须选择一个报文段最大生存时间 MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为 TCP 报文段以 IP 数据报在网络内传输,而 IP 数据报则有限制其生存时间的 TTL 字段。
对一个具体实现所给定的 MSL 值,处理的原则是:当 TCP 执行一个主动关闭,并发回最后一个 ACK,该连接必须在 TIME_WAIT 状态停留的时间为 2倍的 MSL。这样可让 TCP 再次发送最后的 ACK 以防这个 ACK 丢失(另一端超时并重发最后的 FIN)
这种 2MSL 等待的另一个结果是这个 TCP 连接在 2MSL 等待期间,定义这个连接的插口(客户的 IP 地址和端口号,服务器的 IP 地址和端口号)不能再被使用。这个连接只能在 2MSL 结束后才能再被使用
四次挥手释放连接时,等待2MSL的意义
MSL( Maximum Segment Lifetime )最长报文段寿命,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃
为了保证客户端发送的最后一个 ACK 报文段能够到达服务器。因为这个 ACK 有可能丢失,从而导致处在LAST-ACK 状态的服务器收不到对 FIN-ACK 的确认报文。服务器会超时重传这个 FIN-ACK ,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态
两个理由:
- 保证客户端发送的最后一个 ACK 报文段能够到达服务端
这个 ACK 报文段有可能丢失,使得处于 LAST-ACK 状态的 服务端 收不到对已发送的 FIN+ACK 报文段的确认,服务端超时重传 FIN+ACK 报文段,而客户端能在 2MSL 时间内收到这个重传的 FIN+ACK 报文段,接着客户端重传一次确认,重新启动 2MSL 计时器,最后客户端和服务端都进入到 CLOSED 状态,若客户端在 TIME-WAIT 状态不等待一段时间,而是发送完 ACK 报文段后立即释放连接,则无法收到服务端重传的FIN+ACK 报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态
- 防止“已失效的连接请求报文段”出现在本连接中
客户端在发送完最后一个 ACK 报文段后,再经过 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段
为什么TIME_WAIT状态需要经过2MSL才能返回到CLOSE状态
理论上,四个报文都发送完毕,就可以直接进入 CLOSE 状态了,但是可能网络是不可靠的,有可能最后一个 ACK 丢失。所以 TIME_WAIT 状态就是用来重发可能丢失的 ACK 报文
TCP 可靠传输
TCP 使用超时重传来实现可靠传输:如果一个已经发送的报文段在超时时间内没有收到确认,那么就重传这个报文段。
一个报文段从发送再到接收到确认所经过的时间称为往返时间 RTT,加权平均往返时间 RTTs 计算如下:
其中,0 ≤ a < 1,RTTs 随着 a 的增加更容易受到 RTT 的影响。超时时间 RTO 应该略大于 RTTs,TCP 使用的超时时间计算如下:
其中 RTTd 为偏差的加权平均值
TCP 滑动窗口
窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小
发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口
接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收
TCP 流量控制
流量控制是为了控制发送方发送速率,保证接收方来得及接收
接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据
实际上,为了避免此问题的产生,发送端主机会时不时的发送一个叫做窗口探测的数据段,此数据段仅包含一个字节来获取最新的窗口大小信息
TCP 拥塞控制
如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度
TCP 主要通过四个算法来进行拥塞控制:
慢开始、拥塞避免、快重传、快恢复
发送方需要维护一个叫做拥塞窗口(cwnd)的状态变量,注意拥塞窗口与发送方窗口的区别:拥塞窗口只是一个状态变量,实际决定发送方能发送多少数据的是发送方窗口。
为了便于讨论,做如下假设:
- 接收方有足够大的接收缓存,因此不会发生流量控制;
- 虽然 TCP 的窗口基于字节,但是这里设窗口的大小单位为报文段
慢开始与拥塞避免
发送的最初执行慢开始,令 cwnd = 1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …
注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能性也就更高。设置一个慢开始门限 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。
如果出现了超时,则令 ssthresh = cwnd / 2,然后重新执行慢开始。
快重传与快恢复
在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。
在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。
在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd / 2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。
慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh
UDP 和 TCP
TCP 和 UDP 都位于计算机网络模型中的运输层,它们负责传输应用层产生的数据
UDP 的全称是 User Datagram Protocol,用户数据报协议。它不需要所谓的握手操作,从而加快了通信速度,允许网络上的其他主机在接收方同意通信之前进行数据传输
是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信
UDP 的主要特点有:
- UDP 能够支持容忍数据包丢失的带宽密集型应用程序
- UDP 具有低延迟的特点
- UDP 能够发送大量的数据包
- UDP 能够允许 DNS 查找,DNS 是建立在 UDP 之上的应用层协议
TCP 全称是Transmission Control Protocol ,传输控制协议。它能够帮助你确定计算机连接到 Internet 以及它们之间的数据传输。通过三次握手来建立 TCP 连接,三次握手就是用来启动和确认 TCP 连接的过程。一旦连接建立后,就可以发送数据了,当数据传输完成后,会通过关闭虚拟电路来断开连接
是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)
TCP 的主要特点有:
- TCP 能够确保连接的建立和数据包的发送
- TCP 支持错误重传机制
- TCP 支持拥塞控制,能够在网络拥堵的情况下延迟发送
- TCP 能够提供错误校验和,甄别有害的数据包
UDP 与 TCP 的不同点
TCP | UDP |
---|---|
面向连接的协议 | 无连接的协议 |
在发送数据前先需要建立连接,然后再发送数据 | 无需建立连接就可以直接发送大量数据 |
会按照特定顺序重新排列数据包 | 数据包没有固定顺序,所有数据包都相互独立 |
传输的速度比较慢 | 传输会很快 |
头部字节有20个字节 | 头部字节只需要8个字节 |
是重量级的,在发送任何用户数据之前,TCP需要三次握手建立连接 | 轻量级的,没有跟踪连接、消息排序等 |
会进行错误校验,并能够进行错误恢复 | 也会错误检查,但会丢弃错误的数据包 |
有发送确认 | 没有发送确认 |
是可靠的,因为它可以确保将数据传送到路由器 | 不能保证将数据传送到目标 |
UDP、TCP 首部格式
UDP首部格式
UDP 首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。12 字节的伪首部是为了计算检验和临时添加的
TCP首部格式
TCP 首部格式比较复杂
序号: 用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401
确认号: 期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701
数据偏移: 指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度
控制位: 八位从左到右分别是 CWR,ECE,URG,ACK,PSH,RST,SYN,FIN
CWR: CWR 标志与后面的 ECE 标志都用于 IP 首部的 ECN 字段,ECE 标志为 1 时,则通知对方已将拥塞窗口缩小
ECE: 若其值为 1 则会通知对方,从对方到这边的网络有阻塞。在收到数据包的 IP 首部中 ECN 为 1 时将 TCP 首部中的 ECE 设为 1;
URG: 该位设为 1,表示包中有需要紧急处理的数据,对于需要紧急处理的数据,与后面的紧急指针有关;
ACK: 该位设为 1,确认应答的字段有效,TCP规定除了最初建立连接时的 SYN 包之外该位必须设为 1;
PSH: 该位设为 1,表示需要将收到的数据立刻传给上层应用协议,若设为 0,则先将数据进行缓存;
RST: 该位设为 1,表示 TCP 连接出现异常必须强制断开连接;
SYN: 用于建立连接,该位设为 1,表示希望建立连接,并在其序列号的字段进行序列号初值设定;
FIN: 该位设为 1,表示今后不再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位置为 1 的 TCP 段
每个主机又对对方的 FIN 包进行确认应答之后可以断开连接。不过,主机收到 FIN 设置为 1 的 TCP 段之后不必马上回复一个 FIN 包,而是可以等到缓冲区中的所有数据都因为已成功发送而被自动删除之后再发 FIN 包 (详细内容参考 TCP 四次挥手)
窗口: 窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的
一次完整的 HTTP 请求响应过程(网络分层解析)
总结: 解析URL、DNS域名解析、浏览器与网站建立 TCP 连接、请求和数据传输、浏览器渲染页面
详细分析:
以输入 www.google.com
为例
首先应用层的浏览器决定向 DNS 服务器请求解析域名 www.google.com
,那么就要遵循 DNS 协议
DNS 运行在 53 号端口,于是浏览器会创建一个 UDP 套接字,标识该套接字的二元组分别是目的 IP 地址 和 目的端口。而套接字本质上就是为了唯一标识应用层进程,就是为了让响应报文能够找到目的地
那么这里会创建一个 UDP 套接字,二元组为 本机 IP 地址 192.168.43.138
和随机产生一个未使用的端口号
接着,浏览器将 DNS 请求报文封装好推入套接字,开始 DNS 解析过程
最终会从 本地 DNS 服务器 得到 Google 的 IP 地址为:172.194.72.105
应用层
浏览器封装 HTTP 请求报文,然后创建一个 TCP 套接字,采用四元组标识,具体为源 IP 地址:192.168.43.138 + 源端口号:随机的,这里假设为 1234 + 目的 IP 地址:172.194.72.105 + 目的端口号:80
接着,这个报文会被推进 TCP 套接字中,等待运输层来收取
运输层
运输层收取了报文,并判断与目的主机是否建立了 TCP 连接,这里假设没有
那么,运输层将不急着发送应用层数据,得先判断与目的主机之间能够正常通讯,也就是需要三次握手打招呼
通过三次握手,发送端和接收端确认过发送与确认序号,分配了相应的缓存资源等。
一切准备就绪之后,运输层将应用层发过来的数据报又一层封装,添加进 源端口号 和 目的端口号 以及相关差错检验字段
最后将 TCP 数据报向下传递到网络层
网络层
网络层其实很简单,拿到数据报并封装成 IP 数据报,即在原 TCP 报文的前提之上添加 源 IP 地址 和目的 IP 地址 等字段信息
然后交由数据链路层
链路层
数据链路层拿到 IP 数据报,它需要封装成以太网帧才能在网络中传输,也就是它需要目的主机的 Mac 地址,然而我们只知道目的主机的 IP 地址
所以,链路层有一个 ARP 协议,直接或间接的能够根据目的 IP 地址获得使用该 IP 地址的主机 Mac 地址
当然,ARP 协议运行的前提是,目的 IP 地址和当前发送方主机处于同一子网络中。如果不然,发送方将目的 Mac 地址填自己网关路由的 Mac 地址,然后通过物理层发送出去
网关路由由于具有转发表和路由选择算法,所以它知道目的网络该怎么到达,所以一路转发,最终会发送到目的网络的网关路由上
最后,目的网络的网关路由同样会经由 ARP 协议,取得目的主机的 Mac 地址,然后广播发送,最后被目的主机接受
这样谷歌的服务器就接受到一个 HTTP 请求,于是它解析这个请求,确定该请求的动作是什么,也就是它需要什么东西,并构建响应报文,以同样的方式从网络到达源主机