@Dale-Lin
2023-03-08T22:18:10.000000Z
字数 4580
阅读 1153
HTTP
浏览器收到 URL 后,执行以下步骤:
1. 解析 URL 中的主机名。
2. 查询主机名的IP地址(DNS)。
3. 获得端口号。
4. 发起到该IP地址该端口的连接。
5. 向服务器发送请求报文。
6. 向服务器读取响应报文。
7. 关闭连接。
HTTP 连接实际上就是 TCP 连接及其使用规则。
TCP 会按序、无差错地承载 HTTP 数据。
TCP 数据通过IP分组
的小数据块发送。
HTTP(S)协议栈:
应用层
会话层
传输层
网络层
数据链路层
HTTP 要传送一条报文时,会以流的形式将报文数据通过一条打开的 TCP 连接按序传输。
TCP 收到数据流之后,会将数据流砍成段,封装在 IP 分组中,通过因特网传输。
每个TCP段都是由IP分组承载,从一个IP地址发到另一个,包括了源/目的IP地址,长度,TCP端口号,TCP控制标记等。
任意时刻计算机都可以有几条 TCP 连接处于打开状态。TCP 是通过端口号来保持这些连接持续不断地运行。
IP可以连接到正确的计算机,端口号可以连接到正确的应用程序上。
TCP 连接通过4个值识别:
<源IP地址、源端口号、目的IP地址、目的端口号>
这4个值一起唯一定义了一条连接。
TCP用主机的IP地址+主机上的端口号作为TCP连接的端点,这种端点就叫套接字(socket)或插口。
套接字用 (IP: port) 表示。
Socket=Ip address+ TCP/UDP + port
区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。
通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
connection 首部有一个由逗号分隔的首部字段列表:
HTTP/1.1 200 OK
Cache-control: max-age=3600
Connection: meter, close, bill-my-credit-card
在客户端发送一条 HTTP 报文后,客户端和最终的源端服务器之间存在的一串 HTTP 实体(代理,高速缓存等)会解析发送端请求的所有选项并应用,对于带有 Connection 首部的报文,该中间实体会删除 Connection 首部以及 Connection 中列出的所有首部(无论是 HTTP 标准首部还是自定义的任意标签值)。
Connection 中还可以加入 close 值,说明连接要在响应后关闭。
由于 Connection 首部可以防止无意中对本地首部的转发,因此将首部放入 Connection 首部称为“对首部的保护”。
如果只对连接进行简单的管理,TCP的性能时延可能会叠加。例如请求一个有三张图片的页面,如果要用四个连接来完成,那么连接时延和慢启动时延就会叠加。
可通过以下几种方法提高连接性能:
HTTP允许客户端打开多条连接,并行执行多个事务。加载和连接的时延得到重叠。
问题是打开大量连接会消耗很多内存资源。如果带宽不足,并行连接不一定更快。
客户端通常会将并行连接的总数限制成4个。
Web客户端经常会打开同一个站点的连接,这种性质称为站点局部性(site locality)。
HTTP/1.1允许设备在事务处理结束之后将TCP连接保持在打开状态,以便为未来的HTTP请求重用现存的连接。
但管理持久连接时要小心,不然会累积大量的空闲连接,耗费本地以及远程客户端和服务器上的资源。
比较老的持久连接类型,去除了进行连接和关闭连接的开销,所以时间线有所缩减。
Keep-Alive已经不再使用了,但浏览器和服务器对keep-alive握手的使用还是很广泛。
可以通过发送一条包含Connection: Keep-Alive
的首部请求将一条连接保持在打开状态。如果服务器愿意为下一条请求将连接保持在打开状态,就在响应首部中也包含该首部;否则不支持,会在响应后关闭连接。
可以用Keep-Alive通用首部中指定的、由逗号分隔的选项来调节其行为。
即使保持Keep-Alive会话后,客户端和浏览器可以在任意时刻关闭空闲的Keep-Alive连接,并可以随意限制Keep-Alive连接所处理事务的数量。
参数:
- timeout:估计了服务器希望将连接保持在活跃状态的时间。并非一个承诺值。
- max:估计了服务器希望为多少个事务保持此连接的活跃状态。并非一个承诺值。
语法为 Keep-Alive: [name]=value
Content-Length
。否则处理的另一端无法精确地检测出一条报文的结束或另一条报文的开始。出现哑代理的时候,客户端发送的下一条请求会被代理挂起;而代理会挂在哪里等待与服务器的连接关闭。直到客户端或服务器将连接超时、并关闭。
现代的代理都绝不能转发Connection首部和所有名字出现在Connection值中的首部。
现代浏览器都实现了Proxy-Connection(网景做法),浏览器会像代理发送非标准的Proxy-Connection扩展首部,如果代理是盲中继,他会将无意义的Proxy-Connection转发给服务器,而服务器会忽略此首部;如果是个能够理解持久连接的握手动作的代理,就用一个Connection首部取代无意义的Proxy-Connection首部,然后将其发给服务器以达到预期效果。
如果客户端和服务器中不止一个代理且有好有坏,那么问题仍得不到解决。
而且可能出现浏览器不可见的代理,那么浏览器并不会发送Proxy-Connection。
HTTP/1.1停止了Keep-Alive连接的支持,使用Persistent Connection(持久连接)的改进型设计取代。
HTTP/1.1持久连接在默认情况下是激活的,除非在报文中显式地添加了Connection: close
首部。
Connection: close
请求首部后,客户端就无法在那条连接上发送更多的请求了。Connection: close
首部。HTTP/1.1允许在持久连接上可选地使用请求管道。在响应到达之前,可以将多条请求放入队列——请求连续而不等待响应。
在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。
限制:
所有HTTP客户端、服务器、代理都可在任意时刻关闭一条TCP传输连接。通常会在一条报文结束的时候关闭(除非服务器怀疑出现了客户端/网络故障,会在请求中间关闭)。
服务器永远无法确定在它关闭“空闲”连接的那一刻,客户端有没有数据要发送。如果出现这种情况,客户端就会在写入半截请求报文时出现连接错误。
每条HTTP响应都应该有精确的Contend-Length首部。
客户端或代理收到一条因连接关闭而结束的HTTP响应,且实际传输的实体长度与Contend-Length并不匹配(或没有),接收端就应该质疑长度的正确性。
如果接收端是个缓存代理,就不应该缓存这条响应。应将问题报文原封不动地转发(忌“校正”),以维护语义的透明性。
除非会带来副作用,否则在客户端执行事务的过程中连接关闭,客户端应该重新打开连接并重试。对于管道化连接来说情况较严重,因为留下了大量未处理的请求等待重新调度。
如果在发送了一些请求数据后,收到返回结果之前连接关闭了,客户端就无法确定服务器端实际激活了多少事务。如果向一个在线书店POST一张订单,就不能重复执行,否则会产生副作用。
若一个事务无论执行一次还是多次结果都相同,则其是幂等的。
一般GET、HEAD、PUT、DELETE、TRACE、OPTIONS方法都是幂等的。
客户端不应以管道化方式传送非幂等的请求。
发送非幂等请求必须等待来自前一条请求的响应状态!
TCP 是双向的,两端都有一个输入队列和一个输出队列,用于数据的读或写。
close()
会完全关闭。套接字shutdown()
单独关闭一个信道,称为半关闭。关闭连接的输出信道是安全的,另一端的对等实体会从其缓冲区中读完所有数据后收到通知,说明流结束了,从而得知连接关闭了。
关闭输入信道比较危险,除非指导另一端不再发送数据。否则操作系统会向另一端发送一条TCP“连接被对端重置”的报文。
大多操作系统会将其作为很严重的错误来处理,删除对端还未读取的所有缓存数据。对于管道化连接来说很糟糕。
3. 正常关闭
HTTP规范建议,当客户端或服务器突然要关闭一条连接时,应该“正常地关闭传输连接”。
总之应该先关闭输出信道,然后等待另一端关闭其输出信道。
但是无法确保对等实体会实现半关闭或对其检查。因此想要正常关闭的应用程序应该先关闭其输出信道,然后周期性地检查其输入新到的状态(查找数据,或流的末尾)。如果一段时间内对端没有关闭输入信道,可以强制关闭连接以节省资源。