程序员

一次HTTP请求经历了什么?

2020-11-19  本文已影响0人  二手认知

程序员一定被问过这个问题:

我在浏览器输入一个网址,后面发生了什么?

有人要说了,这么老的问题也拿出来说:

就是HTTP协议呀,服务端也使用HTTP协议接收,我们就是这么做的,很简单。。。

上面的回答可能是很多工程师真实的工作体验,现在的技术领域分工高度发达,我们引入一个库就可以高效地开发应用了,完全不需要也没时间去了解底层发生了什么。

那么底层究竟发生了什么呢?事实上这个老问题真的不算简单,可延伸的幅度非常宽广,我们按照下面几个问题慢慢展开:

问题1: 生成HTTP报文后,怎么发出去的?
问题2: 网络为啥要分层呢?我直接发不就行了吗?
问题3: 怎么找到我要访问的服务器呢?
问题4: 我的数据丢了怎么办?
问题5:不是还有个Socket吗?我们用的是Socket长连接,它是第几层啊?
问题6: 我们用的是HTTPS,它和HTTP一样属于应用层吗?

HTTP(Hyper Text Transport Protocol)全称是超文本(扩展的文本)传输协议,HTTP协议诞生的目的就是传输HTML文本,后面逐渐迭代出HTTP/1.0、HTTP/1.1、HTTP/2 和 HTTP/3这些版本,迭代带来的是更好的性能和更加丰富的能力。

TIP: 协议(Ptotocol): 在计算机领域,理解为双方都要遵守的约定。

本文会高频地提到“协议”这个词(如HTTP、TCP、UDP等),为了解决不同场景的问题,计算机领域的前辈们制定了大量的协议,这些协议的凝结着解决问题的智慧。

先看一下HTTP协议的报文格式:

HTTP协议的报文格式

如果你有幸参与过HTTP服务的初期搭建,可能做过一些平时不需要关注的工作: 比如HTTP的版本选择、Header的生成和解析等,后面的同事运气足够好的话,常常会不知道这些东西的存在。

现在你应该知道了,一次HTTP请求,就是以上面的报文格式发出请求和收到响应的过程。

好的,文章结束了,告辞!


对这个解释有位同学非常不满意,甚至有点想骂人(能讲点我不知道的吗),然后抛出一个问题:

问题1: 生成HTTP报文后,怎么发出去的?

如果问老程序员,他会说发给了下层,下面还有好几层。。。

网络的分层模型

与很多大型项目解决方案类似,网络的基本处理框架也是分层。每一层负责自己的事情,处理完成后丢给上层(接收场景)或下层(发送场景)。越下层能力越通用。我们以实际使用的TCP/IP模型为例,如下图。

TCP/IP网络模型

如上图所示,TCP/IP的模型分为5层,从下到上依次是: 物理层、MAC层、IP层、传输层、应用层。我们用到的HTTP协议就属于应用层。

很多人会把IP层叫网络层,把MAC层叫数据链路层,网络层和数据链路层的说法来自OSI模型(有7层),当然没必要太较真,了解就好。

基本模型了解后,我们就知道了,生成HTTP报文后,会发送到下面的传输层,然后一层一层地向下传输。那么一次HTTP请求的过程大概就是。。。这样:

好的,文章结束了,告辞!


对这个解释有位同学依然不满意,甚至有点想骂人(能说点我不知道的吗),然后抛出一个问题:

问题2: 网络为啥要分层呢?我直接发不就行了吗?

我们上面已经说了,HTTP报文不是直接发给接收端的HTTP,而是向下逐层传输,最终由物理层发出去,接收端也是从物理层接收到数据,然后向上逐层传输,最终到达应用层的HTTP。

经济学有一个基本理论:分工产生效能。

与所有复杂软件项目类似,网络处理是一个非常复杂的工程,每一层的协议都有自己的数据格式,按照分层的方式,不同的层只关心自己的工作,更高效也更容易排查问题。

现实世界里的网络建设已经进行了几十年,除非可以从软件到硬件重新实现一套标准且成功搭建,否则只能使用现有的分层网络模型,别无选择。

另外,假设可以不分层(事实上不行), 允许直接把HTTP报文传输到接收端,那么下层提供的能力你需要全部实现一遍:怎样找到服务器网络包丢了怎么办网络包顺序错了怎么办,这会让本来只需要简单实现的代码变得极其复杂,维护过烂代码的人都知道,如果项目不分模块不解耦,随着程序的迭代,后续的程序将复杂到无法维护。

看到这里你若有所思:

“等一下,你刚刚说我只需要专注于应用层的HTTP?”

。。。

“那我还看这篇文章干嘛?告辞!”


有位同学还是不满意,面色凝重:“:刚才说HTTP把报文发给了下层,我怎么知道要发到哪里去呢?”

问题来了:

问题3: 怎么找到我要访问的服务器呢?

这里有一个隐藏的问题:在网络世界里,哪个属性是具有全局定位功能的?

>>答案: IP地址

不管是IPv4还是IPv6,分配了IP地址的设备才能被定位到; 在TCP/IP模型中,"IP"指的是IP协议(IP层协议),网络包通过IP协议发送到对应的IP地址的设备。

回到问题3,找到我要访问的服务器可以分两步:

1. 知道服务器在哪里:通过域名找到服务器的IP地址

我们在进行HTTP请求的时候,通常不会直接访问IP,而是访问一个域名。这样做有很多好处,同时需要解决一个问题: 我需要根据域名来找到服务器的IP。

DNS(Domain Name System)协议解决了这个问题, 服务商存储了域名与IP的映射关系,对外提供域名解析的服务,解析的过程有可能使用运营商提供的LocalDNS,也可能是公司自建或云服务商提供的HTTPDNS,总之它的作用就是告诉你域名对应的IP。DNS协议使用非常广泛,但不是本文的重点。

这里请注意,DNS和HTTP都是应用层的协议,HTTP协议本身不包括DNS的内容,但是实现HTTP的请求时用到了DNS协议。

2. 通过IP地址,把数据发到服务器

通过DNS协议已经找到了服务器的IP地址,接下来的问题是:

如何一步一步经过中间设备,最终成功访问到服务器IP呢?

其实,这是网络领域的核心问题,要解决这个问题,我们需要路由协议和一个耳熟能详的中间设备: 路由器。

路由器(Router)是一台网络设备,具有数据处理和运算能力,有多张网卡,每张网卡都同时是路由器的入口和出口。它的工作机制是:

一个路由器为什么知道我的数据包要怎么转发呢? 是因为它的路由表里包含了对应的路由规则。

那路由规则是什么时候写入的呢? 写入分静态路由和动态路由两种,由于网络信息变化太多,静态路由已经不适用了。动态路由的规则主要通过广播的形式通知其他路由器,其他路由器接收到信息之后,构建或更新基于图的数据结构。实际上,确定转发规则的时候,就是实现图中计算最短路径。比较流行的动态路由协议有BGP和OSPF等。

IP层的路由协议还有一个问题:不保证数据包送达。

我们当前的讨论(使用路由协议,把数据包发送到对应IP地址的设别)发生在IP层,也就是TCP/IP模型中的第三层,在IP层看来,人们看视频,刷网页,打游戏的过程,就是根据对应的路由协议,一群路由器不停转发的过程。

好的,你现在已经知道了,HTTP的请求就是这样一步一步找到了服务器,也把数据发送过去了,文章也就结束了,告辞!


有位同学说: “等一下先别走,你说IP包不能保证送达”

那问题来了:

问题4: 我的数据丢了怎么办?

你: IP啊,听说你不能保证网络包送达呀。。。

IP层: 我尽力了!

你: 。。。我要转账的,我怎么知道收没收到啊?

IP层: 我尽力了!

你: 。。。

为了解决这个问题,TCP协议登场了。

前面提到的HTTP是应用层协议(第五层),路由协议是IP层协议(第三层),新来的这位TCP老兄属于传输层协议(第四层)。

传输层协议最被人乐道的就是TCP(Transport Control Protocol 传输控制协议)和UDP(User Datagram Protocol 用户数据报协议),其中HTTP主要使用了TCP作为传输层协议,我们这里只介绍TCP。

TCP老兄说:既然让我解决这么难的问题,那就要按照我的套路来处理了,你们也别嫌我烦,我需要“三次握手”来建立连接,需要维护连接的数据结构,我需要建立一套复杂的机制来保证不丢包和不乱序,也需要“四次挥手”来断开连接,还有。。。

TCP的主要特性是可靠传输,它是通过面向连接来实现可靠的,至于TCP的连接,实际上是两端维护了一定的数据结构,在逻辑上维护了一个“连接”的状态,在这种状态下,双方都会接收对方发过来的数据。

使用TCP传输,首先要建立连接,典型的过程如下图:

TCP建立连接-“三次握手”

连接建立后,我们就可以把HTTP请求里的数据通过TCP的连接发送到服务器了。

回到上面的问题,你TCP老兄是如何保证传输可靠的呢?TCP下面基于IP层的,我一个IP包丢了,你是怎么知道丢了,又是怎么处理的?

我们先讲一个无奈的逻辑,TCP的下层是基于IP层的,某个IP包(IP Packet)丢了这件事TCP一点办法都没有。它又没跟着这个包,它不会知道包丢了,只能在对方收到包的时候发动一个响应包,来间接地推断出包没丢。这也是连接(上图)中A和B建立连接的时间点不同的原因,它们并不是所谓“三次握手”完成后同时建立连接的。

讲到这里,很多文章上来就会说拥塞控制、滑动窗口,一下子能把人讲晕。这里用一个非典型的传输场景(下图),说一下TCP是怎么做到可靠的。没有了解过的朋友先感觉一下,至于更细节的内容,可以查看我的另一篇文章: 《关于TCP的几个问题》

建议花几分钟看一下这个略显复杂的流程,按照数字序号和旁边的标注(重点关注丢包后的处理逻辑):

非典型TCP传输例子

一图胜百言,我们发现为了保证可靠(避免丢包+乱序)TCP也是煞费苦心,没有任何黑科技,就是点名+记录+重传。

点到为止,这里介绍了TCP传输的基本逻辑,TCP处理的日常的场景中往往更加复杂,也有更多的策略。这里要得出一个结论:如果项目在技术上直接或间接地使用TCP作为你的传输层协议,那么丢包重传、乱序这些逻辑TCP已经全部实现了,你不需要在上层再加一层相应ACK的逻辑了(除非场景足够特殊)。

某一次在公司的接口文档中,看到一位程序员挑战技术方案:

方案是: 在基于TCP长连接的推送服务中,服务端要求客户端在收到推送消息时,向服务端发送ACK(应答)。

这个方案被老程序员挑战(非原话): 我们PUSH底层基于TCP,内部已经实现了丢包检测、重传等机制,上层再加一个ACK就是浪费流量。

看到这条写于2015年评论,我对这位老程序员肃然起敬。

每次聊到TCP内心就会涌出感慨: TCP协议作为TCP/IP模型的排头兵,凝聚了多少前辈的智慧,在不可靠的网络世界中,纯粹靠逻辑建起了一个(几乎)可靠的网络上层,TCP协议的使用者几乎等于全球的网民,几十年来,作为互联网可靠传输能力的保障,TCP协议的创造者到体验到的是怎样的成就感呢!

扯远了,关于TCP的连接断开(“四次挥手”)这里就不做介绍了,现在你(基本)知道为什么不怕丢包了,那文章也结束了,告辞~


有位同学说:“等一下,你刚刚说TCP长连接,我们的长连接是用Socket做的”

那么问题又来了:

问题5:不是还有个Socket吗?我们用的是Socket长连接,它是第几层啊?

这个概念经常被混淆,很多工程师只知道Socket不知道TCP。

Socket是系统提供给应用层的操作接口(更准确地说它是一个系统调用),用于让系统处理应用层以下逻辑,Socket不属于网络分层的概念。

那为什么要提供这个接口呢?

在Linux系统中,TCP/IP模型中实现了2到4层(数据链路层、IP层和传输层)协议的处理代码在内核里,只有应用层的实现需要应用自己做。过程中处理2到4层数据处于内核态,内核中对于网络包的处理不区分应用(这样的设计,为全局的流量监控和代理打下了基础),处理应用层数据处于用户态;这样两者就需要跨内核态和用户态通信,就需要一个系统调用完成这个衔接,这就是 Socket。也就是说,如果创建Socket时指定了SOCK_STREAM(TCP),那么调用socket.connect()就会进行“三次握手”创建连接; 调用socket.close()就会"四次挥手"关闭连接。

好,你现在知道什么和TCP的关系了,告辞~


这位同学又来了: “再等一下吧,我们用的是HTTPS,他和HTTP一样吗?”

所以问题还是来了:

问题6: 我们用的是HTTPS,它和HTTP一样属于应用层吗?

先补充一点点基础知识:

OSI, 另一种网络模型,有7层,其中应用层、表示层和会话层三层对应着TCP/IP模型的应用层。

回到问题,我们通常说的HTTPS,是指HTTP+TLS的组合。其中**TLS(Transport Layer Security)协议在OSI模型中,属于会话层(Session Layer),那么对应着TCP/IP模型,TLS属于应用层。

说起TLS,可以聊一下网络安全的问题,你可能听过它之前的版本SSL(Secure Sockets Layer),它们都是用来进行网络加密传输的协议。从文章上面的内容你可能也意识到了,我们无法控制IP数据包在网络中的传输路径,如果中途被人截获、篡改是非常危险的,在今天我们已经如此依赖网络,那么如何杜绝这种事情呢?

还是一样的困境:我们无法改变下层,只能改变自己,我们无法避免数据包被劫持和篡改,但如果能够检测到数据包被篡改了、能做到被截获之后对方无法解析出数据、能够识别到数据被恶意重传了。。。这样也能解决问题。

TLS就在做这件事, TLS会在传输层协议上(通常是TCP连接)建立一条TLS的连接,建立连接的过程会做很多事情,比如证书验证,密钥协商,其中会用到非对称加密和对称加密。连接建立之后的数据传输只使用对称加密。

互联网的世界里并非全是光明,网络攻防上演着一波又一波的博弈。即使在今天,HTTP劫持依然在发生,非对称加密的RSA算法在数学上证明了暴力破解需要(相对)无限长的时间,看起来能够保证安全了,Google已经把未使用HTTPS的网站标记为不安全了,很多新版本的手机系统也默认不允许明文(不经过加密)的数据传输。

现在,你知道了TLS属于哪一层了,甚至知道的有点多了,好的,告。。。算了,不告辞了,总结一下吧。


总结: 一个HTTP请求经历了什么?

能看到这里,同学你的好奇心是有点强啊!

如果之前没有了解过,你可能觉得挺有道理,但是脑子很懵,那我们就捋一捋,一个HTTP请求经历了什么。。。

用户数据加上一层层Headers,最终通过硬件网卡发出去

好的,文章真的结束了,告辞!

上一篇下一篇

猜你喜欢

热点阅读