1. 键入网址到网页显示,期间发生了什么?#
超参数一面、腾讯 WXG 测开一面
回答模板
当您在浏览器中输入网址并按下回车后,整个过程涉及 TCP/IP 协议栈的每一层协作,具体过程如下:
- 应用层:浏览器解析 URL,生成 HTTP 请求报文(包含请求方法、路径、头部字段等)。随后触发 DNS 查询(通过 UDP 协议),将域名解析为 IP 地址。若访问 HTTPS 站点,还会触发 TLS 握手协商加密参数。
- 传输层:获取目标 IP 后,操作系统通过 TCP 协议与服务器建立连接(三次握手:SYN → SYN-ACK → ACK)。TCP 为 HTTP 数据提供分段、序列号、确认应答和重传机制,确保可靠传输。连接建立后,HTTP 请求被封装为 TCP 数据段发送。
- 网络层:TCP 数据段交给 IP 协议处理,添加源/目标 IP 地址构成 IP 数据包,通过路由选择算法决定转发路径。可能经过多个路由器(跳数递增、TTL递减),最终抵达目标服务器。
- 网络接口层:IP 数据包被封装为帧(如以太网帧),添加 MAC 地址头部。通过 ARP 协议查询下一跳路由器或目标服务器的 MAC 地址,经物理网络(如交换机、光纤)传输至下一节点。
- 服务器端处理:服务器反向解封装帧→IP 包→TCP 段→HTTP 请求,处理后生成 HTTP 响应(状态码、响应头、HTML 等内容),再沿协议栈封装返回。
- 客户端解析与渲染:浏览器接收响应后,解析 HTML 构建 DOM 树,加载 CSS/JS 等子资源(可能触发多次 TCP 连接复用或并发),最终完成页面渲染。
- TCP 连接释放:数据传输完成后,通过 TCP 四次挥手(FIN-ACK)安全关闭连接。
详细的过程如下
想必不少小伙伴面试过程中,会遇到「当键入网址后,到网页显示,其间发生了什么」的面试题。
这问题真挺常问的,好几家公司问了这个问题。
接下来以下图较简单的网络拓扑模型作为例子,探究探究期间发生了什么?

(1) HTTP#
浏览器做的第一步工作就是解析 URL
首先浏览器做的第一步工作就是要对 URL 进行解析,从而生成发送给 Web 服务器的请求信息。
让我们看看一条长长的 URL 里的各个元素的代表什么,见下图:

所以图中的长长的 URL 实际上是请求服务器里的文件资源。
要是上图中的蓝色部分 URL 元素都省略了,那应该是请求哪个文件呢?
当没有路径名时,就代表访问根目录下事先设置的默认文件,也就是 /index.html 或者 /default.html 这些文件,这样就不会发生混乱了。
生产 HTTP 请求信息
对 URL 进行解析之后,浏览器确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息了。

(2) DNS#
通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器。
但在发送之前,还有一项工作需要完成,那就是查询服务器域名对应的 IP 地址,因为委托操作系统发送消息时,必须提供通信对象的 IP 地址。
有一种服务器就专门保存了 Web 服务器域名与 IP 的对应关系,它就是 DNS 服务器。
域名的层级关系
DNS 中的域名都是用句点来分隔的,比如 www.server.com,这里的句点代表了不同层次之间的界限。
- 在域名中,越靠右的位置表示其层级越高。
- 毕竟域名是外国人发明,所以思维和中国人相反,比如说一个城市地点的时候,外国喜欢从小到大的方式顺序说起(如 XX 街道 XX 区 XX 市 XX 省),而中国则喜欢从大到小的顺序(如 XX 省 XX 市 XX 区 XX 街道)。
- 实际上域名最后还有一个点,比如
www.server.com.,这个最后的一个点代表根域名。 - 也就是,
.根域是在最顶层,它的下一层就是.com顶级域,再下面是server.com。
所以域名的层级关系类似一个树状结构:
- 根 DNS 服务器(.)
- 顶级域 DNS 服务器(.com)
- 权威 DNS 服务器(server.com)

根域的 DNS 服务器信息保存在互联网中所有的 DNS 服务器中。
这样一来,任何 DNS 服务器就都可以找到并访问根域 DNS 服务器了。
因此,客户端只要能够找到任意一台 DNS 服务器,就可以通过它找到根域 DNS 服务器,然后再一路顺藤摸瓜找到位于下层的某台目标 DNS 服务器。
域名解析的工作流程
- 客户端首先会发出一个 DNS 请求,问
www.server.com的 IP 是啥,并发给本地 DNS 服务器(也就是客户端的 TCP/IP 设置中填写的 DNS 服务器地址)。 - 本地域名服务器收到客户端的请求后,如果缓存里的表格能找到
www.server.com,则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器:“能告诉我www.server.com的 IP 地址吗?” 根域名服务器是最高层次的,它不直接用于域名解析,但能指明一条道路。 - 根 DNS 收到来自本地 DNS 的请求后,发现后置是
.com,说“www.server.com这个域名归.com区域管理”,我给你.com顶级域名服务器地址给你,你去问问它吧。” - 本地 DNS 收到顶级域名服务器的地址后,发起请求问“你能告诉我
www.server.com的 IP 地址吗?” - 顶级域名服务器说:“我给你负责
www.server.com区域的权威 DNS 服务器的地址,你去问它应该能问到”。 - 本地 DNS 于是转向问权威 DNS 服务器:“
www.server.com对应的 IP 是啥呀?”server.com的权威 DNS 服务器,它是域名解析结果的原出处。 - 权威 DNS 服务器查询后将对应的 IP 地址
X.X.X.X告诉本地 DNS。 - 本地 DNS 再将 IP 地址返回客户端,客户端和目标建立连接。
至此,我们完成了 DNS 的解析过程。现在总结一下,整个过程我画成了一个图。

那是不是每次解析域名都要经过那么多的步骤呢?
当然不是了,还有缓存这个东西的嘛。
- 浏览器会先看自身有没有对这个域名的缓存,如果有,就直接返回;
- 如果没有,就去问操作系统,操作系统也会去看自己的缓存,如果有,就直接返回;
- 如果没有,再去
hosts文件看; - 也没有,才会去问「本地 DNS 服务器」
(3) 协议栈#
通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统中的协议栈。
协议栈的内部分为几个部分,分别承担不同的工作。上下关系是有一定的规则的,上面的部分会向下面的部分委托工作,下面的部分收到委托的工作并执行。

应用程序(浏览器)通过调用 Socket 库,来委托协议栈工作。
协议栈的上半部分有两块,分别是负责收发数据的 TCP 和 UDP 协议,这两个传输协议会接受应用层的委托执行收发数据的操作。
协议栈的下面一半是用 IP 协议控制网络包收发操作,在互联网上传数据时,数据会被切分成一块块的网络包,而将网络包发送给对方的操作就是由 IP 负责的。
此外 IP 中还包括 ICMP 协议和 ARP 协议。
ICMP用于告知网络包传送过程中产生的错误以及各种控制信息。ARP用于根据 IP 地址查询相应的以太网 MAC 地址。
IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收操作。
(4) TCP —— 可靠传输#
HTTP 是基于 TCP 协议传输的,所以在这我们先了解下 TCP 协议。
TCP 包头格式
我们先看看 TCP 报文头部的格式:

首先,源端口号和目标端口号是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。
接下来有包的序号,这个是为了解决包乱序的问题。
还有应该有的是确认号,目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决丢包的问题。
接下来还有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。
除了做流量控制以外,TCP 还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。不能改变世界,就改变自己嘛。
TCP 传输数据之前,要先三次握手建立连接
在 HTTP 传输数据之前,首先需要 TCP 建立连接,TCP 连接的建立,通常称为三次握手。
这个所谓的「连接」,只是双方计算机里维护一个状态机,在连接建立的过程中,双方的状态变化时序图就像这样。

- 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。
- 然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。
- 服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。
- 客户端收到服务端发送的 SYN 和 ACK 之后,发送对 SYN 确认的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。
- 服务端收到 ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。
所以三次握手目的是保证双方都有发送和接收的能力。
如何查看 TCP 的连接状态?
TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。

TCP 分割数据
如果 HTTP 请求消息比较长,超过了 MSS 的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。

MTU:一个网络包的最大长度,以太网中一般为 1500 字节。MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
数据会被以 MSS 的长度为单位进行拆分,拆分出来的每一块数据都会被放进单独的网络包中。也就是在每个被拆分的数据加上 TCP 头信息,然后交给 IP 模块来发送数据。
注:HTTP 头部和消息体是作为一个整体被 TCP 分割的。

TCP 报文生成
TCP 协议里面会有两个端口,一个是浏览器监听的端口(通常是随机生成的),一个是 Web 服务器监听的端口(HTTP 默认端口号是 80, HTTPS 默认端口号是 443)。
在双方建立了连接后,TCP 报文中的数据部分就是存放 HTTP 头部 + 数据,组装好 TCP 报文之后,就需交给下面的网络层处理。
至此,网络包的报文如下图。

(5) IP —— 远程定位#
TCP 模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成网络包发送给通信对象。
IP 包头格式
我们先看看 IP 报文头部的格式:

在 IP 协议里面需要有源地址 IP 和目标地址 IP:
- 源地址 IP,即是客户端输出的 IP 地址
- 目标地址,即通过 DNS 域名解析得到的 Web 服务器 IP
因为 HTTP 是经过 TCP 传输的,所以在 IP 包头的协议号,要填写为 06(十六进制),表示协议为 TCP。
假设客户端有多个网卡,就会有多个 IP 地址,那 IP 头部的源地址应该选择哪个 IP 呢?
当存在多个网卡时,在填写源地址 IP 时,就需要判断到底应该填写哪个地址。这个判断相当于在多块网卡中判断应该使用哪个一块网卡来发送包。
这个时候就需要根据路由表规则,来判断哪一个网卡作为源地址 IP。
在 Linux 操作系统,我们可以使用 route -n 命令查看当前系统的路由表。

举个例子,根据上面的路由表,我们假设 Web 服务器的目标地址是 192.168.10.200。

- 首先先和第一条目的子网掩码(Genmask)进行 与运算,得到结果为
192.168.10.0,但是第一个条目的 Destination 是192.168.3.0,两者不一致所以匹配失败。 - 再与第二条目的子网掩码进行 与运算,得到的结果为
192.168.10.0,与第二条目的 Destination192.168.10.0匹配成功,所以将使用eth1网卡的 IP 地址作为 IP 包头的源地址。
那么假设 Web 服务器的目标地址是 10.100.20.100,那么依然依照上面的路由表规则判断,判断后的结果是和第三条目匹配。
第三条目比较特殊,它目标地址和子网掩码都是 0.0.0.0,这表示默认网关,如果其他所有条目都无法匹配,就会自动匹配这一行。并且后续就把包发给路由器,Gateway 即是路由器的 IP 地址。
IP 报文生成
至此,网络包的报文如下图。

(6) MAC —— 两点传输#
生成了 IP 头部之后,接下来网络包还需要在 IP 头部的前面加上 MAC 头部。
MAC 包头格式
MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息。

在 MAC 包头里需要发送方 MAC 地址和接收方目标 MAC 地址,用于两点之间的传输。
一般在 TCP/IP 通信里,MAC 包头的协议类型只使用:
0800: IP 协议0806: ARP 协议
MAC 发送方和接收方如何确认?
发送方的 MAC 地址获取就比较简单了,MAC 地址是在网卡生产时写入到 ROM 里的,只要将这个值读取出来写入到 MAC 头部就可以了。
接收方的 MAC 地址就有点复杂了,只要告诉以太网对方的 MAC 的地址,以太网就会帮我们把包发送过去,那么很显然这里应该填写对方的 MAC 地址。
所以先得搞清楚应该把包发给谁,这个只要查一下路由表就知道了。在路由表中找到相匹配的条目,然后把包发给 Gateway 列中的 IP 地址就可以了。
既然知道要发给谁,按如何获取对方的 MAC 地址呢?
不知道对方 MAC 地址?不知道就喊呗。
此时就需要 ARP 协议帮我们找到路由器的 MAC 地址。

ARP 协议会在以太网中以广播的形式,对以太网所有的设备喊出:“这个 IP 地址是谁的?请把你的 MAC 地址告诉我”。
然后就会有人回答:“这个 IP 地址是我的,我的 MAC 地址是 XXXX”。
如果对方和自己处于同一个子网中,那么通过上面的操作就可以得到对方的 MAC 地址。然后,我们将这个 MAC 地址写入 MAC 头部,MAC 头部就完成了。
好像每次都要广播获取,这不是很麻烦吗?
放心,在后续操作系统会把本次查询结果放到一块叫做 ARP 缓存的内存空间留着以后用,不过缓存的时间就几分钟。
也就是说,在发包时:
- 先查询 ARP 缓存,如果其中已经保存了对方的 MAC 地址,就不需要发送 ARP 查询,直接使用 ARP 缓存中的地址。
- 而当 ARP 缓存中不存在对方 MAC 地址时,则发送 ARP 广播查询。
查看 ARP 缓存内容
在 Linux 系统中,我们可以使用 arp -a 命令来查看 ARP 缓存的内容。

MAC 报文生成
至此,网络包的报文如下图。

(7) 网卡#
网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,我们需要将数字信息转换为电信号,才能在网线上传输,也就是说,这才是真正的数据发送过程。
负责执行这一操作的是网卡,要控制网卡还需要靠网卡驱动程序。
网卡驱动获取网络包之后,会将其复制到网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列。

- 起始帧分界符是一个用来表示包起始位置的标记
- 末尾的 FCS(帧校验序列)用来检查包传输过程是否有损坏
最后网卡会将包转为电信号,通过网线发送出去。
(8) 交换机#
下面来看一下包是如何通过交换机的。交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备。
交换机的包接收操作
首先,电信号到达网线接口,交换机里的模块进行接收,接下来交换机里的模块将电信号转换为数字信号。
然后通过包末尾的 FCS 校验错误,如果没问题则放到缓冲区。这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同。
计算机的网卡本身具有 MAC 地址,并通过核对收到的包的接收方 MAC 地址判断是不是发给自己的,如果不是发给自己的则丢弃;相对地,交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址。
将包存入缓冲区后,接下来需要查询一下这个包的接收方 MAC 地址是否已经在 MAC 地址表中有记录了。
交换机的 MAC 地址表主要包含两个信息:
- 一个是设备的 MAC 地址,
- 另一个是该设备连接在交换机的哪个端口上。

举个例子,如果收到的包的接收方 MAC 地址为 00-02-B3-1C-9C-F9,则与图中表中的第 3 行匹配,根据端口列的信息,可知这个地址位于 3 号端口上,然后就可以通过交换电路将包发送到相应的端口了。
所以,交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口。
当 MAC 地址表找不到指定的 MAC 地址会怎么样?
地址表中找不到指定的 MAC 地址。这可能是因为具有该地址的设备还没有向交换机发送过包,或者这个设备一段时间没有工作导致地址被从地址表中删除了。
这种情况下,交换机无法判断应该把包转发到哪个端口,只能将包转发到除了源端口之外的所有端口上,无论该设备连接在哪个端口上都能收到这个包。
这样做不会产生什么问题,因为以太网的设计本来就是将包发送到整个网络的,然后只有相应的接收者才接收包,而其他设备则会忽略这个包。
有人会说:“这样做会发送多余的包,会不会造成网络拥塞呢?”
其实完全不用过于担心,因为发送了包之后目标设备会作出响应,只要返回了响应包,交换机就可以将它的地址写入 MAC 地址表,下次也就不需要把包发到所有端口了。
局域网中每秒可以传输上千个包,多出一两个包并无大碍。
此外,如果接收方 MAC 地址是一个广播地址,那么交换机会将包发送到除源端口之外的所有端口。
以下两个属于广播地址:
- MAC 地址中的
FF:FF:FF:FF:FF:FF - IP 地址中的
255.255.255.255
(9) 路由器 —— 出境大门#
路由器与交换机的区别
网络包经过交换机之后,现在到达了路由器,并在此被转发到下一个路由器或目标设备。
这一步转发的工作原理和交换机类似,也是通过查表判断包转发的目标。
不过在具体的操作过程上,路由器和交换机是有区别的。
- 因为路由器是基于
IP设计的,俗称三层网络设备(物理层 + 链路层 + 网络层),路由器的各个端口都具有 MAC 地址和 IP 地址; - 而交换机是基于以太网设计的,俗称二层网络设备(物理层 + 链路层),交换机的端口不具有 MAC 地址。
路由器基本原理
路由器的端口具有 MAC 地址,因此它就能够成为以太网的发送方和接收方;同时还具有 IP 地址,从这个意义上来说,它和计算机的网卡是一样的。
当转发包时,首先路由器端口会接收发给自己的以太网包,然后路由表查询转发目标,再由相应的端口作为发送方将以太网包发送出去。
路由器的包接收操作
首先,电信号到达网线接口部分,路由器中的模块会将电信号转成数字信号,然后通过包末尾的 FCS 进行错误校验。
如果没问题则检查 MAC 头部中的接收方 MAC 地址,看看是不是发给自己的包,如果是就放到接收缓冲区中,否则就丢弃这个包。
总的来说,路由器的端口都具有 MAC 地址,只接收与自身地址匹配的包,遇到不匹配的包则直接丢弃。
查询路由表确定输出端口
完成包接收操作之后,路由器就会去掉包开头的 MAC 头部。
MAC 头部的作用就是将包送达路由器,其中的接收方 MAC 地址就是路由器端口的 MAC 地址。因此,当包到达路由器之后,MAC 头部的任务就完成了,于是 MAC 头部就会被丢弃。
接下来,路由器会根据 MAC 头部后方的 IP 头部中的内容进行包的转发操作。
转发操作分为几个阶段,首先是查询路由表判断转发目标。

具体的工作流程根据上图,举个例子:
- 假设地址为 1
0.10.1.101的计算机要向地址为192.168.1.100的服务器发送一个包,这个包先到达图中的路由器。 - 判断转发目标的第一步,就是根据包的接收方 IP 地址查询路由表中的目标地址栏,以找到相匹配的记录。
- 路由匹配和前面讲的一样,每个条目的子网掩码和
192.168.1.100IP 做&与运算后,得到的结果与对应条目的目标地址进行匹配,如果匹配就会作为候选转发目标,如果不匹配就继续与下个条目进行路由匹配。 - 如第二条目的子网掩码
255.255.255.0与192.168.1.100IP 做&与运算后,得到结果是192.168.1.0,这与第二条目的目标地址192.168.1.0匹配,该第二条目记录就会被作为转发目标。 - 实在找不到匹配路由时,就会选择默认路由,路由表中子网掩码为
0.0.0.0的记录表示「默认路由」。
路由器的发送操作
接下来就会进入包的发送操作。
首先,我们需要根据路由表的网关列判断对方的地址。
- 如果网关是一个 IP 地址,则这个IP 地址就是我们要转发到的目标地址,还未抵达终点,还需继续需要路由器转发。
- 如果网关为空,则 IP 头部中的接收方 IP 地址就是要转发到的目标地址,也是就终于找到 IP 包头里的目标地址了,说明已抵达终点。
知道对方的 IP 地址之后,接下来需要通过 ARP 协议根据 IP 地址查询 MAC 地址,并将查询的结果作为接收方 MAC 地址。
路由器也有 ARP 缓存,因此首先会在 ARP 缓存中查询,如果找不到则发送 ARP 查询请求。
接下来是发送方 MAC 地址字段,这里填写输出端口的 MAC 地址。还有一个以太类型字段,填写 0800 (十六进制)表示 IP 协议。
网络包完成后,接下来会将其转换成电信号并通过端口发送出去。这一步的工作过程和计算机也是相同的。
发送出去的网络包会通过交换机到达下一个路由器。由于接收方 MAC 地址就是下一个路由器的地址,所以交换机会根据这一地址将包传输到下一个路由器。
接下来,下一个路由器会将包转发给再下一个路由器,经过层层转发之后,网络包就到达了最终的目的地。
不知你发现了没有,在网络包传输的过程中,源 IP 和目标 IP 始终是不会变的,一直变化的是 MAC 地址,因为需要 MAC 地址在以太网内进行两个设备之间的包传输。
(10) 服务器与客户端#
数据包抵达了服务器,于是服务器开始扒数据包的皮!

- 数据包抵达服务器后,服务器会先扒开数据包的 MAC 头部,查看是否和服务器自己的 MAC 地址符合,符合就将包收起来。
- 接着继续扒开数据包的 IP 头,发现 IP 地址符合,根据 IP 头中协议项,知道自己上层是 TCP 协议。
- 于是,扒开 TCP 的头,里面有序列号,需要看一看这个序列包是不是我想要的,如果是就放入缓存中然后返回一个 ACK,如果不是就丢弃。
- TCP 头部里面还有端口号, HTTP 的服务器正在监听这个端口号。于是,服务器自然就知道是 HTTP 进程想要这个包,于是就将包发给 HTTP 进程。
- 服务器的 HTTP 进程看到,原来这个请求是要访问一个页面,于是就把这个网页封装在 HTTP 响应报文里。
- HTTP 响应报文也需要穿上 TCP、IP、MAC 头部,不过这次是源地址是服务器 IP 地址,目的地址是客户端 IP 地址。
- 穿好头部衣服后,从网卡出去,交由交换机转发到出城的路由器,路由器就把响应数据包发到了下一个路由器,就这样跳啊跳。
- 最后跳到了客户端的城门把守的路由器,路由器扒开 IP 头部发现是要找城内的人,于是又把包发给了城内的交换机,再由交换机转发到客户端。
- 客户端收到了服务器的响应数据包后,开始扒皮,把收到的数据包的皮扒剩 HTTP 响应报文后,交给浏览器去渲染页面,一份特别的数据包快递,就这样显示出来了!
- 最后,客户端要离开了,向服务器发起了 TCP 四次挥手,至此双方的连接就断开了。
总结#
TCP/IP 网络模型共有 4 层,分别是应用层、传输层、网络层和网络接口层,每一层负责的职能如下:
- 应用层,负责向用户提供一组应用程序,比如 HTTP、DNS、FTP 等;
- 传输层,负责端到端的通信,比如 TCP、UDP 等;
- 网络层,负责网络包的封装、分片、路由、转发,比如 IP、ICMP 等;
- 网络接口层,负责网络包在物理网络中的传输,比如网络包的封帧、 MAC 寻址、差错检测,以及通过网卡传输网络帧等;

网络通信是分层进行的,每一层都只与另一端的对等层进行对话。下层为上层提供服务。这个过程就像寄信:
- 应用层 (HTTP):你写好信的内容(HTTP 请求/响应)。这包括了 HTTP 头部(如 Content-Type: text/html) 和 消息体(如 HTML 代码)。
- 传输层 (TCP):TCP 层拿到这封完整的“信”(HTTP 数据),但它发现这封信太长了,一个信封装不下。于是它把信拆分成几个小份,每一份都塞进一个 TCP 信封里。每个 TCP 信封上都写着信息(TCP 头部),比如“这是第几份”、“总共有几份”、“发送方和接收方的端口号”。
- 网络层 (IP):IP 层不管 TCP 拆成了几份,它只管拿来一个 TCP 信封,就把它塞进一个更大的 IP 信封里。IP 信封上写着更大的地址信息(IP 头部),比如发送方和接收方的 IP 地址,用于在整个网络上路由。
- 数据链路层 (MAC):最后,MAC 层把 IP 信封再塞进一个帧信封里,这个信封上写着在当前局域网内下一站设备的地址(MAC 头部),比如下一个路由器或者交换机的 MAC 地址。这个帧信封的大小受
MTU限制。
2. 如果 URL 请求的网页响应很慢,可能在哪个环节出现问题?#
腾讯 WXG 测开一面
当 URL 请求的网页响应很慢时,问题可能出现在多个环节,包括客户端、网络传输、服务器端以及内容本身。以下是可能的问题点:
- 在客户端层面
- 浏览器缓存过多或配置错误可能拖慢页面加载
- 操作系统网络栈异常(如 TCP 连接数限制或缓冲区设置不当)也可能导致连接建立缓慢
- DNS 客户端缓存污染或硬件资源(如 CPU、内存)不足会进一步加剧延迟
- DNS 解析环节可能出现问题
- 例如本地 DNS 服务器响应缓慢、递归查询超时,或域名解析记录未正确缓存。如果 DNS 服务器故障或域名配置错误,会导致域名到 IP 地址的转换耗时过长
- 网络传输过程中
- 路由路径不佳可能导致数据包经过过多跳数,从而增加延迟
- 带宽瓶颈在用户本地网络或服务器接入端可能成为限制因素,尤其是在传输大文件时
- 数据包丢失或网络抖动会触发 TCP 重传,降低有效吞吐量,而防火墙或中间设备策略也可能意外丢弃合法流量
- 服务器端问题包括
- 负载过高,例如 CPU、内存或 I/O 资源饱和,导致无法及时处理请求
- 应用逻辑性能差,如低效的数据库查询、代码执行缓慢或缓存未命中,会直接拖慢响应速度
- 后端依赖服务(如第三方 API 或数据库)延迟也可能阻塞整体处理流程
- Web 服务器配置不当(如进程数不足或保持连接超时设置不合理)会影响请求处理效率
- 协议与连接方面
- TCP 三次握手或 TLS 协商在高延迟网络中可能显著增加连接建立时间
- HTTP/1.1 的队头阻塞问题(未启用并行连接时)会导致单个请求延迟影响后续资源加载
- CDN 或代理服务器配置错误可能造成请求转发效率低下或缓存失效
- 内容本身的问题也不容忽视,例如未压缩的大资源(如图片、视频)会增加传输时间,过多阻塞渲染的 JavaScript 或同步加载策略会延迟页面呈现,而重复请求或未优化的资源链接受限於浏览器并发限制。
3. UDP 通信:如果 client 端 sendto 一段 1024 字节的 buf,server 端循环调用 recvfrom(fd,buf,64,0),能否收完?能的话需要调用几次?不能收完原因是什么?#
腾讯面试题
在 UDP 通信中,sendto() 发送的每一次数据都是一个完整的独立报文,接收端使用 recvfrom(fd, buf, 64, 0) 时,每次只能接收一个完整报文的最多 64 字节,如果报文长度超过了缓冲区大小(如发送端发送了 1024 字节),则接收端只会接收到前 64 字节,超出部分会被系统直接丢弃,无法通过多次 recvfrom() 调用将同一个报文拆分接收,因此在这种情况下接收端无法收完整个报文。
4. tcp 通信:client 端循环调用 send(fd,buf,1) 1024 次发给 server,从 server 端捉包,客户端总共发了几个包过来?#
腾讯面试题
在 TCP 通信中,尽管客户端调用了 1024 次 send(fd, buf, 1) 每次仅发送 1 字节的数据,但由于 TCP 是面向字节流的协议,发送的数据会被内核缓冲区聚合后再发送,而不会直接对应为 1024 个网络包。特别是在默认启用 Nagle 算法的情况下,TCP 会将多次小的数据发送请求进行合并,直到缓冲区满或收到 ACK 才会实际发送出去,因此从服务端抓包来看,最终接收到的数据通常会被合并成更少数量的 TCP 包,远少于 1024 个。这种行为由操作系统的 TCP 堆积机制和网络状况共同决定,即使应用层调用了 1024 次 send(),网络中实际传输的包数可能只有几十个或几百个。
5. 应用层有哪些协议?#
- HTTP:超文本传输协议
- HTTPS:HTTP 的安全版本,在 HTTP 下加入 SSL/TLS 层,用于对通信进行加密、认证,确保数据的安全性和完整性
- FTP:文件传输协议,用于在客户端和服务器之间进行双向文件传输(上传和下载)。
- SMTP:简单邮件传输协议,用于发送邮件以及将邮件从发送人的邮件服务器转发到接收人的邮件服务器
- POP3:邮局协议第 3 版,用于从邮件服务器下载邮件到本地计算机,通常下载后会删除服务器上的邮件
- IMAP:互联网消息访问协议,更高级的邮件接收协议,允许用户在本地管理服务器上的邮件(如创建、删除、移动邮箱文件夹),邮件始终保留在服务器上
- SSH:远程连接协议,用于通过加密的连接安全地远程登录到另一台计算机,并执行命令,是 Telnet 的安全替代品
- DNS:域名系统协议,它不是直接为用户服务的,而是为其他应用层协议服务的。它将人类可读的域名(如
www.google.com)转换为机器可读的 IP 地址(如142.251.42.206) - DHCP:动态主机配置协议,自动为网络中的设备分配 IP 地址、子网掩码、默认网关和 DNS 服务器地址,即“插网线就能上网”的基础
- SIP:会话发起协议,用于创建、修改和终止包含视频、语音、即时消息等在内的多媒体会话,是很多 VOIP(网络电话)系统的基础
- RTP:实时传输协议,通常与 SIP 等协议配合使用,负责实际传输音频和视频流数据
6. TCP 是什么,怎么保证可靠性的?#
腾讯 TEG 一面
TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
它主要通过以下核心机制来保证可靠性的:
- 确认和重传机制
- 确认 (ACK):接收方在成功收到数据包后,会向发送方返回一个确认报文(ACK)。ACK 中包含了期望收到的下一个字节的序列号。
- 超时重传:发送方发送一个数据包后会启动一个定时器。如果在定时器超时前没有收到对应的 ACK,发送方就认为该数据包丢失,会重新发送它。
- 快速重传:如果发送方连续收到 3 个相同的 ACK(意味着接收方收到了乱序的包,一直在重复索要某个丢失的包),发送方会立即重传那个被认为丢失的数据包,而不必等待超时。
- 序列号和确认号:每个字节的数据都被赋予一个唯一的序列号。确认号告诉发送方“我已经成功收到了确认号之前的所有数据,期望下一个收到的数据序列号是这个确认号”。这解决了数据包乱序和确认丢失的问题。
- 校验和:TCP头部和数据部分都包含一个校验和。接收方会计算校验和,如果与报文中的校验和不匹配,则丢弃该数据包。发送方会因为收不到 ACK 而触发重传,从而解决了数据错误的问题。
- 流量控制:使用滑动窗口协议来实现。接收方通过 TCP 头部的“窗口大小”字段告诉发送方自己还有多少缓冲区可以接收数据。这防止了发送方发送数据过快,导致接收方缓冲区溢出,从而解决了数据淹没的问题。
- 拥塞控制:通过一套复杂的算法(如慢启动、拥塞避免、快速恢复)来探测网络当前的承载能力。当发现网络出现拥塞(如丢包)时,会主动降低发送速率,从而减轻网络负担,避免整个网络崩溃。
总结: TCP 通过序列号/确认号确保数据有序、不丢;通过校验和确保数据正确;通过流量控制保护接收方;通过拥塞控制保护网络。其可靠性是这一整套机制协同工作的结果。
7. 三次握手如果改成两次握手会怎样?#
腾讯 TEG 一面
核心问题: 无法防止已失效的连接请求报文突然又传送到服务器,从而导致错误和资源浪费。
TCP 三次握手改为两次握手会导致可靠性严重下降,主要引发两个问题:已失效的连接请求和无法同步初始序列号。若只有两次握手,当客户端发送的 SYN 报文因网络延迟而滞留,客户端会重发新 SYN 并建立连接;但延迟的旧 SYN 之后到达服务端时,服务端会误以为是新的连接请求并直接响应,导致服务端资源被无效占用(半连接状态)。此外,两次握手无法确保双方对初始序列号的确认达成一致:服务端无法确认客户端是否收到自己的 SYN-ACK 响应,若该响应丢失,客户端无法感知连接已建立,而服务端却认为连接有效,导致数据单向传输失败。因此,第三次握手的 ACK 是防止历史连接混乱和确保序列号同步的关键。
8. 如果网络条件特别好,能不能两次握手?#
腾讯 TEG 一面
即使在网络条件特别好的环境下,TCP 也不能改为两次握手,原因在于两次握手无法解决历史连接和初始序列号同步确认这两个根本性问题。
首先,历史连接问题无法避免。假设客户端发送了一个 SYN 报文后由于某种原因(如应用层重启)决定放弃连接,但该 SYN 报文因网络延迟稍后才到达服务器。在两次握手模型下,服务器收到后会立即回复 SYN-ACK 并认为连接已建立,分配资源并等待数据传输。然而客户端早已放弃这个连接,不会发送数据,导致服务器资源被长期无效占用(直到超时)。三次握手中的第三次 ACK 是关键:客户端通过它来确认本次连接的有效性,若连接已失效(如收到旧的 SYN-ACK),客户端会发送 RST 复位报文来终止服务端的无效连接。
其次,序列号同步的可靠性无法保证。TCP 依赖序列号来保证数据有序性和可靠性,而初始序列号(ISN)的同步需要双方确认。在两次握手中,服务器无法确认客户端是否成功接收到了自己发送的 SYN-ACK(包含服务器的 ISN)。如果这个 SYN-ACK 丢失,客户端根本不知道服务器已准备就绪,而服务器却认为连接已建立并开始等待数据,此时双方状态不一致。第三次握手的 ACK 正式确认了双方对初始序列号的认可,确保了连接状态的双向同步。
因此,即使网络完美无缺,两次握手在协议设计层面仍存在本质缺陷,无法保证连接的可靠性和一致性。TCP 的三次握手是基于逻辑必要性而非网络质量的设计。第三次握手的开销(一个 ACK 包)极小,与它所带来的连接可靠性保障相比,是完全可以接受的。为了节省这微不足道的开销而引入巨大的连接混乱风险,是绝对不值得的。
9. UPD 传输的数据是不是一个完整报文#
网易游戏一面
- UDP 是面向报文的传输协议,它以报文(Datagram)为单位进行传输,不分割也不合并。
- 应用层交给 UDP 一个报文,UDP 添加头部后交给 IP 层发送。接收端收到的数据也一定是一个完整的报文(不会被分片成多个)。
- 如果报文超过网络传输路径的最大传输单元(MTU),则 IP 层(非 UDP)会对报文分片传输,并在接收端自动重组后再交给 UDP。
10. UDP 如何保证传输效率又不丢包#
网易游戏一面
UDP 本身不保证可靠性,也就是说它不会主动处理丢包或重传。
- 如果应用层需要在 UDP 基础上实现高效可靠传输,需要自己实现:
- ACK 应答机制(确认重传)
- 序列号机制(识别丢包、乱序)
- 流量控制、拥塞控制
- 实践中,比如实时音视频通话、游戏等场景,更倾向于使用 UDP,偶尔丢包可接受,但延迟必须低,可靠性可以通过应用层算法进行补偿(如丢帧插值、快速重传策略)。
因此 UDP 本身仅保证效率,可靠性需要应用层自己设计补偿机制。
11. TCP 和 UDP 粘包分包,怎么解决?#
网易游戏一面
粘包

分包/半包

粘包、分包问题的原因:
- TCP 是面向流的协议,没有报文边界的概念,数据会合并、分割。
- UDP 是面向报文的协议,有天然的报文边界,不存在粘包问题,但仍可能因报文超过 MTU 而发生分片(IP 层分片重组)。
TCP 解决粘包分包的常见方案:
- 长度字段法:报文头部增加一个字段表示当前报文长度,接收方按长度拆分数据。
- 分隔符法:在报文末尾增加特殊分隔符标记报文结束(如 HTTP 协议中
\r\n)。 - 固定长度法:每个消息长度固定。
- 协议约定:使用标准协议(如 HTTP、Protobuf)明确消息结构。
UDP 是否有粘包问题:
- UDP 天然不存在粘包问题,但应保证数据小于 MTU,避免 IP 分片。
- 即使数据报被 IP 层分片,在 UDP 层也会被重组好再交给应用层。
这是一个非常经典的网络编程问题,接下来我会用清晰易懂的方式解释 TCP 粘包和分包。
首先要理解最关键的一点:TCP 是面向字节流的协议,它不关心应用层数据的边界。
想象一下 TCP 连接就像一根水管,发送方不断地往里面倒水(数据),接收方从另一端接水。TCP 保证水的顺序是对的,并且不丢失,但它不保证你每次接起来的一瓢水,正好是发送方每次倒进去的那一壶。你可能一次接到两壶水(粘包),也可能只接到半壶水(分包)。
(1) TCP 粘包#
定义:指发送方发送的多个数据包,在接收方接收时被“粘”在了一起,变成一个大的数据包。
通俗比喻:发送方分别发送了“Hello”和“World”两个包。接收方一次读取可能收到的是“HelloWorld”,两个包粘在了一起。
产生原因:
- Nagle 算法:为了优化网络效率,TCP 默认会使用 Nagle 算法。该算法会将多个小的、发送间隔短的数据包合并成一个大的数据包再发送,从而减少网络上的小包数量。
- 接收方缓冲区积累:发送方发送数据包的速度快于接收方应用层读取数据的速度,导致多个数据包在接收方的缓冲区中堆积在一起,当应用层一次读取足够大的缓冲区时,就把多个包读出来了。
发送方:
[Packet A] [Packet B] [Packet C]
| | |
v v v
TCP 发送流: |--A--|--B--|--C--|
|
v
接收方缓冲区: |-----A+B+C-----| <- 应用一次读取到了合并的数据plaintext(2) TCP 分包#
定义:指发送方发送的一个数据包,在接收方接收时被拆分成多个数据包接收。
通俗比喻:发送方发送了一个“HelloWorld”包。接收方可能第一次读取到“Hel”,第二次读取到“loWo”,第三次读取到“rld”。
产生原因:
- MSS 限制:TCP 有一个最大报文段长度。如果应用层下发的数据包大小超过了 MSS,TCP 层在发送前必须对这个数据包进行拆分。
- MTU 限制:在数据链路层,网络接口有一个最大传输单元。如果 IP 数据包(包含 TCP 头和数据)超过了 MTU,它也会被分片传输。虽然这对 TCP 是透明的,但本质也是一种分包。
- 接收方缓冲区不足:当接收方的 TCP 缓冲区大小小于来的数据包时,TCP 只会将缓冲区满的部分数据交付给应用层,剩下的数据要等应用层读取腾出空间后再继续交付。
发送方:
[Packet A (很大)]
|
v
TCP 发送流: |--A1--|--A2--|--A3--|
|
v
接收方缓冲区: |--A1--| <- 应用第一次读取
然后: |--A2--| <- 应用第二次读取
然后: |--A3--| <- 应用第三次读取plaintext(3) 为什么 UDP 没有粘包和分包问题?#
作为对比,UDP 是面向数据报的协议。
- 🔥保护消息边界:UDP 每次发送都是一个完整的、独立的数据报。接收方每次接收也必须以一个完整的 UDP 数据报为单位。即使数据报被 IP 层分片,在 UDP 层也会被重组好再交给应用层。
- 要么收全,要么丢包:如果接收方的缓冲区放不下一个完整的 UDP 数据报,这个数据报就会被直接丢弃。
所以,对于 UDP:
- 发送多少次
sendto,接收方就需要多少次recvfrom。 - 每次
recvfrom得到的数据,必然是且仅是一次sendto发送的完整数据。
(4) 如何解决粘包和分包问题?#
既然 TCP 本身不维护边界,就需要应用层自己来定义通信协议,以便能从字节流中正确地拆分出原始的数据包。
常见的解决方案有:
1. 定长消息#
每个数据包都固定长度。例如,规定每个包都是 100 字节。如果发送的数据不足 100 字节,就用空格或 0x00 填充。
- 优点:处理简单。
- 缺点:浪费带宽,不灵活。
2. 分隔符#
在每个数据包的结尾加上一个特殊的分隔符,例如换行符 \n。接收方通过这个分隔符来切分数据流。HTTP 协议的头部分就是用 \r\n 来分隔的。
- 优点:简单直观。
- 缺点:如果消息体本身包含分隔符,需要转义处理,增加复杂性。
3. 长度前缀(最常用、最标准的方法)#
在数据包的首部加上一个固定长度的字段,用来表示包体的长度。
处理流程:
-
应用层协议定义一个头部,比如前 4 个字节 (
uint32_t) 表示包体长度。 -
发送时,先计算包体长度,写入头部,再写入包体数据。
-
接收时:
a. 先读取固定长度的头部(例如 4 字节)。
b. 解析头部,得到后面包体的真实长度 N。
c. 继续从缓冲区读取至少 N 个字节,这样就得到了一个完整的数据包。
应用层数据包结构:
[ 4字节长度头 (0x00000005) ] [ 5字节包体 ("Hello") ]plaintext(5) 为什么要解决 TCP 粘包和分包/半包问题?#
解决粘包和分包问题根本目的是为了能让接收方正确地、无歧义地还原出发送方发出的原始业务消息。
如果不解决,会导致通信完全无法正常进行。
1. 业务逻辑的完整性被破坏#
应用程序是基于“消息”或“请求”来工作的。每个数据包通常代表一个完整的业务指令。
- 例子:一个在线游戏,客户端发送了两个指令:“攻击A”和“使用药水”。
- 粘包后果:服务器可能一次性收到“攻击A使用药水”,它无法理解这个合并的字符串是什么指令,导致逻辑错误。
- 分包后果:服务器先收到“攻击”,它不知道要攻击谁,等下一个包“A”到来时,逻辑已经混乱。
不解决的后果:应用程序无法理解对方在说什么,业务逻辑无法执行。
2. 数据解析失败#
很多标准的数据格式(如 JSON, XML)和序列化协议(如 Protobuf)要求一个完整的数据块才能被正确解析。
- 例子:客户端发送了一个 JSON 字符串
{"action": "login", "user": "alice"}。- 粘包后果:接收方可能收到
{"action": "login", "user": "alice"}{"action": "logout", "user": "bob"},这根本不是合法的 JSON,解析器会直接报错。 - 分包后果:接收方第一次只收到
{"action": "login",这同样是一个残缺的、非法的 JSON,解析也会失败。
- 粘包后果:接收方可能收到
不解决的后果:数据反序列化失败,程序可能崩溃或抛出异常。
3. 状态同步混乱#
在客户端-服务器模型中,通信双方都维护着一定的状态。消息的顺序和完整性对状态同步至关重要。
- 例子:金融交易系统,服务器依次发送两条消息:“余额=100元” 和 “扣款20元”。
- 粘包后果:客户端收到“余额=100元扣款20元”,无法解析,不知道当前余额是多少。
- 分包后果:客户端先收到“余额=100”,然后卡住了,它以为余额就是100,但随后收到的“元扣款20元”又无法理解,导致UI显示错误。
不解决的后果:客户端和服务器状态不一致,用户体验受损,甚至造成数据错误。
4. 协议本身无法工作#
很多标准的应用层协议(如 HTTP、FTP、Redis 协议)都内置了解决粘包/分包的机制。如果你自己设计的协议不处理这个问题,那就等同于这个协议是无效的。
- 以 HTTP 为例:
- 它的“长度前缀”就是
Content-Length头。浏览器读取到 HTTP 头部,发现Content-Length: 1024,就知道在头部之后,还需要读取 1024 字节才能得到一个完整的响应体。 - 它的“分隔符”就是
\r\n。通过读取头部的\r\n\r\n来分隔头部和主体。
- 它的“长度前缀”就是
如果 HTTP 不解决粘包/分包,网页将永远无法正常加载。
(6) 总结#
| 特性 | TCP(流式协议) | UDP(数据报协议) |
|---|---|---|
| 数据边界 | 不保护,可能导致粘包和分包 | 保护,每次接收都是一个完整的数据报 |
| 解决方案 | 应用层协议解决,如长度前缀法 | 无需额外处理 |
理解 TCP 粘包和分包是进行可靠网络编程的基础。在实践中,几乎总是使用“长度前缀”的方法来设计应用层协议,例如 Google 的 Protocol Buffers 和许多消息队列的通信协议都采用这种方式。
12. TCP 为什么是四次挥手#
网易游戏一面
TCP 四次挥手的原因:TCP 连接是全双工通信,数据在两个方向上可以独立传输,需要分别关闭。
四次挥手的过程:
主动方 被动方
|------- FIN ------>|
| |
|<------ ACK -------|
| |
| |(被动方可能仍有数据要发送)
|<------ FIN -------|
| |
|------- ACK ------>|lua- 第一次挥手:主动关闭方发出
FIN,表示我方数据发送完毕。 - 第二次挥手:被动方发送
ACK,确认收到对方的关闭请求,但此时被动方可能还有数据未发送完。 - 第三次挥手:被动方发完数据后再发送
FIN,通知主动方自己数据也发送完毕。 - 第四次挥手:主动方再发送
ACK,确认收到被动方的关闭请求。
13. 在浏览器中访问一个 http 服务器,这里面会经过哪些协议?#
快手一面
当你在浏览器里输入一个 http://... 地址访问服务器时,整个过程会涉及到一系列分层的协议,每一层负责不同的功能。整体遵循的是 TCP/IP 协议栈,从应用层到物理层逐层完成通信。可以这样理解:
- 在最顶层,浏览器使用 HTTP 协议 发出请求。HTTP 是应用层协议,规定了请求报文和响应报文的格式,例如
GET /index.html HTTP/1.1。如果用的是 HTTPS,那么在 HTTP 之前会先进行 TLS/SSL 握手,加密后才传输数据。 - HTTP 请求需要依赖 TCP 协议 提供可靠的字节流传输。浏览器和服务器之间首先通过三次握手建立 TCP 连接,之后 HTTP 报文就会作为 TCP 的数据部分发送。TCP 协议保证了数据的有序性和完整性,丢包时会自动重传。
- TCP 连接又要通过 IP 协议 来实现跨主机的数据包传递。IP 协议定义了地址和路由机制,确保数据从浏览器所在的主机能够正确送达目标服务器的 IP 地址。如果访问的是一个域名,浏览器会先用 DNS 协议 把域名解析成 IP 地址,这一步也是不可或缺的。
- 在链路上传输时,IP 报文会被封装在 以太网协议(或 Wi-Fi 协议) 的帧里,通过物理层的比特流传输。为了更高效地在局域网找到目标设备,还会用到 ARP 协议 来把 IP 地址解析为 MAC 地址。
所以整体过程是:
- DNS:把域名解析为服务器 IP。
- ARP(如果需要):在局域网内解析 IP 到 MAC。
- TCP:通过三次握手建立连接。
- HTTP:浏览器发出请求,服务器返回响应。
- IP/以太网/物理层:底层负责把数据包真正送到服务器。
如果是 https://,还要加上 TLS/SSL 层,建立加密通道后才开始传输 HTTP 数据。
14. 为什么不直接用 tcp 协议,还需要用 http 协议?#
快手一面
这是个非常经典的问题。我们完全可以用 TCP 在两台机器之间收发字节流,但光靠 TCP,还缺少很多“应用层语义”。HTTP 就是为了解决这些问题而存在的。
首先,TCP 只是传输层协议。它提供的功能是:建立可靠的、双向的字节流通道,保证数据顺序不乱、不丢、不重。它并不知道字节流里是什么内容,也没有规定数据要如何划分、如何解释。你完全可以在 TCP 上自己定义一套格式来交流,但如果没有标准,不同的应用之间就无法互通。
HTTP 是应用层协议,它定义了浏览器和服务器之间通信的统一格式。比如:
- 如何表示“我要获取某个资源”(GET 请求);
- 如何附带参数、发送表单或文件(POST 请求);
- 如何在请求头里写出数据类型、压缩方式、认证信息(Header);
- 如何让服务器告诉客户端返回的状态(200 OK、404 Not Found)。
有了这些约定,不同厂商的浏览器、服务器都能在一个共同的语义上交流,而不仅仅是传输字节。
如果我们直接用 TCP,理论上也能实现浏览器和服务器交互,但必须自己再设计一整套协议,比如:如何表示一个请求的开始和结束,怎么传递参数,怎么返回错误码,怎么处理长连接。这些 HTTP 已经帮我们定义好了,相当于是建立在 TCP 之上的一个通用“语言”。
再进一步看,HTTP 还解决了 扩展性和兼容性 的问题。随着需求变化,它不断引入新特性:从 HTTP/1.0 的简单请求,到 HTTP/1.1 的持久连接、管道化,再到 HTTP/2 的多路复用、头部压缩,HTTP/3 甚至换到了 QUIC(基于 UDP)。如果只用裸 TCP,这些机制都需要自己重新造轮子。
15. 如何查看 HTTP 丢包的情况?#
HTTP 基于 TCP,因此 HTTP 丢包实质是网络层或传输层的问题。排查步骤如下:
- 使用
ping命令:检测到目标服务器的基本连通性和网络延迟,看是否有包丢失(packet loss) - 使用
traceroute(Windows 是tracert):追踪数据包经过的路径,定位在哪个网络节点出现丢包或高延迟 - 使用
curl -v或wget:详细输出 HTTP 请求/响应过程,观察连接建立、SSL握手、数据传输各阶段是否超时或失败 - 专业工具:使用
tcpdump抓包,然后用 Wireshark 分析,可以精确看到 TCP 重传(Retransmission),这是判断丢包的最直接证据
16. GET 请求和 POST 请求二者有什么区别?分别什么场景下使用?#
根本区别:
- GET 是幂等的(多次执行效果相同)且安全的(不修改服务器数据),主要设计用于获取资源。
- POST 是非幂等的,主要设计用于提交数据并可能修改服务器状态。
具体差异:
- 参数位置:GET 参数在 URL 查询字符串中,POST 在请求体内。
- 安全性:GET 参数暴露在 URL 和浏览器历史中,不适合传敏感信息。
- 数据长度:GET 受 URL 长度限制(通常2KB-8KB),POST 可传输更大数据。
- 缓存与书签:GET 可被缓存、可收藏为书签,POST 一般不会。
使用场景:
- GET 用于搜索、筛选、跳转页面等获取数据的操作。
- POST 用于登录、注册、下单、支付等修改数据的操作。
17. 在淘宝下浏览,cookie 是在淘宝域这边,那如果从淘宝跳到支付宝,怎么处理中间这些过程呢?#
非常好、非常核心的一个问题!这个问题涉及到单点登录(SSO, Single Sign-On)的核心流程。下面我以一个清晰、分步骤的方式为你解释整个处理过程。
核心问题:为什么淘宝的Cookie不能直接用于支付宝?#
简单回答:浏览器基于安全原因实施的“同源策略”(Same-origin policy)。
- 淘宝的域名可能是
taobao.com。 - 支付宝的域名是
alipay.com。
它们的域名不同,因此浏览器在向支付宝的服务器发送请求时,绝不会携带淘宝域名下的Cookie。这是至关重要的安全机制,防止恶意网站窃取你在其他网站的登录状态。
解决方案:基于信任的“授权”流程#
既然Cookie不能共享,淘宝就需要用一种“信物”告诉支付宝:“这个用户是我这儿来的,他已经在我这儿登录了,你也要让他登录。” 这个流程的核心是令牌(Token) 的传递和验证。
整个过程可以概括为以下几个关键步骤:
第1步:用户点击支付,发起跳转#
用户在淘宝选好商品,点击“付款”按钮。此时,淘宝后端会生成一个临时的、一次性的令牌,我们通常称之为 授权码(Auth Code)。然后,淘宝会将用户浏览器重定向到支付宝的登录/授权页面。
跳转的URL看起来会像这样:
https://auth.alipay.com/authorize?client_id=淘宝在支付宝注册的ID&redirect_uri=淘宝的回调地址&state=一个随机字符串(防CSRF攻击)&auth_code=刚刚生成的临时令牌
关键点: 这个 auth_code 就是淘宝给支付宝的“介绍信”。
第2步:浏览器跳转到支付宝#
用户的浏览器根据上一步的URL,跳转到了支付宝的域名下。此时,浏览器会携带支付宝自己的Cookie(如果用户之前登录过支付宝且Cookie未过期,那么他已经是登录状态)。
第3步:支付宝验证授权码#
支付宝的服务器接收到这个请求后,会做几件事:
- 验证用户登录状态:检查浏览器请求中携带的支付宝Cookie。如果用户已登录,支付宝就知道当前用户是谁。如果未登录,会先引导用户输入账号密码登录。
- 联系淘宝进行验证:支付宝的服务器会在后台(服务器到服务器),通过一个安全的API接口,联系淘宝的服务器。它会说:“我收到了一个授权码
auth_code=xxx,请问这个码是有效的吗?它对应的是哪个用户?”
第4步:淘宝确认授权码有效性#
淘宝的服务器在后台收到支付宝的查询请求后,会:
- 验证这个
auth_code是否是自己刚生成的、是否在有效期内、是否已经被使用过。 - 如果一切有效,淘宝会告诉支付宝:“这个码有效,它对应的用户ID是
TB123456。”
关键点: 这个服务器对服务器的通信是绝对安全的,因为它们之间有预先约定好的身份验证方式(例如使用App Secret),避免了中间人伪造请求。
第5步:支付宝建立本地会话并重定向回淘宝#
支付宝确认了用户身份后:
- 在自己的系统中,为这个用户(TB123456对应的支付宝用户)创建一个支付宝域的登录会话(Session),并生成一个支付宝的令牌(比如叫
alipay_token)。 - 通常,它会通过Set-Cookie头部,将这个会话信息设置为支付宝域名下的Cookie。
- 然后,支付宝将浏览器重定向回第一步中
redirect_uri指定的淘宝回调地址,并附上成功的信号和这个alipay_token。
回跳的URL像这样:
https://www.taobao.com/callback?from=alipay&result=success&alipay_token=支付宝生成的令牌
第6步:淘宝确认最终状态,完成支付流程#
用户的浏览器跳回淘宝的页面。
- 淘宝的后端收到
alipay_token后,会再次在后台向支付宝验证这个令牌的有效性(同样是安全的服务器间通信)。 - 验证通过后,淘宝就知道:“好了,用户已经在支付宝那边登录成功了,支付环节可以继续了。”
- 随后,淘宝会展示支付页面,用户输入密码完成支付。
技术总结与关键点#
| 步骤 | 发生地点 | 关键动作 | 通信方向 |
|---|---|---|---|
| 1. 准备跳转 | 淘宝 | 生成临时 auth_code,拼接跳转URL | 用户浏览器 -> 淘宝 -> 重定向 -> 支付宝 |
| 2. 跳转 | 浏览器 | 携带支付宝Cookie访问支付宝 | 用户浏览器 -> 支付宝 |
| 3. 验证授权码 | 后台 | 支付宝服务器用 auth_code 询问淘宝服务器 | 支付宝服务器 < - > 淘宝服务器 |
| 4. 确认身份 | 后台 | 淘宝服务器告知支付宝用户身份 | 淘宝服务器 -> 支付宝服务器 |
| 5. 建立会话 & 回跳 | 支付宝 | 支付宝设置自身Cookie,带 alipay_token 跳回淘宝 | 用户浏览器 -> 支付宝 -> 重定向 -> 淘宝 |
| 6. 最终确认 | 后台 | 淘宝用 alipay_token 向支付宝确认 | 淘宝服务器 < - > 支付宝服务器 |
核心思想: 通过浏览器重定向传递一次性临时令牌(Auth Code),再通过后端服务器之间的安全通信来验证用户真实身份,最终在目标网站(支付宝)上建立独立的本地会话。
这种模式是OAuth 2.0等标准授权协议的精髓。对于用户来说,如果他已经登录了支付宝,整个过程几乎是无感的,点击“付款”后直接就到了支付界面,实现了流畅的单点登录体验。
18. TCP 为什么“四次挥手”,三次不行?#
关键点:TCP 全双工,两条方向独立关闭。
- 步骤:A 发 FIN → B 回 ACK(A→B 方向关闭);B 还有数据要发,等发完再发 FIN → A 回 ACK。
- 若“三次”把 B 的 ACK 和 FIN 合并,无法覆盖“半关闭继续发送”的常见场景(B 可能还有尾包)。
- TIME_WAIT(最后 ACK 的一方进入):防止旧包影响后续连接,确保对端能重传 FIN 时仍可应答。
