http是什么与https有什么区别
http
定义
http,即超文本传输协议,常被用于在浏览器和网站服务器之间的通信,以明文方式发送内容,不提供任何方式的数据加密。
特点
无状态
HTTP协议无法根据之前的状态来处理本次请求。
灵活
HTTP允许传输任意类型的数据对象。正在传输的类型由
Content-Type
加以标记。
状态码
4xx(客户端错误)
- 404:服务器中不存在请求的资源。
- 403:请求的权限不足。
- 401:身份认证失败,该
用户不存在
。 - 400(bad request):请求存在语法错误,通常是因为请求的方式不符合接口文档规范。
5xx(服务端错误)
- 500:服务器错误,但是未给出具体原因
- 502:上游服务器返回了错误的响应
- 504:上游服务器
响应超时
。
3xx(重定向)
- 304:服务器提示浏览器读取缓存(重定向到缓存)
- 302:临时重定向,第一次请求返回一个临时请求url,第二次请求访问这个临时url,产生两次请求
- 301:永久重定向,第一次请求返回一个永久请求url,第二次请求访问这个永久url,产生两次请求
2xx(请求成功)
200(成功):请求已成功,并返回
响应头
或响应体
201:创建用户成功(由0到1的过程)
204:服务器成功处理了请求,但是没有返回任何内容,
04
通常表示“没有”的概念;通常用于响应DELETE
请求,表示资源已被成功删除,但没有返回具体内容。
HTTP1.0
默认使用短连接
即请求头中的Connection字段的值默认是close,每次请求都需要与服务器建立一个TCP
连接,服务器完成请求处理并响应数据
后立即断开TCP
连接。比如,解析html
文件,当发现文件中存在资源文件的时候,这时候又创建单独的链接,最终导致,一个html
文件的访问,包含了多次的请求和响应,每次请求都需要创建连接、关闭连接。频繁的建立,断开连接,明显造成了性能上的缺陷,导致网络利用率较低,如果需要建立长连接,需要设置一个非标准的Connection
字段
1 | Connection: keep-alive |
也就是说其实http1.0
也能实现长连接,但是需要手动修改请求头。
队头阻塞
由于HTTP1.0规定下一个请求必须在前一个请求响应到达之前才能发送,假设前一个请求响应一直不到达,那么下一个请求就不发送,后面的请求就阻塞了。说简单点就是串行请求。
不支持断点续传
HTTP1.0不支持断点续传功能,每次都会传送全部的页面和数据。如果只需要部分数据就会浪费多余带宽
HTTP1.1
现在浏览器发送请求,默认使用的http版本就是1.1,与HTTP1.0的区别在于:
默认支持长连接
1 | HTTP/1.1 304 Not Modified |
- 即在一个
TCP连接
上,可以传送多个HTTP
请求和响应,减少了建立和关闭连接的消耗和延迟。这样,在加载html
文件的时候,文件中多个请求和响应就可以在一个连接中传输,减少了加载时间。 - 只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。如果某个 HTTP 长连接超过一定时间没有任何数据交互,服务端就会主动断开这个连接。
Pipelining(流水线技术)
- 即可在同一个 TCP 连接里面,客户端可以发起多个请求,只要第一个请求发出去了,不必等其响应,就可以发第二个请求出去,可以减少整体的响应时间。但是服务器端必须按照接收到客户端请求的先后顺序依次回送响应结果,以保证客户端能够区分出每次请求的响应内容,这样也显著地减少了整个下载过程所需要的时间。
- 但是这样其实是存在问题的,因为服务端响应请求的顺序,不一定等于浏览器发送请求的顺序,浏览器无法知道哪个响应,对应哪个请求,默认认为请求发送顺序等于请求响应顺序,所以就存在问题。
- 虽然不用 Pipelining,但浏览器并没有退回到“串行发送”的老路,而是采用了更聪明的方式。
并发连接
- 在 HTTP/1.1 下,浏览器会为同一个域名建立 6~8 个并发连接(不同浏览器略有差异),然后在这些连接上轮流发送请求。
- 并发连接中的每个连接是串行请求的,也就是下一个请求需要等待上一个请求响应后才能发送
- 这些并发连接的目的端口号都是一样的,即80或者443,但是源端口号是不一样的,从而用来区别不同的连接
新的请求方法
添加新的请求方法比如put
、delete
、options
(预检请求),添加了新的请求头比如If-None-Match
,If-Modified-Since
http2.0
多路复用
所有请求都走同一个 TCP 连接
为每个请求和响应分配独立的流(stream),每个流有唯一的流ID,请求和响应通过流ID关联
浏览器可以连续发送请求,服务器可以异步发送响应,浏览器根据流ID正确匹配请求和响应,而不用担心匹配错误的问题
https
https
是基于http
的,在http的基础上使用了TLS/SSL
加密,详见文章hexo博客搭建的一些思考 | 三葉的博客
GET请求和POST请求的区别
用途不同
get请求被用来获取数据,post请求被用来提交数据。
编码方式
GET请求只能进行
url编码
,而POST支持多种编码方式。url编码是将url中不能包含的字符(如汉字,空格,特殊符号等),转换成浏览器和服务器都能识别的格式,通常使用百分号加上2位16进制数来表示每个字符的acsii编码,因此url编码也叫百分号编码。url编码有利于保证url的合法性。
携带信息
GET请求一般把
携带的参数
放在URL
上,不安全,而且url上携带的参数
有长度限制
,而POST请求一般把携带的参数
放在请求体
上,没有长度限制,也相对更为安全。然而,从传输的角度来说,他们都是不安全的,因为
HTTP
在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文。只有使用HTTPS
才能加密安全。
http常用请求头
Accept
:客户端能够接受的响应数据类型1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Authorization
:用于超文本传输协议的认证信息1
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
1
2
3
4
5
6
7
8
9
10
11
12request.interceptors.request.use(
(config)=>{
const token = localStorage.getItem('token')
if(token){
config.headers.Authorization = `Bearer ${token}`
}
return config
},
(err)=>{
return Promise.reject(err)
}
)Cache-Control
: 不仅仅可以在请求中携带,也可以在响应中携带- 在请求中携带:
no-cache
:强制进行协商缓存no-store
:客户端 直接忽略缓存,每次都向服务器发送完整请求,服务器必须返回 200 OK 和完整资源内容,不会返回 304。max-age=<seconds>
:客户端愿意接受一个已经存在的缓存,只要它的年龄不超过指定秒数(愿意接受强缓存,但是有条件)
- 在响应中携带:
no-cache
:允许浏览器缓存资源,但是不设置资源的有效时间,这样就使得浏览器无法进行强缓存,每次都得进行协商缓存。效果就等同于设置max-age=0
no-store
:浏览器不会将响应内容保存到本地缓存中,中间代理(如代理服务器或 CDN)也不会缓存该响应。常见场景包括:敏感数据(如银行账户信息、个人隐私数据)的传输;动态生成的内容(如实时股票价格、聊天记录),这些内容每次请求都可能不同,简单的来说就敏感的内容和动态变化的内容都没有必要缓存max-age=<seconds>
:指定资源被认为新鲜的最大时间长度,在此期间内无需再次验证资源
- 在请求中携带:
Content-Length
:表示请求体的长度Content-Type
:告知服务器请求体的数据类型If-Modified-Since
:在协商缓存中被用来询问服务器是否应该使用缓存。1
If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT //值为上次返回的Last-Modified, 询问自该时间点后资源是否发生改变
If-None-Match
:在协商缓存中被用来询问服务器是否应该使用缓存。1
2If-None-Match: "737060cd8c284d8af7ad3082f209582d"
//值为上次返回的Etag,就是文件内容的哈希值,文件内容改变,这个值就会改变。Cookie
:携带cookie,cookie具体的介绍前往js面试一文中。1
2
3buvid3=57161BE6-C8C6-DA39-581F-E6FCB97E737107226infoc;
b_nut=1708906907;
i-wanna-go-back=-1;//就是这样的键值对格式,一个键值对就是一个cookie,其实每个cookie包含的信息远不止如此
浏览器的缓存策略
浏览器的缓存方式主要分为两大类,强缓存和协商缓存:
强缓存
- 当浏览器请求某个资源的时候,如果浏览器缓存中存在该资源,且没有过期,浏览器不会发送实际请求到服务器,而是直接读取本地缓存,此时响应状态码为
200 (from disk cache)
或200 (from memory cache)
,响应头中还可以看到Age字段,表示缓存的年龄。 - 缓存是否过期是根据缓存的
Expires/Cache-Control
字段来判断的。特点是客户端查看缓存资源的特定字段,来判断是否使用缓存。 Cache-Control
优先级高于expires
,expires
是http1.0的产物,而Cache-Control
是http1.1
的产物,两者同时存在的时候expire
会被Cache-Control
的max-age
覆盖,在不支持http1.1的情况下可能就需要expires
来保持兼容。Cache-Control
的值是一个字符串,形如:"no-cache, must-revalidate, ...."
,类似的关键字还有- no-store:禁用缓存
- private:私有缓存,禁止中间人(比如CDN等代理缓存)
- public:共享缓存,允许中间人缓存
- max-age:资源可以被缓存的最大时间,单位:秒,是一个相对时间,作用偏向于客户端缓存(相比于s-maxage)
- s-maxage:作用与
max-age
类似,但仅适用于共享缓存(如 CDN、代理服务器),若同时设置s-maxage
和max-age
,共享缓存会优先采用s-maxage
,而客户端缓存仍用max-age
。 - must-revalidate:缓存使用陈旧资源时,必需先验证状态
1
2
3
4
5//设置缓存的过期时间,即10s后
//将10s后的日期对象,转化成格林尼治标准时间
res.setHeader('Expires', new Date(new Date().getTime() + 1000 * 10).toGMTString())
//设置缓存有效时间为10s
res.setHeader('Cache-Control','max-age=10')- 当浏览器请求某个资源的时候,如果浏览器缓存中存在该资源,且没有过期,浏览器不会发送实际请求到服务器,而是直接读取本地缓存,此时响应状态码为
协商缓存
是指在缓存中的资源过期后,浏览器通过在请求头中设置
If-Modified-Since
或者If-None-Match
字段,询问服务器是否应该使用缓存;如果服务器发现资源未改变,则响应304
提示浏览器使用缓存,否则响应200
并返回新的资源,以及最近一次
修改该资源的时间即Last-Modified
,或者最新的ETag
值。If-Modified-Since
的值,为上次返回的Last-Modified
值,If-None-Match
的值为上次返回的ETag
值;Last-Modified
表示本地文件最后修改日期;Etag
,就是由文件内容得出的哈希值,文件内容改变,这个值就会改变。协商缓存好比食物的保质期过了,于是询问还能不能吃。

如何理解OSI七层模型
应用层
该层协议定义了应用进程之间的交互规则,包括
DNS
协议,HTTP
协议,电子邮件系统采用的SMTP
协议等。在应用层交互的数据单元,我们称之为报文
表示层
该层提供的服务主要包括
数据压缩
,数据加密
以及数据描述
,使应用程序不必担心在各台计算机中表示和存储的内部格式差异
会话层
会话层就是负责建立、管理和终止表示层实体之间的通信会话
该层提供了数据交换的定界和同步功能,包括了
建立检查点
和恢复方案
的方法传输层
传输层的主要任务是为两台主机
进程之间
的通信提供服务。其中,主要的传输层协议是TCP
和UDP
网络层
负责主机到主机的通信,
ip
地址就工作在这一层,常见的设备比如路由器
。在发送数据时,网络层把传输层产生的报文或用户数据报封装成分组或者包,向下传输到数据链路层。在网络层使用的协议是无连接的网际协议(Internet Protocol)和许多路由协议
数据链路层
物理层
比较重要的就是应用层,传输层,网络层,数据链路层,物理层,对这些模型的解释主要还是偏概念,较难以理解,了解就好。

案例解析

如图所示PC0-PC5
,这6台主机通过交换机Switch0
相连,这6台主机构成一个广播域
,也可以说是一个内部网络
,一个局域网(LAN)
这6台电脑之间的通信不需要经过路由器
,也就是说“不需要联网”。
而Switch0
又和一个路由器Router0
相连,这就把这个内部网络
和互联网
连接了起来,允许这些主机访问其他主机或者服务器,比如B站服务器。
这台路由器常常被叫做默认网关
。路由器会给连接到它的每台主机(也包括自身)动态分配(DHCP,动态主机配置协议)一个私有ip地址,如图所示。
当PC0中的浏览器
要发送一个请求时,这个请求在应用层
,被称作报文
,然后被交给传输层
,传输层
把报文
与源端口
和目标端口(比如80或者443)
封装成段
,交给网络层
,网络层
把段
与源ip地址
和目标ip地址
封装成包

我们知道目标ip
可以通过dns域名解析
得到,而目标端口通常就80(http)或者443(https)【也就是说目标IP和目标端口号都是在应用层指定的】,而源ip地址
则是路由器分配给我们的私有ip地址
,那源端口
是如何确定的呢:
- 浏览器通常使用HTTP或HTTPS协议进行通信,而这些协议一般基于TCP。
- 在建立TCP连接时,需要一个四元组来唯一标识一条连接:源IP地址、源端口、目的IP地址和目的端口。
- 当你发起一个HTTP(S)请求时,浏览器会调用操作系统的API,来创建一个新的TCP连接。
- 此时,操作系统会在本地未被占用的
高端口
(通常是1024到65535之间,这是因为低于1024的端口被视为知名端口,通常保留给系统服务和特定应用程序使用)中选择一个作为源端口。 - 这些由操作系统,为客户端应用(例如:浏览器)自动分配的端口,被称为“临时”或“动态”端口。
- 每次发起新的请求,如果需要建立新的TCP连接,则可能会分配不同的
源端口
,尽管对于同一目标服务器的并发请求,它们可能共享相同的源IP地址
,目的IP地址
,端口
,但通过不同的源端口
加以区分。 - 在一个已建立的TCP连接期间,源端口在整个会话期间保持不变。只有当连接关闭后,该端口才可被重新分配用于其他连接。
而源mac地址
就是自己电脑的mac地址,至此,我们还需要知道这个目标ip
地址,对应的mac
地址,才能封装成帧
,才能在数据链路层
的交换机上进行转发。
那该如何获得目标ip对应的mac地址呢?
我们先查看本地主机的arp缓存表
,查找这个ip地址
对应的mac地址
,如果缓存表中没有对应的记录,我们就需要进行arp广播
我们将目标mac地址指定为”广播mac地址”,封装得到到一个arp广播帧,广播到本地所有主机。

如果目标ip地址
在本地网络(也就是说目标ip地址是私有ip),目标ip地址
对应的主机
在收到arp广播帧
后,拆分帧为包,检查目标ip,发现确实是发给自己的帧,于是会返回自己的MAC地址
(单播,因为广播帧中包含了源mac地址和源ip地址)给PC0
,然后PC0
知道目标ip对应的mac地址后,把数据包封装成帧
,通过交换机转发给目标主机即可。
在本地开发环境中,当你使用脚手架工具(如Vue CLI、Create React App等)启动一个本地服务器时,通常会看到两个访问地址:一个是
localhost(127.0.0.1)
,另一个是包含具体IP地址的形式,这里的具体IP地址,通常是你的计算机在本地网络中的私有IP地址,私有ip地址可以用来访问本地网络中的主机
如果目标ip地址
不在本地网络(通常情况下),广播arp请求则无响应
,所有本地主机都表示:”这不是在询问我的mac地址捏”,此时修改广播帧的目标ip为默认网关ip,再发送一次arp广播(此时的目标mac是广播mac,目标ip是默认网关ip)

默认网关,即路由器
收到arp广播帧
后,发现是在询问自己的MAC地址(目标ip=默认网关ip),并记住客户端MAC地址(源mac)
与客户端ip地址(源ip)
的对应关系(存储在arp缓存),并返回自己的MAC地址(单播),然后主机PC0
就能将数据封装成帧
,转发给默认网关

强调一下最终转发给默认网关
的帧的细节:
- PCO转发给默认网关的帧,的目标IP地址,就是真正的目标ip地址,比如B站服务器的IP地址,而不是默认网关ip地址
- PCO转发给默认网关的帧,的目标mac地址,是默认网关的mac地址,而不是直接设置为最终目标服务器的MAC地址,实际上,对于局域网外的目标服务器,我们也没有办法直接获得其MAC地址。
- 默认网关,即路由器,在接收到转发的
帧
,解封帧
成包
,替换包中的源ip地址(私有ip地址),为路由器对应的公有ip地址 - 路由器还会给这个私有ip地址,分配一个对外端口号,替换源端口号,并记录对应关系(NAT)
有了对外端口号,就能实现公有IP地址
到私有ip地址
的1对多
映射,有人肯能会问,为什么还要替换端口号呢?直接有原来的源端口号来标识不同的私有ip不行吗?确实不行,因为不同的私有ip(不同的主机),可以使用相同的端口号。

路由器再根据包里的目标ip地址
,进行路由转发
,服务器所在的默认网关,如果知道这个目标ip地址
对应的是那台服务器(知道目标ip地址对应的服务器mac地址),则把数据包
封装成帧
,经交换机
转发给对应的服务器,如果不知道则进行arp广播
。
对应的服务器收到帧
后进行逐层解封,根据目标MAC地址
确认这个帧是发给自己的,根据目标ip地址
确认这个包是发给自己的,根据目标端口,确定要与哪个应用程序交互,再把报文
交给这个应用程序处理。

详细参考:互联网数据传输原理 |OSI七层网络参考模型_哔哩哔哩_bilibili
如何理解TCP/IP协议

TCP/IP协议不仅仅指的是TCP
和IP
两个协议,而是指一个由FTP
、SMTP
、TCP
、UDP
、IP
等协议构成的协议簇,
只是因为在TCP/IP
协议中TCP
协议和IP
协议最具代表性,所以通称为TCP/IP协议簇。
如何理解UDP 和 TCP?
TCP
TCP(Transmission Control Protocol,传输控制协议),是一种可靠的、面向字节流的通信协议,即将应用层报文看成一串无结构的字节流,无论应用层交付过来多少报文,都视为一连串的字节流,随后分解为多个TCP报文段
后交给网络层传输。

TCP报文段
我们知道TCP协议的特点就是可靠传输,流量控制和拥塞控制

TCP的头部大小至少为5*4 = 20字节
其中的端口号的作用不必多说,用来确定源端口号和目标端口号,建立进程之间的通信
序列号(seq)用于标记数据部分的第一个字节在原始字节流中的位置。有了序列号,接收端才能对接收到的报文段进行重新排序,组装。还可以判断哪些报文段是重复的,直接丢弃 。
确认应答号(ack),等于对应的请求报文的序列号+数据长度(数据长度等于TCP报文长度 - 首部长度),在响应的报文段中携带ack,就表示编号为ack之前的字节都接收到了,使用的是累计确认。
如果发送端发现,某条数据报长时间没有得到确认,就会重传该条数据报,有了确认应答号,就能知道某个数据段是否被成功接收。
TCP的可靠传输很大程度上依赖着序列号和确认应答号
窗口大小指的是接收窗口(rwnd)大小。它表示从当前确认号(ack)开始,接收方愿意接受的数据量。这个值由接收方根据其当前的接收能力和缓冲区可用空间设定,并通过每个ACK消息报告给发送方,从而实现流量控制
补充:拥塞控制
TCP还有的一个功能是拥塞控制。拥塞窗口并不直接出现在TCP头部中,它是发送方内部维护的一个状态变量,用来估计网络的拥塞状况并据此调整发送速率。发送方根据一系列规则和算法,如慢启动、拥塞避免、快速重传(3ack机制,当收到比期望的序列号大的报文段,就发送一个ack给接收端,告诉接收端自己期望的是哪个报文段,当接收端在发现超时之前,就能重传丢失的报文段,所以叫做快速重传)和快速恢复等机制,动态调整cwnd的大小,以尝试最大化吞吐量的同时避免网络拥塞。

分析上述图:
- 慢开始指的是起始的拥塞窗口很小(大小为1),但是增长速度是很快的
- 达到慢开始门限的时候,进行拥塞避免,每个RTT,拥塞窗口的大小+1,增长速度慢
- 当发生网络超时的时候(也就是发送端超过一定时间没有接收到ack),说明网络比较拥挤,于是转为慢开始,并将慢开始门限设置为超时的时候的拥塞窗口大小的一半
- 当接收到3个ack的时候,执行快恢复算法
3-ACK
要是等到超时的时候,发送端再重传,未免效率较低,有没有一种方法,能在超时事件发生之前,就提醒发送端重传报文段呢?
每当比期望序号大的失序报文段
到达时,发送一个冗余ACK,指明下一个期待
的字节的序号
比如,发送方已发送1,2,3,4,5报文段
接收方收到1,返回给1的确认(确认号为报文段2的第一个字节,表示期望收到第二个报文段),但2号报文段丢失。
接收方收到3,仍返回给1的确认(确认号为报文段2的第一个字节)
接收方收到4,仍返回给1的确认(确认号为报文段2的第一个字节)
接收方收到5,仍返回给1的确认(确认号为报文段2的第一个字节)
发送方收到3个对于报文段1的冗余ACK
,认为2报文段丢失,重传2号报文段(快速重传)
连接建立和断开
三次握手
三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包,主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。
过程如下:
- 第一次握手:客户端给服务端发一个
SYN(同步)报文
,并指明客户端的初始化序列号seq
,此时客户端处于SYN_SENT
状态seq
代表每个报文的序列号,在每个报文中都需要携带,用来区分发送的不同报文。它的值等于当前报文段中的数据的第一个字节在原始字节流中的编号。- SYN这种大写的字段的值(还比如ACK),只有2个值,0表示关闭,1表示开启,SYN=1表示请求开启同步
- 第二次握手:服务器收到客户端的
SYN
报文之后,会以自己的SYN 报文
作为应答,为了确认客户端的 SYN,将客户端的seq+1
作为ack
的值(虽然没携带数据,但是规定就是ack=seq+1),此时服务器处于SYN_RCVD
的状态- ACK字段的值只有0和1,ACK=1表示确认的意思,SYN=1和ACK=1一起使用表示确认开启同步
- ack用于响应报文中,它的值等于相应的请求报文的序列号+1,只有开启了ACK,才能使用ack字段
- ACK=1和ack总是同时出现的,只有当ACK=1的时候,ack才有效。
- 第三次握手:客户端收到
SYN 报文
之后,会发送一个ACK 报文
,值为服务器的seq+1
。此时客户端处于ESTABLISHED
状态。服务器收到 ACK 报文之后,也处于ESTABLISHED
状态,此时,双方已建立起了连接。

四次挥手

FIN字段也是大写的字段,值也是只有0和1
UDP
- UDP(User Datagram Protocol,用户数据报协议),是一个简单的面向数据报的通信协议,即对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部,形成UDP数据报后,就交给了下面的网络层。
- UDP是无连接的,不需要和接收方打招呼建立连接,而是直接发送数据。这么设计的好处就是,可以实现一对多的数据传输,只需要在网络层中,将目标IP地址设计为广播地址即可,现实生活中的直播技术使用的就是UDP。
- UDP的首部很小,只占用了8个字节(4*2=8)
- 从UDP数据报中可以看出,UDP并没有确认机制(没有确认应答号ack),所以传输是不可靠的
区别
这2个协议都是工作在传输层
的协议,都会给应用层交付过来的报文,添加端口号(源端口,目标端口),把报文封装成TCP报文段或者UDP数据报。下面说说区别
连接建立
TCP是面向连接的,即发送数据之前需进行三次握手建立连接,而UDP不需要建立连接,直接发送数据。所以TCP只能实现1对1数据传输,而UDP可以实现1对多数据传输,因为TCP连接限制了目标IP只能对应一台主机。
报文分段
TCP将应用层交付过来的数据报视为字节流,如果递过来的
报文
很大,TCP会将报文分段,并给这些报文段
编号(seq)。而UDP则始终把
应用层
交付过来的报文当做一个整体
,不进行分片,直接加上头部封装成UDP数据报,所以UDP是面向用户数据报的。
首部开销
TCP报文段
首部内容更多,首部更大,至少有20字节;而UDP数据报
首部开销小,只有8个字节。
可靠传输,流量控制与拥塞控制
TCP是可靠的,UDP是不可靠的(尽全力交付),因为TCP头部中包含确认应答号,也就是说,接收方可以根据确认应答号,判断哪些tcp报文段接收成功,哪些丢失了,并重传丢失的数据报。
TCP具有流量控制,因为TCP报文段的头部中包含接收窗口大小,接收端可以在发送ack的时候,指定接收窗口的大小,表示从当前确认号开始,接收方愿意接受的数据量。而UDP数据报的头部中不包含接收窗口,所以不具备流量控制的作用
TCP具有拥塞控制,而UDP没有,UDP不会自动调整发送速率以响应网络状况的变化,存在这种区别的原因,主要还是这2种协议的设计理念不同
总结
TCP 应用场景适用于对效率要求低
,对准确性要求高
或者要求有连接的场景,而UDP 适用场景为对效率要求高
,对准确性要求低
的场景,各有优缺。
关于TCP和UDP的具体介绍,参考本博客内的《计算机网络》一文
IP
什么是IP
IP(Internet Protocol),也叫互联网协议。它定义了如何将数据分割成数据包、地址编码、路由选择以及如何重新组装这些数据包以恢复原始信息。IP 提供的是无连接的服务,这意味着每个数据包独立处理,不需要事先建立专用的通信通道。这也意味着 IP 不保证数据包按顺序到达或不会丢失。
什么是IP地址
IP地址是IP (Internet Protocol,互联网协议)为每个连接到网络
的设备分配的一个逻辑地址,用来唯一标识每一台设备,这使得设备之间能够互相识别并进行通信。IP地址的格式可分为ipv4
(32位),ipv6
(128位 )。
IPv4格式IP地址分类
ipv4格式的IP地址被分为私有IP地址
和公有IP地址
,私有ip地址的出现,主要是为了解决ipv4格式的IP地址数量不足的问题。
私有ip地址只能在局域网
中通信,而公有ip地址才能在互联网
通信,一个公有ip地址
常常对应多个私有ip地址
。
路由器内部的DHCP
会自动为设备分配私有ip地址
,而公有ip地址
由运营商分配。
ipv4格式的地址另一种常见的分类方式是分为A,B,C
四大类
A类
对应的子网掩码为
255.0.0.0
,即前8位用来表示网络号
,后24位用来表示主机号
,前一位比特必须是0
(限定前几位比特是为了能快速识别是哪类IP地址),表示 IP 地址范围为0.0.0.0
到127.255.255.255
B类
对应的子网掩码为
255.255.0.0
,即前16位用来表示网络号
,后16位用来表示主机号
,前两位比特必须是10
,表示 IP 地址范围为128.0.0.0
到191.255.255.255
C类
对应的子网掩码为
255.255.255.0
,即前24位用来表示网络号
,后8位用来表示主机号
,前三位比特必须是110
,最为常见,表示 IP 地址范围为192.0.0.0
到223.255.255.255
特殊地址
127.0.0.0
对任何ip地址的访问都会访问这个ip地址
广播地址
主机号全为
1
的地址。网络地址
主机号全为
0
的地址,不能用来标识某一台主机。
DNS协议
DNS(Domain Names System),域名系统,是互联网一项服务,是把域名
转换成对应的IP地址
的服务器。
什么是域名
域名可以理解为给ip地址起的别名,方便记忆。域名一般由三部分构成,由.
连接,从右到左分别是顶级域名,权威域名,本地域名。
域名查询方式
递归查询
如果 A 请求 B,那么 B 作为请求的接收者一定要给 A 想要的答案,可以理解为帮人帮到底,但是这样对B服务器(域名服务器)的压力就很大,域名服务器不返回下级域名服务器ip地址,而是负责的帮忙询问,只返回普通服务器ip地址。
迭代查询
如果接收者 B 没有请求者 A 所需要的准确内容,接收者 B 将告诉请求者 A,如何去获得这个内容,但是自己并不去发出请求。
域名缓存
计算机中DNS
的记录分成了两种缓存方式:
- 浏览器缓存:浏览器在获取网站域名的实际 IP 地址后会对其进行缓存,减少网络请求的损耗。
- 操作系统缓存:操作系统的缓存其实是用户自己配置的
hosts
文件
DNS解析过程
- 当我们访问一个网站,就需要通过DNS解析获取它的ip地址,首先搜索浏览器的 DNS 缓存,缓存中维护一张
域名
与IP 地址
的对应表 - 若缓存没有命中,则继续搜索操作系统的 DNS 缓存
- 若操作系统缓存仍然没有命中,操作系统将向本地域名服务器(默认域名服务器)发送一次DNS查询请求,询问这个域名的ip地址,本地域名服务器采用
递归查询
自己的 DNS 缓存(因为域名是多级结构嘛),查找成功则返回结果。 - 若本地域名服务器的 DNS 缓存没有命中,则本地域名服务器向上级域名服务器进行迭代查询
- 首先本地域名服务器向根域名服务器发起请求,询问
根域名服务器
,返回顶级域名服务器的ip地址
到本地服务器 - 本地域名服务器拿到这个顶级域名服务器的ip地址后,就向其发起请求,返回权威域名服务器的
ip地址
- 本地域名服务器根据权威域名服务器的ip地址向其发起请求,最终得到该域名对应的
IP 地址
。
- 首先本地域名服务器向根域名服务器发起请求,询问
本地域名服务器
将得到的 IP 地址返回给操作系统
,同时自身将 IP 地址缓存
操作系统
将 IP 地址返回给浏览器
,同时自身将 IP 地址缓存
- 至此,
浏览器
就得到了域名对应的 IP 地址,并将 IP 地址缓存

域名解析参考:【实操演示】域名DNS解析设置 | 第一次设置域名解析?看这个就明白了 | 什么是域名解析 | 如何设置_哔哩哔哩_bilibili
如何理解CDN
CDN也叫做内容分发网络,通过在多地设置CDN服务器,并存储源服务器
的文件副本,并把请求路由到最近或者最合适
的服务器,从而起到减少网络时延的作用,特点是就近取材,内容缓存
除了减少网络时延,提高网页的加载速度,CDN技术还能解决源服务器宕机导致的服务瘫痪。
CDN在域名解析过程中起作用,配置了cdn加速的域名,解析后返回的不再是固定的源服务器ip
,而是距离请求设备最近的
或者最合适的cdn服务器
的ip。cdn服务器
的选择工作由CDN专用DNS服务器实现。
接入CDN加速后的域名解析流程,拿www.sanye.blog
这个域名举例:
- 先查询本地浏览器DNS缓存,是否有
www.sanye.blog
这个域名对应的ip地址,如果没有,再查询操作系统DNS缓存
- 如果
操作系统DNS缓存
命中,则把对应的ip地址返回给浏览器
,否则操作系统向本地dns服务器
发送一次dns查询请求。 - 本地dns服务器
递归查询
本地记录,如果查找到对应的ip地址则返回给操作系统,否则询问上级服务器
。 - 本地dns服务器进行一系列
迭代查询
,最终询问sanye.blog
权威域名服务器,并获得一条CNAME
记录。 - 这条
CNAME
记录表明www.sanye.blog
指向一个cdn加速域名
。 - 本地DNS服务器自动继续解析这个
加速域名
,这个解析过程涉及到了cdn专用域名服务器
,最终得到一台最合适的cdn服务器的ip地址,返回给操作系统。
cdn专用域名服务器的工作流程:
- 查看用户的
IP 地址
,查表得知地理位置
,找相对最近的边缘节点 - 看用户所在的
运营商网络
,找相同网络的边缘节点 - 检查边缘节点的负载情况,找负载较轻的节点
- 其他,比如节点的“健康状况”、服务能力、带宽、响应时间等
结合上面的因素,得到最合适的cdn服务器(边缘结点)
,然后把这个节点的ip地址返回给本地dns服务器
,本地dns服务器将这个ip地址返回给操作系统,操作系统则把这个ip返回给浏览器,最终用户就能够就近访问CDN
的缓存代理。

其中有两个衡量CDN
服务质量的指标:
- 命中率:用户访问的资源恰好在缓存系统里,可以直接返回给用户,命中次数与所有访问次数之比
- 回源率:缓存里没有,必须用代理的方式回源站取,回源次数与所有访问次数之比。必须指定cdn服务器的
回源地址
,也就是源服务器地址,不然cdn服务器都不知道去哪儿拷贝资源。
命中率 + 回源率 = 1,命中率越高,表明加速效果越好。
CDN原理具体参考:【白话科普】用动画告诉你 CDN是如何工作的 | CDN是什么 | 如何让你的网站网站快速打开 | CDN原理 | 服务器自由_哔哩哔哩_bilibili
地址栏输入 URL 敲下回车后发生了什么
这个问题常常和《如何提高SPA的首屏加载速度》联合考察,提高首屏加载速度的方法,通常在TCP连接建立后考察
所有步骤如下:
- URL解析
- DNS 查询
- 建立TCP 连接
- 发送HTTP 请求
- 响应请求
- 页面渲染
URL解析
判断输入的url是否合法
,不合法则根据关键字进行搜索,合法则对这个URL进行结构分析。
一个合法的URL包括协议
,域名/ip
,端口号
(默认为80或者443),资源路径
,查询参数
等
DNS查询
通过DNS查询获得域名部分对应的ip地址
。
在之前文章中讲过DNS
的查询,这里就不再讲述了。
建立TCP连接
然后再根据得到的ip地址,通过三次握手,与目标服务器建立TCP连接。
在之前文章中讲过TCP连接的建立,这里就不再讲述了
发送http请求
连接建立后,浏览器发送http请求报文
http请求报文
结构从上至下依次是:
请求行
包括
请求方法
,资源路径
,协议以及协议版本号
请求头
http请求
常见请求头
再前面有介绍。请求体
用来存放
请求携带的数据

服务端响应请求
当服务器接收到浏览器的请求之后,就会对请求进行处理,执行一些逻辑操作,响应index.html
文件
http响应报文
结构从上至下依次是:响应行,响应头,响应体,结构其实和请求报文
结构很类似,就不举例说明了。
在服务器响应之后,由于现在http
默认开始长连接keep-alive
,一般只有当页面关闭之后,tcp
连接才会经过四次挥手完成断开。
页面渲染
当浏览器接收响应报文后,会对这个报文进行解析:
- 查看
响应头
的信息,根据不同的指示做对应处理,比如重定向,存储cookie,解压gzip,缓存资源等等 - 特别是查看响应头的
Content-Type
的值,根据不同的资源类型采用不同的解析方式。 - 查看响应体,发现返回的是一个html文件,便开始解析这个html文件。
后续步骤:
资源预加载:如果在 html 中存在
<link>
、<script>
、img
等标签,浏览器会在开始解析html标签之前,预加载这些资源。
解析HTML,构建 DOM 树。
- 如果在解析html标签的时候,遇到了link标签或者img标签,将link或者img插入dom树,继续解析html构建dom树。
- 如果遇到了script标签,将script插入dom树;如果标签未添加defer/async,且对应的js文件还未加载完毕,则该js文件的加载和执行都会阻塞dom树的构建
- 否则,js文件的加载不会阻塞dom构建;对于async,js文件加载好后会立即执行,如果此时dom树正在构建,dom树的构建会被阻塞。
当css文件加载完毕之后,就开始解析 CSS ,构建CSSOM树,因为下载和解析CSS的工作,是在预解析线程中进行的,所以CSS的加载和解析都不会阻塞html的解析。为了验证这一点,可以使用
performance
面板观察到(在edge中就是打开性能录制,查看”主要”(主线程))如下图所示,
分析html=解析html=构建dom树
,和分析css=解析css=构建cssom树
,是可以同时执行的。其中,”计算脚本”的意思是,编译并执行js代码所花的时间,其中”自我”部分指的就是纯执行时间(不含子任务,如编译)。
虽然DOM树和CSSOM树能够并行构建,但两者都需要准备就绪后,才能生成最终的渲染树,所以说,css的加载和解析是会推迟渲染的。了解这一点有助于更好地设计网站结构和优化加载性能。
Recalculate Style:合并 DOM 树和 CSS 规则,生成 render 树
布局 render 树( Layout / reflow ),确定元素的位置和大小
绘制 render 树( paint ),绘制页面像素信息
浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上

页面空白问题
“白屏”通常指的是用户看到的是一个空白的页面(纯白或无内容),可能的原因包括:
- 页面确实还没有开始渲染;
- DOM 已经构建完成,CSS 也加载了,但 JS 还没执行完,框架还没把内容插入到页面中,内容是空的,所以页面是白屏的;
我们讨论第二种情况:
1 |
|
这是一个典型的SPA应用的index.html
文件,其中body中的html标签非常少,就是个空壳子,需要等待加载,执行好引入的js文件修改dom结构后才有实际的内容。如果引入的js文件体积很大,导致html标签解析结束,dom构建好,cssom也构建好后,但js文件还未加载完毕,由于页面渲染不会等待js文件,所以页面会直接渲染,出现空屏。
说说你对websocket的理解
是什么
WebSocket,是一种网络传输协议,位于OSI
模型的应用层。可在单个TCP
连接上进行全双工
通信,能更好的节省服务器资源和带宽并达到实时通迅
。
协议名
引入ws
和wss
分别代表明文和密文的websocket
协议,且默认端口使用80或443,几乎与http
一致
1 | ws://www.chrono.com |
握手
类似HTTP请求建立TCP
连接需要握手,WebSocket
也要有一个握手过程,然后才能正式收发数据。
与HTTP的区别与联系
联系
- 和http协议一样,都是一种网络传输协议,都工作在应用层,收发数据都基于tcp连接
区别
更强的实时性(全双工)
相对于HTTP请求需要等待客户端发起请求服务端才能响应(半双工),延迟明显更少。
保持连接状态(有状态)
创建通信后,可省略状态信息,不同于HTTP每次请求需要
携带身份验证
,也可以说,websocket是有状态
的。较少的控制开销
数据包头部协议较小,不同于http每次请求需要携带完整的头部。
使用方式
服务端
安装
1 | npm i ws |
创建一个websocket服务
1 | const wss = new ws.Server({port:8080},()=>{ |
监听客户端连接
1 | wss.on('connection',(s)=>{ |
监听用户发送消息
1 | wss.on('connection',(socket)=>{ |
实现广播
1 | socket.on('message',(s)=>{ |
监听用户断开连接
1 | socket.on('close', () => { |
检测websocket状态
1 | let timer = null |
完整代码
1 | const ws = require('ws') |
客户端
使用内置的WebSocket
即可
创建ws对象并建立连接
1 | const ws = new WebSocket("ws://localhost:8080")//传入url,与指定的服务器建立连接 |
监听连接建立
1 | ws.addEventListener('open',(e)=>{ |
监听服务端传来的消息
1 | ws.addEventListener('message',(e)=>{ |
向服务器发送消息
1 | function emit(){ |
主动断开连接
1 | ws.close() |
之后wss.clients
数组的长度会-1
完整代码
1 | <body> |
小结
- 客户端创建ws对象使用的是js内置
WebSocket
对象,服务端使用的是npm下载的node
包 - 客户端监听使用
addEventListener
,服务端监听使用on
- 双端发送消息使用的都是
send
,接受消息的事件名
都是message
,不过传入回调函数的数据格式不同。
应用场景
- 弹幕
- 媒体聊天
- 协同编辑
- 基于位置的应用
- 体育实况更新
- 股票基金报价实时更新
跨站和跨域有什么区别?什么是主域名,子域名?
添加这部分内容主要是为了后续理解cookie和localStorage
浏览器通过 eTLD+1
判断是否属于同站(Same-Site)请求,防止跨站攻击。其中eTLD( Effective Top-Level Domain),意为有效顶级域名
。
www.bilibili.com
和game.bilibili.com
:同站(共享 eTLD+1,即bilibili.com
)。a.github.io
和b.github.io
:跨站(因github.io
是公共后缀,视为不同站点)。
我们可能会问,com是有效顶级域名,允许用户直接注册二级域名(如 example.com
),但这里的io
为什么就不是有效顶级域名,github.io
要被视为公共后缀(也就是特殊的顶级域名)。因为这类特殊的顶级域名,不允许用户直接注册二级域名,通常由服务商统一管理子域,防止恶意网站通过子域共享 Cookie 或凭据(如 user1.github.io
和 user2.github.io
应视为不同站点)
主域名通常就是eTLD+1
,域名的其他部分就是子域名,因此我们可以说,简单的来说,主域名相同就是同站,否则就是跨站。
完整域名 | eTLD(有效顶级域名) | eTLD+1(主域名) |
---|---|---|
www.bilibili.com | .com | bilibili.com |
user.github.io | github.io (公共后缀) | user.github.io |
shop.co.uk | .co.uk | shop.co.uk |
跨域就是浏览器基于同源策略,推出的安全手段,请求的域名,协议,端口号
中有一项和当前页面的不同,就是跨域请求,跨域不一定跨站(比如只是协议不同),但是跨站就一定跨域(因为跨站域名一定不同)。
其他
这部分我看到的比较有意思的面试题,或者自己想出的问题,不过面试问的问题都是基于项目出发的,懂的都懂。
说说二维码登录流程
用户在网页上选择二维码登录选项。
网页携带
设备信息(如浏览器类型、操作系统等)
向服务器请求生成
用于登录的二维码;服务端生成一个二维码id
,并将这个id与请求中携带的设备信息
绑定,存储在服务器数据库中,把二维码id返回给网页。网页获取
二维码id
后,展示二维码,并轮询二维码状态(设置定时器,每个一段时间发送一个ajax请求询问二维码状态)。用户用移动设备扫描二维码,获取到
二维码id
,移动设备将账号信息
连同二维码id
发送给服务器服务端收到信息后会将
账号信息
与二维码id
绑定,并返回一个临时token;此时移动端会提示是否登录
,pc端二维码状态变为已扫描
移动端确认登录,会将
临时token
发送到服务器,服务器收到token后会根据二维码id
绑定的设备信息
和用户信息
生成一个用于pc端登录的token
,并在pc端轮询二维码状态的时候返回给pc端
。


说说用户登录流程
- 用户输入账号密码,前端先校验账号密码的
格式
是否正确,然后把账号密码连同设备信息(如浏览器类型、操作系统、设备标识符等)
发送给后端对应接口
- 后端校验账号密码是否正确,如果校验通过(正确),把
设备信息
和账号
绑定,并返回一个根据用户id
和设备信息
加密后生成的token字符串 - 前端接收到返回的token后把它存储在
localStorage
- 后续前端的每次请求都会
携带token
和设备信息
- 后端会校验请求中的
token
是否有效,是否与携带的设备信息
匹配,不匹配或者token无效则请求失败;服务器应该返回相应的错误信息
,并要求用户重新登录。
说说withCredentials
withCredentials
是XMLHttpRequest
中的一个属性,用于控制是否在跨域请求
中发送凭据(如 cookies、HTTP 认证信息等)。
1 | // 原生XHR |
1 | // Fetch API |
当浏览器检测到跨域请求需要携带凭证(如 withCredentials: true
)时,会触发以下机制
此次跨域请求会携带凭证
但响应头中必须同时满足:
1
2Access-Control-Allow-Origin: https://your-domain.com // 明确指定域名,禁止使用 *
Access-Control-Allow-Credentials: true如果响应中未正确返回上述字段,或
Access-Control-Allow-Origin
使用了通配符*
,那么浏览器会拦截此次携带凭据的跨域请求的响应。
简单的来说,就是需要前后端协调配合,才能实现跨域携带cookie。
默认情况下,cookies 只会在同源请求中自动发送。这意味着如果请求是从同一协议、主机名和端口的页面发出的,则会包含相关 cookies。
举个例子:
如果当前页面是 www.bilibili.com
,并且你发送一个请求,请求的域名是 www.bilibili.com
,因为这个请求没有跨域,那么无论是设置为 Domain=www.bilibili.com
的 Cookie 还是设置为 Domain=.bilibili.com
的 Cookie 都会被自动携带。
但当你在 https://www.bilibili.com
页面下,发送请求 https://game.bilibili.com
时,这是一个跨域请求,但是没跨站Domain=www.bilibili.com
的 Cookie不会被自动携带,因为它们仅限于 www.bilibili.com
。Domain=.bilibili.com
的 Cookie可能会被携带,但是需要满足以下条件:
- 客户端在请求中明确设置了
withCredentials: true
。 - 服务器在响应中,正确配置了 CORS 头,允许凭据传输。
如果withCredentials
的值为false(就是没有配置或者配置为false),那即便Domain=.bilibili.com
的cookie能在game.bilibili.com
下生效,也不会被携带。
如果发送请求,请求的域名是www.sanye.blog
,很明显这是一个跨站请求(关于跨站和跨域的区别,参考前文),同时也是一个跨域请求,此时如果我们想要这个请求能携带cookie,就必须在请求的时候设置withCredentials = true
,然后服务器还需要响应正确的cors头,否则此次请求的响应会被浏览器拦截。要注意的是,并不是所有Domain = www.sanye.blog
的cookie都会被携带,只有SameSite的值为None
的cookie才会被携带,因为此次请求还是跨站的。
如何实现控制文件点击下载或者预览
前端
不添加download属性,默认点击链接预览图片
。
通过a标签的download
属性强制下载
,但是必须要求网页以http/https协议(网络协议)加载,不能是直接打开的网页。
1 | <a href="./violet.png" download="薇尔莉特">点击下载</a> |
给download赋值可以指定下载时,使用的文件名。如果省略此属性,则浏览器会尝试自动解析URL
中的文件名(寻找默认文件名)
后端
可以在响应头中通过设置Content-disposition
来决定是下载还是预览,因为点击a链接
会送一个获取图片(文件)的请求。
inline
指示浏览器直接显示内容(如在浏览器窗口或新标签页中打开),这是默认行为。
attachment
:指示浏览器将内容作为附件下载,同时还可以通过filename
指定下载名称
1 | Content-Disposition: inline; |
如何让图片复制后的链接失效
- 在链接中添加
token
,让这个链接变成临时链接 - 使用
URL.createObjectURL
创建的临时url来展示图片
host referer origin 这三个请求字段有什么区别
Host
作用:
Host
头部是HTTP/1.1协议中必须包含的一个请求头,用于指定请求的目标主机名和端口号。格式:
Host: example.com[:port]
用途:它帮助服务器识别客户端想要访问的具体主机,这对于虚拟主机(即在同一IP地址上托管多个域名)非常重要。典型的例子就是
github.io
,github pages
都被托管到4个固定的ip,但是却对应多个域名,比如域名syhy.github.io
和sanye.github.io
对应的其实可能是同一个ip地址,但是服务器可以根据请求中的host,即syhy.github.io
和sanye.github.io
,判断出一个是要访问用户syhy
的pages,一个是想要访问sanye
的pages。如果URL使用了默认端口(HTTP为80,HTTPS为443),则可以省略端口号。
示例:
1 | Host: www.example.com |
值得注意的是,一个页面的location
对象中也有host
属性,不过这个指的是当前页面的域名+端口号
Referer
作用:
Referer
头部包含了发起当前请求的页面的URL。它主要用于追踪用户是从哪个页面跳转过来的,有助于分析流量来源以及实现防盗链等功能。注意拼写错误:虽然正确的英文单词应该是“Referrer”,但在HTTP协议中该头部被误写作“Referer”。
格式:
Referer: http://example.com/path
用途:
- 网站可以通过这个信息来了解用户是从哪里链接过来的。
常用于统计分析、反盗链措施等场景。
示例:
1 | Referer: https://www.example.com/previous-page |
Origin
作用:
Origin
头部用于标识发起请求的源站点(scheme, host, port)。它主要应用于跨域资源共享(CORS)
请求中,用来告知服务器请求的来源,以便决定是否允许该请求。格式:
Origin: scheme://host[:port]
用途:在
跨域请求时
,浏览器会自动添加此头部以帮助服务器判断是否接受来自不同源的请求。与
Referer
不同的是,Origin
仅包含协议、主机名和端口号,而不包括具体的资源路径。
示例:
1 | Origin: https://www.example.com |
总结
Host
:指定请求的目标主机和端口,是HTTP请求的基本组成部分(域名+端口号)- **
Referer
**:提供发起请求的当前页面的完整URL
,主要用于跟踪用户行为或实施安全策略(协议+域名+端口号+资源路径)。 - **
Origin
**:标识请求发起者的源(协议+主机+端口),主要用于跨域请求的安全控制(协议+域名+端口号)
比如我在https://www.example.com/post/index.html
发送一个请求https://www.sanye.blog
,这个请求的host
就是www.sanye.blog
,而这个请求的referer
就是https://www.example.com/post/index.html
,这个请求的origin
就是https://www.example.com