一、简介
首先 Http 是互联网上的一个协议
超文本传输协议(英语:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议[1]。HTTP是万维网的数据通信的基础。
设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。
Http 协议的特点
- 支持客户端/服务端(C/S)模式
- 简单快速:客户端向服务端请求服务的时候,只需要传递请求的方法、路径,常见的请求方法有 GET、POST,每种请求方法规定了客户与服务器通信的类型。由于 Http 协议简单,使得 Http 服务的程序规模很小,因而通信的速度很快
- 灵活:Http 允许传输任意类型的数据对象,由 Content-Type 请求头标记
- 无连接:每次处理完一次http通信后,都会断开 TCP 链接,(http1.0 版本已经解决该问题,使用 Keep-Alive 保持持久连接)
- 无状态:每次请求都和其他请求不相关,是无记忆的,如果第二次请求需要第一次请求传入的数据,第二次请求仍然需要重新传入,在一定程度上造成了浪费。(Cookie、Session、Token 等解决)
二、URI 和 URL
前面的引用里面讲 HTTP 或者 HTTPS 协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识,其实除了 URI ,我们经常见到的还有 URL,这两者有什么区别呢?
看下定义:
- URI:Uniform Resource Identifier 统一资源标识符
- URL:Uniform Resource Location 统一资源定位符
URI 是用来标示 一个具体的资源的,我们可以通过 URI 知道一个资源是什么。
URL 则是用来定位具体的资源的,标示了一个具体的资源。
他们两个是包含关系的,URL 属于 URI ,关系如下:
参考链接
URI 和 URL 都要遵循的格式如下
scheme:[//authority][/path][?query][#fragment]
- scheme 对于 URL,是访问资源的协议名称,对于 URI ,是分配标识符的规范的名称
- authority 用户授权信息部分(可选)
- path 用于在 scheme 和 authority 内标识资源路径
- query 和 path 一起用来标识资源,对于 url 是查询字符串
- fragment 资源特定部分的标识符
URI
摘自维基百科:
URL
URL 的语法格式:(带[] 为可选项)
protocol :// hostname[:port] / path / [?query]#fragment
protocol 协议
指定使用的传输协议,最经常见到的是 HTTP 协议,其他的协议还有 https、file、ftp等
hostname 主机名
指的是存放资源的域名或者是 IP 地址,有时主机名前也可以包含连接到服务器的用户名和密码,格式(username:password@hostname)
port 端口号(可选)
端口号是可选的,如果不填写的话,http 访问默认使用 80 端口,https 默认使用 443 端口
path 路径
路径代表的是一个服务器上目录或者是具体文件的地址,由零个或者多个 / 隔开。
query 查询(可选)
query 代表是在当前路径下的查找的限定符,前面是由 ?开头,多个参数的时候用 & 符号隔开,采用 key-value 的形式,key 和 value 用= 隔开,格式为 ?key = value & key1 = value1 。
fragment 片段(可选)
代表的是资源中的某一个片段。可以指定 fragment 直接定位到具体的位置
平时的 Http 请求中,一般使用的是 URL 来进行资源访问的,比如我们常见的 GET 请求, Android 官方文档的 GET 请求为例:
https://developer.android.com/about/versions/pie#androidnbsp9
// https 的默认端口是 443 端口,如果是 http 请求则是默认 80 端口
https://developer.android.com:443/about/versions/pie#androidnbsp9
类型 | 对应 |
---|---|
protocol | https |
hostname | developer.android.com |
port | 443 |
path | about/versions/pie |
query | 这里未体现 |
fragment | androidnbsp9 |
具体的可以去维基百科看看
三、网络分层体系
经常听说的网络模型有 OSI七层网络模型 和 TCP/IP四层网络模型两种,分别如下:
TCP/IP 模型是借鉴了OSI 模型的一些概念而建立起来的,OSI 模型其实是一种理论下的模型,而 TCP/IP 协议现在已经成为互联网的标准。
TCP/IP 协议从字面意思来看是,并不单纯的指传输层的 TCP 协议和网路层的 IP 协议,其实指的是进行 IP 通信过程中所用到的协议簇的统称,只是因为 TCP 协议和 IP 协议是 TCP/IP 协议中很重要的协议,所以就称之为 TCP/IP 协议了。
网络层的 IP/ICMP 协议、传输层的 TCP/UDP 协议、传输层的 HTTP 协议等都属于 TCP/IP 协议,这些协议与 TCP/IP 紧密联系,互相协作,一起完成完整的 HTTP 通信,也称为 TCP/IP 网际协议群。
互联网进行通信时,需要相应的网络协议,TCP/IP 原本就是为使用互联网而开发制定的协议族。因此,互联网的协议就是 TCP/IP,TCP/IP 就是互联网的协议。
先看下 TCP/IP 协议的网络数据传输图:
客户端要发送的数据,需要经过层层协议进行包装,然后通过电信号进行传输,然后服务端在经过协议进行层层解开包装获取客户端传入的数据进行处理。
这种分层的好处是很大的:
- 各层之间是独立的。某一层并不需要知道它的下一层是如何实现的,而仅仅需要知道该层通过层间的接口所提供的服务。这样,整个问题的复杂程度就下降了。也就是说上一层的工作如何进行并不影响下一层的工作,这样我们在进行每一层的工作设计时只要保证接口不变可以随意调整层内的工作方式。
- 灵活性好。当任何一层发生变化时,只要层间接口关系保持不变,则在这层以上或以下各层均不受影响。当某一层出现技术革新或者某一层在工作中出现问题时不会连累到其他层的工作,排除问题时也只需要考虑这一层单独的问题即可。
- 结构上可分割开。各层都可以采用最合适的技术来实现。技术的发展往往是不对称的,层次化的划分有效避免了木桶效应,不会因为某一方面技术的不完善而影响整体的工作效率。
- 易于实现和维护。这种结构使得实现和调试一个庞大又复杂的系统变得易于处理,因为整个的系统已被分解为若干个相对独立的子系统。进行调试和维护时,可以对每一层进行单独的调试,避免了出现找不到问题、解决错问题的情况。
- 能促进标准化工作。因为每一层的功能及其所提供的服务都已有了精确的说明。标准化的好处就是可以随意替换其中的某几层,对于使用和科研来说十分方便。
3.1 应用层
应用层作为 TCP/IP 协议的最高层,直接面向用户,是我们平时开发过程中遇到的最多的。但是网络中传输的数据是字节流,不能够被程序很好的识别,所以需要在应用层定义协议,规范数据格式,供用户去访问,常见的应用层的协议有:HTTP、FTP、SMTP 等。我们最经常使用的就是 HTTP 和 HTTPS 协议了,HTTPS 协议是在 HTTP 协议的基础上对数据进行了加密处理,下面章节再讲 HTTP 和 HTTPS 协议。
3.2 传输层
传输层顾名思义就是负责数据的传输的,在数据经过这一层的时候,会添加传输层的协议头,然后交给下一层网络层去处理,常见的传输层的协议有 TCP 协议、UDP 协议,对比如下:
可靠性
TCP 是需要先建立连接以后才可以开始传输的,在传输过程中是用流量控制、拥塞控制等保证数据的正确传输。TCP 为了保证可靠传输,给每个包一个序号,同时需要也保证了传送到接收端实体包的有序接收,然后接收端实体对已经成功接收到的字节发回一个相应的确认(ACK),如果发送端实体在合理的时间内未收到回复 ACK,那么就会认为数据丢失,然后重新传送数据。
UDP 则是不需要建立连接的,没有使用流量控制、拥塞控制,只管自己发送数据,不能保证数据能够被正确的接收
连接性
建立一次 TCP 连接,需要进行三次握手,握手之后才开始数据传输,断开连接的时候需要经历四次挥手。
UDP 则不需要这些过程,直接发送数据。
报文
TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;
UDP是面向报文的。
连接对象个数
TCP 只要建立连接以后才能传输数据,只能连接两个端点,采用端对端方式传播
UDP 则可以连接多个端点,支持1对1,1对多,多对1,多对多通信
拥塞控制
当网络拥塞的时候,
TCP 能减少向网络中注入数据速率和数量,缓解网络拥塞
UDP 则不会进行控制,一直发送数据。
3.3 网络层
上面图中的数据,在经过网络层的时候,会加上 IP 首部,IP 首部里面就包含了源 IP 地址和目标 IP 地址,网络层就是负责将数据从源 IP 地址 传输到目标 IP 地址,是点对点的通信。
IP 首部如下:
网络层设计的东西还是比较多的,这里暂时不写了,以后有空再填坑。
3.4 链路层
链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的(MAC,物理地址,一个主机会有一个MAC地址)。
四、HTTP 报文
前面讲了分层模型,这里讲下 HTTP 的报文格式:
HTTP 报文又分为两部分:
4.1 请求报文
请求报文由 请求行(request line)、请求头部(header)、空行和请求体4个部分组成
来看一个具体的请求报文:
1. 请求行
请求行分为三个部分:请求方法、请求地址和协议版本
- 请求方法
HTTP/1.1 定义的请求方法有8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。
最常的两种GET和POST,如果是RESTful接口的话一般会用到GET、POST、DELETE、PUT。 - 请求地址
URL:统一资源定位符,是一种自愿位置的抽象唯一识别方法。可参考上面介绍的 URL - 协议版本
协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1,目前大多数还是使用的 HTTP1.1 版本
2.请求头部
请求头部为请求报文添加了一些附加信息,由“key/value”对组成,每行一对,key和value之间使用冒号分隔;
常见的请求头:
3.请求体
请求数据也就是我们常说的请求体,包含了我们发送给服务器的数据,如果是 GET 请求,path 后面的内容会出现在这里,如果是 POST 请求,请求体题 BODY 里面的内容会出现在这里
一个实际的请求实例
4.2 响应报文
响应报文由状态行、响应头部、空行以及响应数据四部分组成
1. 状态行
由3部分组成,分别为:协议版本,状态码,状态码描述
协议版本就是 HTTP 的协议版本,
状态码是定义的 code ,规定了服务器返回的状态,状态码描述是对状态码的解读。常见的状态码和状态码描述如下:
2. 响应头部
这个与请求头部类似,为响应的报文添加了一些信息。
3. 响应体
用于存放需要返回给客户端的数据信息,通常我们开发中请求接口最关心的就是这里的数据。
一个实际的请求实例
五、TCP/UDP
TCP 报文
TCP 虽然是面向字节流的,但是传送的数据单元是报文段,一个 TCP 报文段分为首部和数据两部分,TCP 的全部功能体现在各首部中的个字段的作用。
TCP 报文段的首部的 20 个字节是固定的,后面的 4n(每行四个字节,n 为整数)是根据需要而增加的选项,所以最小情况是没数据,只有 20 个字节。
TCP 报文头部除了包含源端口和目标端口外,还定义了一些控制位,
控制位 | 含义 |
---|---|
URG: | 紧急数据(urgent data)—这是一条紧急信息 |
ACK: | 确认已收到 |
PSH: | 当两个应用进程交互时,一段键入一个命令就能立即得到对方的响应 |
RST: | 当 RST =1 时,表明 TCP 休闲严重差错,需要重新建立连接 |
SYN: | 当链接建立时用来同步序号 |
FIN: | 用来释放一个连接 |
重点看下和 TCP 三次握手和四次挥手相关的控制位:
- ACK(ACKnowledgment):仅当 ACK=1 时,确认号字段才有效,当 ACk=0 时,确认号无效,TCP 规定,在连接建立以后所有传送的报文段都必须把 ACK 置为 1;
- SYN(SYNchronization) 在简历连接时用来同步序号,当 SYN = 1 而 ACK =0 时,表明是一个连接请求报文段,若对方同意建立连接,则应在响应的报文段中使 SYN=1和 ACK=1,因此 SYN 置为 1 就表示这是一个连接请求或连接接受报文。
- FIN(FINis):用来释放一个连接,当 FIN = 1 时,表示此报文段的发送方的数据已经发送完毕,并要求断开连接。
TCP 三次握手
三次握手指的是 TCP 连接建立的过程,握手需要在客户端和服务端交换三个 TCP 报文段,三次握手过程如下图所示:
图中的 A 是客户端程序,B 是服务端程序,最初的时候连接未建立,两端的 TCP 进程都处于 CLOSED(关闭) 状态,然后进行了 A 主动打开连接,B 被动打开连接。
第一次握手
A 创建本地的传输控制模块 TCB,然后创建一个 TCP 报文段,报文段中:
SYN = 1 同步控制位(=1 时候不能携带数据)
seq = x;序号,供 B 确认
由于此时 SYN 为 1,不能给携带数据,但是要消耗掉一个序号 seq,发送完以后客户端进入 SYN-SENT(同步已发送) 状态
第二次握手
B 在接收到 A 传来的建立连接请求以后,如果是同意建立连接,则会给 A 响应以确认自己同意建立连接,报文段如下:
SYN = 1 同步控制位(=1 时候不能携带数据)
ACK = 1 确认控制位
ack = x + 1 在 A 传来的序号 x 基础上+1,以确认
seq = y 创建自己的 序号 y,供 A 去确认
这一步主要是添加了 ACK =1 ,表示确认有效,然后再把 A 传来的 x 值加一,以供 A 去确认,再创建一个自己的序号 y。最终把 TCP 报文传递给 A,B 进入 SYN-RCVD(同步收到) 状态。
ps:这一步可以把这一个报文段分为两个去发送,一个 ACK=1,ack = x+1;一个是 SYN=1,seq = y,这样的话,就变成了四次握手,不过最终效果是一样的。白白增加了一步操作。
第三次握手
A 在接收到 B 传来的 TCP 报文以后,还需要再次向 B 确认,
ACK = 1
ack = y + 1
seq = x + 1
此时把 ACK = 1表示确认,然后把 B 传来的 seq +1 作为确认序号 ack ,消耗自己的序号数 seq = x+1,这个时候没有 SYN 控制位,此 TCP 是可以携带数据,但是会消耗自己的序号,如果不携带数据的话,不会消耗自己的序号,等到下次数据传输的时候seq 还是 x+1,这个时候 TCP 连接已经建立,A进入ESTAB-LISHED(已建立连接)状态。
当 B 收到 A 传来的 TCP 报文的时候,也进入ESTAB-LISHED(已建立连接)状态。
至此三次握手就完成了。
为什么要进行 TCP 三次握手
为什么进行三次握手而不是两次握手,第三次 A 还要去给 B 去确认呢?
这主要是为了防止已经失效的链接请求报文突然又传送到了 B,并且 B 也给了返回,但是这个时候 A 是不需要这个返回的,所以不会对 B 的返回进行响应,但是 B 还在一直等待 A 的返回,这就导致了 B 的资源白白浪费了。
如果有了第三步握手,B 就认为没有和 A 进行建立连接,就不会对 A 的请求做出响应。
TCP 四次挥手
四次挥手指的是 TCP 连接断开的过程,挥手需要在客户端和服务端交换四个 TCP 报文段,四次挥手过程如下图所示:
刚开始的时候 A 和 B 是已经建立了 TCP 连接的,处于 是可以正常通信的,再不需要该连接的时候执行了上图中的过程,下面依次分析
第一次挥手
当客户端 A 没有数据要和服务器 B 交互的时候,就会发送一个 TCP 报文,
FIN = 1 标志着要断开此 TCP 连接
seq = u ;u 等于前面已经传送过数据的最后一个字节的序号 +1
A 发送了这个 TCP 报文以后就进入了 FIN-WAIT1(终止等待 1)状态,等待 B 的确认,这里要注意的是:即使 FIN 报文段不携带数据,也会将序号消耗掉一个。
第二次挥手
B 接收到 A 发来的请求终止 TCP 连接的报文以后,要回复给 A 一个确认报文,
ACK = 1 ;ACK =1 表示确认
ack = u+1 ;把 A 传来的 u 加 1 以后,通过确认序号返回给 A
seq = v ;自身前面已经传送过的数据的最后一个字节的需要+1 以后作为本次序号传递给 A
B 发送完了本次确认报文以后就进入了 CLOSE-WAIT(关闭等待)状态,这时的 TCP 连接处于半关闭状态,即:A 没有数据要发送给 B,但是 B 还是有可能发送数据给 A 的,这个时候 A 仍然会对 B 发送的数据进行接收。
A 收到 B 传来的确认报文以后,就进入了 FIN-WAIT-2(终止等待 2) 状态,等待 B 发送最后的连接关闭报文
第三次挥手
这个时候发生在 B 也没有数据要发送给 A 了,这时 B 要发送报文告诉 A,B 自身也要关闭连接。
FIN = 1; 关闭连接控制位
ACK = 1; 确认控制位
seq = w; 如果在第二次握手以后 B 没有发送数据,那么 w = v,如果发生了数据发送,就 w > v
ack = u+1;重复发送上次发送的 u+1确认序号。
此时 B 发送了以后自身进入了LAST-ACK(最后确认)状态,等待 A 的确认
第四次挥手
A 在收到 B 的请求关闭连接的报文以后,在确认报文段中将 ACk 置为 1,表示确认,并给 B 回复:
ACK = 1;确认控制位
ack = w+1;确认序号为 B 发来的 w+1
seq = u+1;自身的序号+1
A 在发送了最后的报文以后,自身就进入了TIME-WAIT(时间等待)状态,此时的 TCP 连接还没有释放掉,必须经过时间等待计时器(TIME-WAIT timer)设置的时间 2MSL 以后 A 才会进入到 CLOSED 状态。
只要 B 接收到 A 发来的确认报文,就进入了 CLOSED 状态,但是由于 A 要再发送报文以后等待一段时间才会关闭,所以B 的关闭会比 A 早一点。
为什么要进行 TCP 四次挥手
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。
还有是为了在确保客户端和服务端双方不需要通信的时候,及时的断开连接,不在占用资源服务器资源。
UDP 报文
UDP 的报文结构就比 TCP 报文结构简单了很多。
- 源端口:源端口号。在需要对方回信时选用。不需要时可用全0。
- 目的端口:目的端口号。这在终点交付报文时必须要使用到。
- UDP长度: UDP用户数据报的长度,其最小值是8(仅有首部)。
- 校验和:检测UDP用户数据报在传输中是否有错。有错就丢弃
六、一次完整的 HTTP 请求流程
https://markdown-1258186581.cos.ap-shanghai.myqcloud.com/111.png
上面的报文完整的展示了一次完整的 http 请求响应的过程。
1、三次握手
前面分析过这里不再描述
2、客户端 Http 请求和 服务端 tcp 报文确认
第一步:客户端发送 HTTP 请求报文给服务端,
第二步:服务端收到客户端发送来的 HTTP 请求报文以后通过 TCP 报文告诉客户端已经收到
3、服务端要求客户端立即响应
这段其实自己也不是很清楚,有兴趣的可以看下这里
4、服务端 Http 响应和 客户端 tcp 报文确认
第一步:服务端发送 HTTP 响应报文给客户端,
第二步:客户端收到服务端发送来的 HTTP 响应报文以后通过 TCP 报文告诉服务端已经收到
5、四次挥手
前面分析过这里不再描述
七、最后
以上内容都是自己在学习 HTTP 网络知识的时候总结的,有些地方可能不是很准确,欢迎一起交流沟通。
事实上现在使用 http 协议的已经很少见了,大多数都已经实现了 https 协议,有兴趣的看下这篇 计算机网络学习之 Https 相关。
欢迎关注我的公众号: