应用层有哪些协议?
面试官您好,应用层是TCP/IP协议栈的最高层,它直接面向用户和应用程序,定义了我们能用网络来做什么。这一层的协议非常丰富,我通常会把它们按照核心功能进行分类来介绍。
1. Web网页访问协议
这是我们日常上网接触最多的协议。
- HTTP (HyperText Transfer Protocol, 超文本传输协议)
- 作用:是构建万维网(WWW)的基础,用于从Web服务器请求和传输网页、图片、API数据等资源。它是一个无状态的、基于请求-响应模型的协议。
- HTTPS (HTTP Secure)
- 作用:可以看作是HTTP的安全版。它通过在HTTP和TCP之间,增加一层SSL/TLS加密层,来对通信内容进行加密、验证身份、保证数据完整性,解决了HTTP明文传输带来的安全问题。
2. 域名解析协议
- DNS (Domain Name System, 域名系统)
- 作用:是整个互联网的“电话簿”。它负责将我们容易记忆的域名(如
www.google.com
),解析为计算机能够理解的IP地址(如172.217.160.78
)。没有DNS,我们就只能靠记IP地址来上网了。
- 作用:是整个互联网的“电话簿”。它负责将我们容易记忆的域名(如
3. 文件传输协议
- FTP (File Transfer Protocol, 文件传输协议)
- 作用:专门用于在客户端和服务器之间进行文件上传和下载。它使用两个独立的TCP连接:一个用于控制命令(控制连接),一个用于传输数据(数据连接)。
- SFTP (SSH File Transfer Protocol)
- 作用:是FTP的一个安全替代品,它通过SSH协议对所有传输的数据进行加密。
4. 电子邮件协议
这是一组协同工作的协议,共同完成了邮件的收发。
- SMTP (Simple Mail Transfer Protocol, 简单邮件传输协议)
- 作用:负责发送邮件。当您用邮箱客户端(如Outlook)发送一封邮件时,它就是通过SMTP协议,将邮件推送到邮件服务器的。
- POP3 (Post Office Protocol version 3) 和 IMAP (Internet Message Access Protocol)
- 作用:这两个协议都负责接收邮件。
- POP3:倾向于将邮件从服务器下载到本地设备,并从服务器上删除。
- IMAP:更现代,它允许用户在多个设备上直接在线管理邮件,邮件主要保留在服务器上。
- 作用:这两个协议都负责接收邮件。
5. 远程登录与控制协议
- Telnet
- 作用:一个早期的、用于远程登录和管理服务器的协议。但它是明文传输的,非常不安全。
- SSH (Secure Shell)
- 作用:是Telnet的安全替代品。它通过加密和身份验证机制,提供了一个安全的、远程命令行访问服务器的通道。我们现在管理Linux服务器,几乎都是通过SSH。
6. 其他重要协议与服务
- CDN (Content Delivery Network, 内容分发网络):CDN虽然更像一种架构,但它在应用层发挥着至关重要的作用。它通过在全球部署大量的边缘节点服务器,将网站的静态资源(如图片、CSS、JS文件)缓存到离用户最近的地方,极大地加速了内容的访问速度,并降低了源站的负载。
- RPC (Remote Procedure Call, 远程过程调用):这不是一个具体的协议,而是一种协议框架。它允许一个程序像调用本地方法一样,去调用另一台服务器上的方法。我们常用的Dubbo, gRPC, Thrift等框架,都是RPC的实现。
通过这些丰富多样的应用层协议,我们的应用程序才能在网络上实现各种复杂的功能。
HTTP报文有哪些部分?
面试官您好,HTTP报文是HTTP协议中,客户端和服务器之间进行通信所传输的数据块。它遵循一个非常严格的格式,我们可以把它想象成一封格式化的“信件”。
这封“信件”主要分为两种:客户端发出的请求报文(Request Message)和服务器返回的响应报文(Response Message)。它们的结构大同小异,都主要由四个部分组成。
下面我通过一个用户登录的例子,来具体说明这两类报文的组成:
1. 请求报文 (Request Message) —— “客户端的请求信”
假设我们正在一个登录页面,输入了用户名和密码,点击登录按钮。浏览器就会构造并发送一个类似下面这样的HTTP请求报文:
POST /login HTTP/1.1
Host: www.example.com
Content-Type: application/json
User-Agent: Mozilla/5.0 ...
Content-Length: 43
{
"username": "alice",
"password": "mypassword"
}
这封“请求信”的结构,完美地对应了四个部分:
a. 请求行 (Request Line) —— “信的标题”
POST /login HTTP/1.1
- 它包含了三部分信息:
- 请求方法 (Method):
POST
,表明这次请求的意图是提交数据。 - 请求目标 (URL):
/login
,指明了要请求的服务器资源路径。 - HTTP协议版本:
HTTP/1.1
。
- 请求方法 (Method):
b. 请求头部 (Request Headers) —— “信封上的信息”
Host: www.example.com
Content-Type: application/json
- …
- 它以“键: 值”的形式,提供了关于这次请求的各种元数据。比如,
Host
指明了目标服务器的域名,Content-Type
说明了请求体中的数据格式是JSON,User-Agent
则告诉服务器客户端的类型(浏览器信息)。
c. 空行 (Blank Line) —— “信头与正文的分隔线”
- 一个回车换行符(CRLF)。它的作用非常关键,就是用来明确地分隔开头部和请求体。
d. 请求体 (Request Body) —— “信的正文内容”
{ "username": "alice", ... }
- 这里存放的是实际要传输给服务器的数据。对于
POST
请求,这里就是我们要提交的表单数据。对于GET
请求,请求体通常是空的。
2. 响应报文 (Response Message) —— “服务器的回信”
服务器在处理完上面的登录请求后,会返回一个类似下面这样的HTTP响应报文:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 35
Date: Mon, 28 Oct 2023 12:00:00 GMT
{
"status": "success",
"token": "xyz123abc"
}
这封“回信”的结构,也同样包含四个部分:
a. 状态行 (Status Line) —— “回信的标题”
HTTP/1.1 200 OK
- 它也包含三部分信息:
- HTTP协议版本:
HTTP/1.1
。 - 状态码 (Status Code):
200
,这是一个非常重要的数字,代表“请求成功”。其他常见的如404
(未找到)、500
(服务器内部错误)等。 - 状态短语 (Reason Phrase):
OK
,对状态码的一个简短的文本描述。
- HTTP协议版本:
b. 响应头部 (Response Headers) —— “回信信封上的信息”
Content-Type: application/json
- …
- 同样提供了关于这次响应的元数据。比如,
Content-Type
告诉浏览器,我返回给你的也是JSON格式的数据,Content-Length
则指明了响应体的长度。
c. 空行 (Blank Line)
- 同样,用于分隔头部和响应体。
d. 响应体 (Response Body) —— “回信的正文内容”
{ "status": "success", ... }
- 这里存放的是服务器实际返回给客户端的数据。比如,登录成功后的用户信息、token,或者是一个HTML页面的完整内容。
总结一下,无论是请求还是响应,HTTP报文都遵循着这样一个 “起始行 + 头部 + 空行 + 主体” 的清晰结构。正是这个标准化的格式,保证了在复杂的互联网环境中,客户端和服务器之间能够准确无误地进行通信。
HTTP常用的状态码有哪些?
面试官您好,HTTP状态码是服务器用来响应客户端请求结果的一种标准化方式。它们被清晰地划分为五个大的类别,每个类别都代表了一类特定的响应情况。
1xx:信息性状态码
- 含义:表示服务器已接收到请求,正在进行处理,是一个临时的、中间状态的响应。
- 常见码:
100 Continue
:客户端可以继续发送请求的剩余部分。在上传大文件时可能会用到。
- 实践:这一类状态码在日常开发中我们很少直接接触。
2xx:成功状态码
- 含义:表示服务器已成功接收、理解并处理了客户端的请求。这是我们最期望看到的结果。
- 常见码:
200 OK
:最常见的成功状态码。表示请求已成功,响应体中包含了请求的资源。201 Created
:表示请求已成功,并且在服务器上创建了一个新的资源。通常是POST
或PUT
请求成功后的响应。204 No Content
:表示请求已成功处理,但没有内容可以返回。通常用于DELETE
请求成功后,或者一个更新操作成功但无需返回新数据时。
3. 3xx:重定向状态码
- 含义:表示为了完成请求,客户端需要采取进一步的操作,通常是需要重定向到另一个URL。
- 常见码:
301 Moved Permanently
(永久重定向):请求的资源已被永久移动到新的URL。浏览器和搜索引擎会缓存这个新的URL。非常适合用于网站域名更换或URL结构调整。302 Found
(临时重定向):请求的资源暂时被移动到新的URL。浏览器和搜索引擎不会缓存这个重定向。适用于临时的活动页面、登录跳转等场景。304 Not Modified
:用于HTTP缓存。当客户端发起一个带条件的GET请求(比如If-Modified-Since
),服务器发现资源没有变化,就会返回304,告诉客户端可以直接使用本地缓存,从而节省了带宽。
4. 4xx:客户端错误状态码
- 含义:表示客户端的请求本身存在错误,导致服务器无法处理。
- 常见码:
400 Bad Request
:最常见的客户端错误,表示请求报文的语法有误,服务器无法理解。401 Unauthorized
:表示请求需要身份认证。客户端需要提供有效的凭证(如Token)才能访问。403 Forbidden
:表示服务器已经理解了请求,但拒绝执行。与401不同,这通常意味着“即使你亮明了身份,你也没有权限访问这个资源”。404 Not Found
:最著名的状态码,表示服务器上找不到请求的资源。405 Method Not Allowed
:您提到的这个也很重要,表示请求的方法(如POST
)不被目标资源所支持(比如该URL只支持GET
)。
5. 5xx:服务器错误状态码
- 含义:表示服务器在处理一个看似有效的请求时,内部发生了错误。
- 常见码:
500 Internal Server Error
:最常见的服务器错误。这是一个通用的、笼统的错误码,表示服务器遇到了一个意外情况,无法完成请求。通常是代码中的Bug导致的。502 Bad Gateway
:通常出现在使用了反向代理或网关的架构中。它表示作为网关的服务器,从上游服务器(比如真正的业务服务器)收到了一个无效的响应。503 Service Unavailable
:表示服务器当前暂时无法处理请求。这可能是因为服务器过载、正在进行停机维护等。通常这是一个临时状态,稍后会恢复。504 Gateway Timeout
:也与网关有关。它表示作为网关的服务器,在指定时间内没有收到来自上游服务器的响应,即上游服务超时了。
通过理解这些状态码的精确含义,我们可以在开发和排查问题时,快速地定位到是客户端、网络还是服务器端出了问题。
HTTP层请求的类型有哪些?
面试官您好,HTTP协议定义了一组请求方法(Request Methods),也常被称为“HTTP动词”。它们用来指明客户端希望对服务器上的目标资源(由URL指定) 执行什么样的操作。
我通常会按照常用程度和语义特性来介绍它们:
一、 最核心的四种方法 (CRUD操作)
这四种方法,完美地对应了我们对资源的“增删改查”操作,是构建RESTful API的基础。
1. GET:查 (Read)
- 作用:从服务器获取指定的资源。比如,获取一篇文章、一个用户信息。
- 特点:
- 安全的 (Safe):一个GET请求不应该对服务器上的资源产生任何副作用(只读)。
- 幂等的 (Idempotent):对同一个URL执行一次或多次GET请求,其结果应该是完全相同的。
- 可缓存的:GET请求的响应可以被浏览器或代理服务器缓存。
- 参数通常放在URL的查询字符串中。
2. POST:增 (Create)
- 作用:向服务器提交数据,请求服务器创建一个新的资源。比如,注册一个新用户、发布一篇文章。
- 特点:
- 非安全的:它会改变服务器上的资源。
- 非幂等的:连续执行两次相同的POST请求,通常会在服务器上创建两个新的、不同的资源。
- 数据放在请求体(Request Body)中。
3. PUT:改 (Update)
- 作用:向服务器更新一个已存在的资源,或者如果资源不存在,则创建它。它通常要求客户端提供完整的资源表示。
- 特点:
- 非安全的:它会改变服务器上的资源。
- 幂等的:这是它与POST的一个关键区别。对同一个URL执行一次或多次相同的PUT请求,最终服务器上该资源的状态应该是完全相同的(“以最后一次为准”)。比如,
PUT /users/123
来更新用户信息,无论执行多少次,最终用户123的信息都是最后一次提交的那个。
4. DELETE:删 (Delete)
- 作用:请求服务器删除指定的资源。
- 特点:
- 非安全的:它会改变服务器上的资源。
- 幂等的:对同一个URL执行一次或多次DELETE请求,其最终效果都是“该资源不存在”。
二、 其他常用方法
5. HEAD
- 作用:与GET方法完全相同,但服务器在响应中只返回头部信息,不返回响应体。
- 应用场景:非常适合用来检查资源的元信息,比如一个大文件的
Content-Length
(文件大小)、Last-Modified
(最后修改时间)等,而无需下载整个文件,非常节省带宽。
6. PATCH
- 作用:也是用于更新资源,但与PUT不同,PATCH通常用于对资源进行局部更新,即只提供需要修改的那部分字段。
- 特点:非幂等的。比如,一个
PATCH
请求是“将age字段加1”,执行多次,结果就不同了。 - 与PUT的对比:PUT是“整体替换”,PATCH是“局部修改”。
7. OPTIONS
- 作用:用于查询目标资源支持哪些HTTP请求方法。
- 应用场景:在CORS(跨域资源共享) 中,浏览器在发送真正的跨域请求前,会先发送一个OPTIONS“预检”请求,来询问服务器是否允许接下来的跨域请求。
总结一下,通过语义化地使用这些不同的HTTP方法,我们可以设计出结构清晰、符合RESTful风格的API,让客户端和服务器之间的通信意图变得一目了然。
GET和POST的使用场景,有哪些区别?
面试官您好,GET和POST是HTTP协议中最常用、也最容易被混淆的两种请求方法。要理解它们的区别,我通常会从两个层面来看:一是RFC规范定义的“语义”层面,二是实际工程应用中的“惯例”层面。
第一层面:从RFC规范定义的“语义”上,它们有本质区别
这是它们最核心、最根本的区别。
1. 核心作用与语义 (What for?)
- GET:其语义是 “获取(Retrieve)”。它被设计用来从服务器请求和读取指定的资源,它本质上应该是一个只读操作。
- POST:其语义是 “处理(Process)”。它被设计用来向服务器提交数据,请求服务器对这些数据进行处理,从而可能导致服务器状态的改变(比如创建一个新资源)。
2. 参数传递方式 (How to send data?)
- GET:请求的参数通常被编码在URL的查询字符串(Query String) 中,直接附加在URL后面。比如
.../search?q=keyword
。- 限制:URL的长度通常受到浏览器的限制,并且只能包含ASCII字符。
- POST:请求的参数被放在请求体(Request Body) 中进行传输。
- 优势:请求体的大小没有限制,并且可以传输任意格式的数据(如JSON、XML、文件等)。
- GET:请求的参数通常被编码在URL的查询字符串(Query String) 中,直接附加在URL后面。比如
3. 安全性 (Safety) 与 幂等性 (Idempotence)
- GET:
- 安全的 (Safe):一个符合规范的GET请求,不应该对服务器资源产生任何“写”的副作用。
- 幂等的 (Idempotent):对同一个URL,连续发起一次或多次GET请求,服务器返回的结果应该是完全相同的。
- POST:
- 非安全的 (Unsafe):它会改变服务器的资源状态。
- 非幂等的 (Non-idempotent):连续发起两次相同的POST请求,通常会在服务器上创建两个新的、不同的资源。
- GET:
4. 缓存与收藏 (Caching & Bookmarking)
- 正是因为GET是安全且幂等的,所以:
- GET请求的响应可以被浏览器或代理服务器缓存,以提升后续访问的速度。
- GET请求的URL可以被方便地收藏为书签,或者被分享。
- 而POST请求,由于其非幂等性,通常不会被缓存,也不适合被收藏。
- 正是因为GET是安全且幂等的,所以:
第二层面:从实际工程应用上看,界限有时会模糊
虽然RFC规范定义得非常清晰,但在实际开发中,开发者并不总是严格遵守。
“万能的POST”:
- 在一些场景下,即使是获取数据,开发者也可能选择使用POST。
- 常见原因:
- GET的URL长度限制:当查询的参数非常多、非常长时,可能会超出浏览器的URL长度限制,此时只能改用POST,将参数放在请求体中。
- 数据敏感性:GET请求的参数会直接暴露在URL中,容易被记录在浏览器历史、服务器日志里。如果参数包含敏感信息(虽然这本身是不好的设计),开发者可能会选择用POST来提高一点点“隐蔽性”。
- 避免缓存:有时为了确保每次都能获取到最新的数据,开发者可能会用POST来“绕过”浏览器或代理对GET请求的缓存。
“不规范的GET”:
- 同样,也有开发者会用GET请求去执行写操作(比如在一个链接中实现删除功能
.../delete?id=123
)。这是一种非常糟糕的、违反HTTP语义的设计,因为它可能被网络爬虫、预加载等机制无意中触发,导致严重的数据误删问题。
- 同样,也有开发者会用GET请求去执行写操作(比如在一个链接中实现删除功能
总结与我的实践原则
特性 | GET (规范定义) | POST (规范定义) |
---|---|---|
语义 | 获取/查询 | 提交/创建/处理 |
参数位置 | URL 查询字符串 | 请求体 (Body) |
安全性 | 安全 | 不安全 |
幂等性 | 幂等 | 不幂等 |
可缓存性 | 可缓存 | 不可缓存 |
在我的开发实践中,我会严格遵循RFC的语义规范:
- 所有只读、幂等的查询操作,都使用GET。
- 所有会改变服务器状态的写操作(增、改、删),都使用POST、PUT、DELETE等相应的方法。
这样做,不仅能让我们的API设计更清晰、更符合RESTful风格,也能更好地利用HTTP协议自身的缓存等特性,并避免很多潜在的安全问题。我只会在遇到像“URL长度超限”这类不得已的情况下,才考虑用POST来做查询。
HTTP的长连接是什么?
面试官您好,HTTP的长连接(也叫持久连接,Persistent Connection 或 HTTP keep-alive),是HTTP协议中一种非常重要的连接管理机制。
要理解它的作用,我们首先要看一下它的“反面”——短连接。
1. 短连接 (Short-lived Connection) 的时代 (HTTP/1.0)
- 在早期的HTTP/1.0协议中,默认使用的是短连接。
- 工作模式:
- 浏览器每需要请求一个资源(比如一个HTML文件、一张图片、一个CSS文件),都必须与服务器新建一个TCP连接。
- 请求完成后,服务器返回响应。
- 响应一结束,这个TCP连接就立即被关闭。
- 一个生动的比喻:就像你每次去便利店买一瓶水,都需要先和店员握手(建立TCP连接),然后告诉他你要什么,他给你水,你付钱,然后立刻松手说再见(关闭TCP连接)。如果你还想买一包薯片,对不起,请重新再握一次手。
- 缺点(致命):
- 性能开销巨大:一个现代网页通常包含几十上百个资源。如果每个资源都需要一次完整的“TCP三次握手、四次挥手”过程,那么大量的时延和CPU资源都会被浪费在连接的建立和关闭上。
- 慢启动影响:TCP连接有一个“慢启动”的特性,刚建立的连接传输速度较慢。频繁地建立新连接,使得大部分数据传输都处于低效的慢启动阶段。
2. 长连接 (Persistent Connection) 的诞生 (HTTP/1.1)
为了解决短连接的性能问题,HTTP/1.1协议将长连接作为了默认的行为。
工作模式:
- 浏览器在请求第一个资源时,与服务器建立一个TCP连接。
- 当这个资源的请求和响应完成后,这个TCP连接并不会立即关闭。
- 浏览器可以 “复用” 这一个已经建立好的TCP连接,继续发送后续对其他资源的请求(比如请求页面中的图片、CSS、JS文件)。
- 直到一段时间内(由
Keep-Alive
头部的timeout
参数控制)没有新的请求,或者客户端/服务器明确地要求关闭,这个连接才会被断开。
比喻升级:现在,你和店员握一次手后,可以一直保持着握手的状态,连续地向他要水、要薯片、要面包……直到你买完所有东西,才最终松手说再见。
带来的巨大好处:
- 极大减少了连接建立的开销:避免了大量的TCP三次握手和四次挥手,显著降低了延迟。
- 缓解了服务器压力:减少了服务器维护大量短时连接的负担。
- 提升了页面加载速度:后续资源的请求可以更快地发出和响应。
3. 如何控制长连接?
- 在HTTP/1.1中,通过请求/响应头中的
Connection
字段来控制。Connection: keep-alive
(默认值):表示希望保持长连接。Connection: close
:表示处理完当前请求后,就关闭连接。
Keep-Alive
头部还可以包含timeout
(连接超时时间)和max
(一个连接上最多能处理的请求数)等参数。
4. 长连接的演进:HTTP/2的多路复用
- 虽然HTTP/1.1的长连接解决了重复建连的问题,但它仍然存在一个瓶颈:在一个TCP连接上,请求和响应必须是 “一问一答” 的,即发送完一个请求,必须等它的响应回来,才能发送下一个请求。这就是所谓的 “队头阻塞”(Head-of-line blocking)。
- HTTP/2通过引入多路复用(Multiplexing),彻底解决了这个问题。它允许在一个单一的TCP连接上,同时、并行地发送和接收多个请求和响应,而无需等待。这进一步极大地提升了Web的性能。
总结一下,HTTP长连接是一种通过复用TCP连接,来减少连接建立和关闭开销的关键性能优化技术。它是HTTP/1.1的默认行为,并为后续HTTP/2更高效的多路复用奠定了基础。
HTTP默认的端口是什么?
面试官您好
- HTTP协议的默认端口是80。
- HTTPS协议的默认端口是443。
补充说明:“默认端口”的意义
“默认端口”的意义在于,当我们在浏览器中访问一个网址时,如果没有明确地指定端口号,浏览器就会根据协议的类型,自动地去连接这个默认端口。
- 例如:
- 当我们在地址栏输入
http://www.example.com
时,浏览器实际访问的是http://www.example.com:80
。 - 当我们在地址栏输入
https://www.example.com
时,浏览器实际访问的是https://www.example.com:443
。
- 当我们在地址栏输入
正是因为有这个“默认”的约定,我们平时上网才不需要在网址后面手动输入:80
或:443
,大大简化了URL。当然,如果服务器的HTTP/HTTPS服务监听在非标准端口上(比如我们本地开发时常用的8080端口),那么在访问时就必须显式地指定端口号,例如http://localhost:8080
。
HTTP1.1怎么对请求做拆包,具体来说怎么拆的?
面试官您好,您提出的这个问题非常好,它触及了HTTP协议如何在一个TCP连接上,准确地界定一个HTTP报文边界的核心问题。
在HTTP/1.1中,为了在一个TCP连接上传输多个HTTP请求和响应(长连接),客户端和服务器必须有一种明确的方式来知道“一个报文到哪里结束,下一个报文从哪里开始”。这个“拆包”或“边界界定”,主要有以下两种机制:
1. 基于Content-Length
的长度界定(最常见)
这是最直观、最常用的一种方式。
工作原理:
- 发送方(客户端或服务器)在发送报文时,会在头部明确地包含一个
Content-Length
字段,它的值表示报文主体(Body)的精确字节数。 - 接收方在解析完头部、看到
Content-Length
后,就会严格地按照这个长度,从TCP流中读取相应字节数的数据作为报文主体。读完这么多字节后,它就知道这个报文结束了,后面紧跟着的就是下一个报文的起始行。
- 发送方(客户端或服务器)在发送报文时,会在头部明确地包含一个
适用场景:
- 适用于所有在发送前,报文主体的长度是已知的、确定的情况。比如,当我们要上传一个文件时,文件的大小是已知的,就可以用
Content-Length
。
- 适用于所有在发送前,报文主体的长度是已知的、确定的情况。比如,当我们要上传一个文件时,文件的大小是已知的,就可以用
2. 基于Transfer-Encoding: chunked
的分块传输编码
这种机制,是为了解决一个Content-Length
无法处理的痛点。
- 解决了什么问题?
- 当服务器需要返回一个动态生成的、在发送前无法确定其总长度的内容时,
Content-Length
就无能为力了。比如,一个需要从数据库中流式查询并实时返回的大型JSON数组。
- 当服务器需要返回一个动态生成的、在发送前无法确定其总长度的内容时,
- 工作原理:
- 发送方会在头部声明
Transfer-Encoding: chunked
,表示将采用分块的方式传输数据。 - 报文主体会被分割成一个或多个“块”(Chunk) 来发送。
- 每个块的格式是:
chunk-size
: 一个十六进制的数字,表示后面chunk-data
的长度。- 一个回车换行(CRLF)。
chunk-data
: 实际的数据块。- 一个回车换行(CRLF)。
- 这个过程会一直持续,直到所有数据都发送完毕。
- 最后,会发送一个大小为0的“结束块” (
0\r\n\r\n
),来明确地标记整个报文主体的结束。
- 发送方会在头部声明
- 适用场景:
- 非常适合流式传输和动态内容生成的场景,比如服务器端的响应是边生成边发送的。
3. 基于“连接关闭”的界定(HTTP/1.0的方式)
- 这是一种比较古老的方式,主要用在HTTP/1.0的短连接中。
- 工作原理:发送方在发送完所有数据后,直接关闭TCP连接。接收方只要持续地读取数据,直到它感知到连接被对方关闭了(比如
read()
返回-1),就知道报文已经接收完毕。 - 缺点:这种方式无法在一个连接上实现多路复用,因为一次通信就关闭了连接。在HTTP/1.1的长连接中,只有在
Connection: close
头部被明确指定时,才会使用这种方式。
总结一下,在HTTP/1.1中,“拆包”或界定消息边界,主要通过两种方式:
- 对于长度已知的内容,使用
Content-Length
。 - 对于长度未知、需要流式传输的内容,使用
Transfer-Encoding: chunked
。
这两种机制的配合,保证了HTTP/1.1能够在长连接上,准确、高效地传输多个报文。
HTTP 断点重传是什么?
面试官您好,HTTP的断点续传,也叫范围请求(Range Requests),是HTTP/1.1协议中一项非常重要的功能。
它的核心作用,是允许客户端只请求一个大资源(如视频、大文件)的一部分,而不是每次都必须从头开始下载整个文件。这对于实现文件下载的断点续传、在线视频的拖动播放等功能至关重要。
这个功能的实现,是一场客户端和服务器之间,基于特定HTTP头部的精密“对话”。
第一幕:服务器的“能力声明”
首先,服务器需要告诉客户端:“我支持范围请求”。
- 当客户端第一次请求这个资源时(或者通过
HEAD
请求),服务器会在响应头中,包含一个关键字段:Accept-Ranges: bytes
- 这个头部就像是服务器在说:“我能理解你按字节范围来请求数据。” 如果没有这个头,客户端就默认服务器不支持断点续传。
第二幕:客户端的“范围请求”
现在,我们来模拟一个断点续传的场景:
- 客户端开始下载一个大文件,但中途因为网络问题,只下载了前512KB的数据。
- 当网络恢复后,客户端准备继续下载。它会向服务器发起一个新的请求,但这次,它会在请求头中,明确地告诉服务器它需要哪一部分数据:
Range: bytes=512000-
- 这个
Range
头部的含义是:“请从第512000个字节(从0开始计数)开始,一直把文件剩下的部分都传给我。” Range
的格式非常灵活,比如bytes=0-499
(请求前500字节),bytes=-500
(请求最后500字节)。
第三幕:服务器的“部分内容”响应
服务器在接收到这个带Range
头的请求后,如果它能处理这个范围,它就会返回一个特殊的响应:
状态码变为
206 Partial Content
- 它不再是
200 OK
。206
这个状态码,明确地告诉客户端:“好的,我理解你的范围请求,现在发给你的,只是这个资源的一部分。”
- 它不再是
响应头包含关键的范围信息:
Content-Range: bytes 512000-1048575/1048576
- 这个头部非常重要,它告诉客户端:“我这次发送给你的,是字节
512000
到1048575
这部分内容,而这个资源的总大小是1048576
字节(1MB)。” - 这使得客户端可以精确地知道自己接收到的数据块,在整个文件中的位置。
- 这个头部非常重要,它告诉客户端:“我这次发送给你的,是字节
Content-Length: 524288
- 这里的
Content-Length
,不再是整个文件的总大小,而是本次响应体的大小(即512KB)。
- 这里的
- 如果请求的范围无效:
- 比如,客户端请求的范围超出了文件的总大小。此时,服务器会返回一个
416 Requested Range Not Satisfiable
的状态码,并可能在Content-Range
头中指明正确的资源大小。
- 比如,客户端请求的范围超出了文件的总大小。此时,服务器会返回一个
总结
所以,HTTP断点续传的实现,就是通过这样一套客户端与服务器之间的“问答”机制来完成的:
- 服务器先通过
Accept-Ranges
表明能力。 - 客户端通过
Range
头,提出具体的范围请求。 - 服务器通过
206
状态码和Content-Range
头,来精确地响应这部分内容。
正是这套基于HTTP头部的标准协议,才使得我们能够在大文件下载和流媒体播放等场景中,获得流畅、可恢复的用户体验。
HTTP为什么不安全?
面试官您好,HTTP协议之所以被认为是不安全的,其根源在于它的两大天生缺陷:“明文传输”和“无身份验证”。
这两个缺陷,直接导致了三大经典安全风险。
1. HTTP的三大安全风险
风险一:窃听风险 (Eavesdropping)
- 问题:HTTP传输的所有内容——无论是URL、请求头,还是报文体——都是未经加密的明文。
- 危害:在数据传输的任何一个中间环节(如路由器、WIFI热点、代理服务器),攻击者都可以像“听电话”一样,轻松地窃听到通信的全部内容。正如您所说,用户的账号密码、银行卡信息等敏感数据,一旦被窃听,就“容易没了”。
风险二:篡改风险 (Tampering)
- 问题:由于缺乏任何校验机制,中间人不仅能窃听,还能肆意地修改传输的内容,而通信双方对此毫无察觉。
- 危害:最典型的就是“运营商劫持”。比如,攻击者可以在你正常浏览的网页中,强制植入弹窗广告、钓鱼链接,或者将下载的文件替换成恶意软件。这就像您说的,“视觉污染,用户眼容易瞎”。
风险三:冒充风险 (Impersonation / Spoofing)
- 问题:HTTP协议无法验证通信双方的真实身份。
- 危害:攻击者可以轻松地伪造一个看起来和真实网站一模一样的“钓鱼网站”。当用户访问时,无法分辨其真伪。比如,一个假的淘宝网站,用户在上面输入了账号密码并支付,结果就是“钱容易没了”。同样,客户端的身份也可能被伪造,向服务器发送恶意请求。
2. HTTPS如何解决这些问题?—— “安全三件套”
为了解决这些问题,HTTPS应运而生。它并不是一个全新的协议,而是在HTTP和TCP之间,增加了一层SSL/TLS安全层。这个安全层,提供了“安全三件套”,完美地应对了上述三大风险。
解决方案一:信息加密 (Encryption) —— 对抗窃听
- 如何做:通过对称加密和非对称加密相结合的方式,对所有传输的数据进行高强度加密。
- 效果:即使数据包被中间人截获,他也只能看到一堆无法解密的乱码。正如您所总结的,交互信息无法被窃取。
解决方案二:校验机制 (Integrity) —— 对抗篡改
- 如何做:通过消息认证码(MAC)或数字签名,来保证数据的完整性。发送方会根据内容生成一个“指纹”,接收方在收到后会进行校验。
- 效果:一旦数据在传输途中被篡改,这个“指纹”就会对不上,接收方会立刻发现并丢弃这个数据包。这确保了通信内容无法被篡改。
解决方案三:身份证书 (Authentication) —— 对抗冒充
- 如何做:通过由权威的证书颁发机构(CA)签发的数字证书,来证明服务器的身份。
- 效果:当浏览器访问一个HTTPS网站时,它会先去验证服务器出示的证书是否合法、是否由可信的CA签发。只有验证通过,才会建立连接。这确保了我们访问的“淘宝”,是真的淘宝网。
总结
所以,HTTPS通过加密解决了窃听问题,通过校验解决了篡改问题,通过证书解决了冒充问题。它为我们的网络通信,提供了一个安全、可靠的通道。
当然,正如您幽默的总结,HTTPS虽然保护了我们的数据在传输过程中的安全,但它管不了我们自己的“剁手”行为,也管不了网站本身的内容(比如竞价排名广告)。这是技术安全与业务行为的边界。
HTTP和HTTPS 的区别?
面试官您好,HTTP和HTTPS虽然看起来只有一字之差,但它们之间存在着本质的区别。最核心的一点是:HTTPS可以被看作是HTTP的安全增强版,它通过在HTTP之下、TCP之上,增加了一层SSL/TLS安全协议,来解决HTTP自身存在的安全风险。
这种根本性的设计差异,导致了它们在以下几个方面有显著的不同:
1. 安全性与数据传输方式 (最本质的区别)
- HTTP: 它是一个明文传输协议。所有的数据,包括用户名、密码、银行卡号等敏感信息,都在网络上“裸奔”,非常容易被中间人窃听和篡改。
- HTTPS: 它通过SSL/TLS协议,提供了“安全三件套”:
- 数据加密:所有传输的内容都经过高强度加密,即使被截获,也无法解密。
- 数据完整性:通过校验机制,保证数据在传输过程中不被篡改。
- 身份认证:通过数字证书,验证服务器的真实身份,防止钓鱼网站。
2. 连接建立过程
- HTTP: 连接过程相对简单。客户端和服务器之间,只需要完成TCP的三次握手,就可以开始传输HTTP报文了。
- HTTPS: 连接过程更复杂。在完成TCP三次握手之后,还必须进行一次SSL/TLS的握手。
- SSL/TLS握手的作用:这个过程非常关键,它主要负责:
a. 客户端和服务器交换协议版本、加密算法等信息。
b. 客户端验证服务器的数字证书是否可信。
c. 双方协商出一个用于本次会话的对称加密密钥。 - 只有当这个安全通道建立完毕后,才能开始传输被加密后的HTTP报文。
- SSL/TLS握手的作用:这个过程非常关键,它主要负责:
3. 默认端口
- HTTP: 默认使用80端口。
- HTTPS: 默认使用443端口。
- 正是因为有这个默认端口的约定,我们平时在浏览器输入网址时,才无需手动指定。
4. 成本与资源要求
- HTTP: 几乎没有额外的成本。
- HTTPS:
- 证书成本:需要向CA(证书权威机构)申请数字证书,这通常需要支付一定的费用(虽然也有免费的证书,如Let’s Encrypt)。
- 性能成本:
- SSL/TLS握手过程会增加额外的网络往返时延(RTT),使得首次连接的延迟更高。
- 数据的加解密过程,会消耗服务器和客户端更多的CPU资源。
- 不过,随着现代CPU性能的提升和硬件加密的普及,HTTPS带来的性能开销已经变得非常小,对于绝大多数应用来说,这点开销换来的安全性是完全值得的。
总结对比
特性 | HTTP | HTTPS |
---|---|---|
安全性 | 明文,不安全 | SSL/TLS加密,安全 |
连接过程 | TCP三次握手 | TCP三次握手 + SSL/TLS握手 |
默认端口 | 80 | 443 |
证书要求 | 不需要 | 需要CA证书 |
成本 | 低 | 较高(证书费用、CPU消耗) |
在今天的互联网环境下,安全性已经不再是一个可选项。Google等主流浏览器已经将未使用HTTPS的网站标记为“不安全”,并且搜索引擎也更青睐HTTPS网站。因此,全站启用HTTPS已经成为了现代Web开发的标准实践。
HTTPS握手过程说一下
面试官您好,HTTPS的握手过程,其核心目标是让客户端和服务器在一个不安全的网络上,安全地协商出一个用于后续通信的对称加密密钥。
正如您所分析的,我们以传统的、基于RSA算法的密钥交换为例,这个握手过程,可以形象地分为 “两次对话”,总共涉及四次消息传递。
下面我来详细描述一下这四次握手的每一步都在做什么:
第一步:客户端的“问候” (Client Hello)
- 客户端 -> 服务器
- 这是握手的开始。客户端会向服务器发送一个“问候”消息,里面包含了它所支持的各种能力,主要有:
- 客户端支持的TLS协议版本(如TLS 1.2, 1.3)。
- 一个客户端生成的随机数(
Client Random
)。这个随机数是后续生成会话密钥的关键参数之一。 - 一个客户端支持的加密套件(Cipher Suites)列表。这个列表告诉服务器:“我会这些加密算法(如
TLS_RSA_WITH_AES_128_GCM_SHA256
),你看看你会哪个,我们挑一个用吧。”
第二步:服务器的“回应与证明” (Server Hello, Certificate, Server Hello Done)
- 服务器 -> 客户端
- 服务器在收到客户端的问候后,会进行一系列的回应:
Server Hello
:服务器从客户端的加密套件列表中,选择一个它也支持的加密套件,并确定一个TLS协议版本,然后告诉客户端:“好的,我们就用这个版本和这个加密套件来通信吧。” 同时,它也会生成一个服务器端的随机数(Server Random
)。Certificate
:这是最关键的一步。服务器会将自己的数字证书发送给客户端。这个证书,是由权威的CA机构签发的,里面包含了服务器的公钥,以及服务器的身份信息。Server Hello Done
:一个结束标记,告诉客户端:“我的话说完了,该你了。”
(此时,客户端会先对服务器的证书进行验证,确保其合法可信。如果验证失败,握手就会中断。)
第三步:客户端的“确认与密钥交换” (Client Key Exchange, Change Cipher Spec, Encrypted Handshake Message)
- 客户端 -> 服务器
- 客户端在验证完服务器证书后,就确信自己正在和“真的”服务器通信。现在,它需要生成并发送用于加密通信的“钥匙”。
- 生成预主密钥 (Pre-master Secret):客户端会再生成一个随机数,我们称之为“预主密钥”。
Client Key Exchange
:【RSA算法的核心】客户端会用从服务器证书中获取到的公钥,来加密这个刚刚生成的“预主密钥”,然后将其发送给服务器。- 安全性体现:由于只有服务器持有对应的私钥,所以只有它才能解密这个消息,获取到预主密钥。中间人即使截获了,也无法解密。
- 生成会话密钥:现在,客户端和服务器双方,都同时拥有了三个关键的随机数:
Client Random
、Server Random
、Pre-master Secret
。它们会用完全相同的算法,将这三个数混合在一起,各自独立地计算出最终用于通信的 “会话密钥”(一个对称密钥)。 Change Cipher Spec
:客户端发送一个通知,告诉服务器:“我准备好了,从现在开始,我们后面的通信就用刚刚协商好的会话密钥来加密了。”Encrypted Handshake Message
:客户端会将之前所有握手消息的摘要,用这个新的会话密钥加密后,发送给服务器。这既是作为一个“握手完成”的信号,也是对服务器的一次验证,看它能否正确解密。
第四步:服务器的“最终确认” (Change Cipher Spec, Encrypted Handshake Message)
- 服务器 -> 客户端
- 服务器在接收并用自己的私钥成功解密出“预主密钥”后,也会用同样的方法计算出会话密钥。
Change Cipher Spec
:服务器也发送一个通知,告诉客户端:“我也准备好了,我们开始加密通信吧。”Encrypted Handshake Message
:服务器同样会将之前所有握手消息的摘要,用会话密钥加密后发送给客户端,让客户端也验证一下。
当客户端成功解密并验证了这条消息后,SSL/TLS握手过程就正式完成了。之后,双方就可以使用这个高效的、对称的会话密钥,来加密和解密真正的HTTP应用数据了。
总结一下,这个过程,就是通过非对称加密(RSA)来安全地交换一个用于对称加密的密钥,从而兼顾了安全性和性能。
HTTPS是如何防范中间人的攻击?
面试官您好,您提出的这个问题,直击了HTTPS协议设计的核心价值。HTTPS之所以能够有效防范中间人攻击(Man-in-the-Middle Attack, MITM),其秘诀在于它通过SSL/TLS层,建立了一套环环相扣、无法被攻破的信任链和加密机制。
1. 首先,我们来理解一下中间人攻击的“作案手法”
一个典型的中间人攻击,其过程是这样的:
- 客户端以为自己正在和服务器通信。
- 服务器也以为自己正在和客户端通信。
- 但实际上,中间人(攻击者)已经悄悄地介入其中,他分别与客户端和服务器都建立了连接。所有的数据都会先经过他这里,他可以窃听、篡改、再转发。
2. HTTPS如何粉碎这个阴谋?—— “加密”与“身份认证”
HTTPS通过两大核心机制,让中间人的“作案”无法得逞。
机制一:数字证书与身份认证 (Authentication) —— “验明正身,你是谁?”
- 核心武器:由权威的、受信任的CA(证书颁发机构)签发的数字证书。
- 工作流程:
- 正如您所说,当客户端(比如浏览器)向服务器发起HTTPS连接请求时,服务器会首先出示它的“身份证”——数字证书。
- 这张证书里,包含了服务器的公钥、域名信息,以及最重要的——CA机构对这张证书的数字签名。
- 客户端的操作系统或浏览器,内置了一份“可信CA机构列表”。它会用这个列表里的CA公钥,去验证服务器证书上那个签名的真伪。
- 中间人如何被识破?
- 中间人可以截获服务器发给客户端的真证书,然后把自己的假证书发给客户端。
- 但是,这个假证书没有可信CA的签名,或者签名是伪造的。客户端在验证时,会立刻发现这个证书是“伪造的”或“不可信的”。
- 此时,浏览器会立刻向用户发出一个非常严厉的安全警告(比如“您的连接不是私密连接”),或者直接中止连接。
- 这就从第一步,就阻止了用户与一个“假冒”的服务器建立信任。
机制二:密钥交换与数据加密 (Encryption) —— “即使截获,也看不懂”
- 核心武器:基于非对称加密的密钥交换。
- 工作流程:
- 在客户端确认了服务器的真实身份之后,它会信任服务器证书里的那个公钥。
- 然后,客户端会生成一个用于本次通信的 “会话密钥”(或者用于生成会话密钥的“预主密钥”)。
- 它会用刚刚从服务器证书里拿到的那个公钥,来加密这个会话密钥,然后发送给服务器。
- 中间人如何被挫败?
- 正如您分析的,中间人虽然可以截获这段被加密的报文,但他没有服务器的私钥,所以他根本无法解密,也就无法得到真正的会话密钥。
- 只有真正的服务器,才能用自己的私钥,解密出这个会话密钥。
- 之后,客户端和服务器就使用这个只有它们俩知道的、对称的会话密钥,来加密所有的应用数据。
总结
所以,HTTPS防范中间人攻击,是一个双重保险的过程:
- 身份认证,通过数字证书,保证了你正在与之通信的,就是它声称的那个人,而不是一个冒牌货。
- 数据加密,通过非对称加密来安全地协商对称密钥,保证了即使通信被截获,中间人也无法窥探和篡改任何内容。
正是这套严谨的机制,确保了我们的在线支付、信息传递等敏感操作,能够在复杂的互联网环境中安全地进行。
HTTP1.1和2.0的区别是什么?
面试官您好,HTTP/2的诞生,并不是对HTTP协议的推倒重建,而是一次以性能为核心目标的、革命性的升级。它所做的所有改进,几乎都是为了解决HTTP/1.1在性能上存在的几个核心瓶颈。
我可以从以下几个方面,来详细对比它们之间的区别:
1. 传输格式的根本变革:从“文本”到“二进制”
- HTTP/1.1:是一个文本协议。它的报文,无论是头部还是主体,都是人类可读的ASCII字符串。
- 缺点:文本格式冗长,解析效率低。服务器需要将文本逐行解析,才能理解其意。
- HTTP/2 (您的解释非常到位):
- 变革:全面采用二进制格式。整个通信被拆分成了一个个的帧(Frame),比如
HEADERS
帧和DATA
帧。这些帧都是二进制编码的。 - 优势:二进制对计算机极其友好。服务器无需再进行复杂的文本解析,可以直接高效地处理二进制数据,提升了解析效率。
- 变革:全面采用二进制格式。整个通信被拆分成了一个个的帧(Frame),比如
2. 连接模型的革命性升级:从“串行”到“并发多路复用”
这是HTTP/2最核心、最重要的改进。
- HTTP/1.1的瓶颈:队头阻塞 (Head-of-Line Blocking)
- 虽然HTTP/1.1支持长连接,但在一个TCP连接上,请求和响应必须是 “一问一答” 的串行模式。如果你同时发了3个请求,必须等第一个请求的响应回来,才能处理第二个,以此类推。如果第一个请求很慢,后面的所有请求都会被阻塞。
- HTTP/2的解决方案:多路复用 (Multiplexing)
- 变革:HTTP/2引入了流(Stream) 和帧(Frame) 的概念。
- 流(Stream):每一个“请求-响应”对,都被看作是一个独立的、带ID的流。
- 帧(Frame):每个流中的数据,又被拆分成更小的二进制帧。
- 如何工作:在一个单一的TCP连接上,客户端和服务器可以同时、并行地发送和接收来自多个不同流的帧。这些帧在传输时可以是交错的,接收方会根据每个帧头部的流ID,将它们重新组装成完整的请求或响应。
- 优势:彻底解决了队头阻塞问题。一个慢请求,不再会影响到其他请求的传输,极大地提升了并发性能和页面加载速度。
- 变革:HTTP/2引入了流(Stream) 和帧(Frame) 的概念。
3. 性能优化的新利器:头部压缩 (Header Compression)
- HTTP/1.1的问题:HTTP头部通常包含大量重复信息(如
User-Agent
,Accept
等),并且每次请求都必须发送完整的头部,这在请求量大时,会浪费大量带宽。 - HTTP/2的解决方案:HPACK算法
- 变革:客户端和服务器会共同维护一个动态的“头部字典”。
- 如何工作:
- 第一次发送某个头部时,会将其完整地发送,并存入字典,分配一个索引号。
- 后续再发送相同的头部时,就只需要发送这个索引号即可。
- 对于值有微小变化的头部,也只需要发送其差异部分。
- 优势:极大地减少了请求的体积,降低了带宽消耗,尤其是在移动网络环境下,效果非常显著。
4. 交互模式的创新:服务器推送 (Server Push)
- HTTP/1.1的模式:严格的 “请求-响应” 模式。服务器只能被动地等待客户端请求。
- HTTP/2的创新:
- 变革:允许服务器主动地向客户端推送资源,而无需客户端发起请求。
- 如何工作:当客户端请求一个HTML页面时,服务器可以“预见到”这个页面肯定会需要某些CSS和JS文件。于是,它可以在发送HTML的同时,就主动地将这些CSS和JS资源,一并推送给客户端的缓存。
- 优势:减少了客户端解析HTML后再发起请求的网络往返时延(RTT),进一步提升了页面加载速度。
总结一下,HTTP/2通过二进制分帧奠定了基础,通过多路复用解决了核心的队头阻塞问题,再通过头部压缩和服务器推送这两个“助推器”,从多个维度,对HTTP/1.1进行了一次全方位的、以性能为导向的彻底革新。
HTTP进行TCP连接之后,在什么情况下会中断
面试官您好,一个HTTP通信所依赖的TCP连接,其“中断”或“关闭”,可以由多个层面的多种原因触发。我通常会把这些情况分为 “主动关闭”、“异常中断”和“超时中断” 三大类。
1. 主动关闭 (Graceful Shutdown) —— “礼貌地分手”
这是最常见、最正常的关闭方式,由通信的一方主动发起。
- 触发场景:
- HTTP/1.0短连接:在HTTP/1.0中,每次请求-响应完成后,服务器通常会主动关闭连接。
- HTTP/1.1
Connection: close
:当客户端或服务器在HTTP头部中明确发送了Connection: close
时,表示处理完当前这次通信后,就希望关闭连接。 - 应用程序主动调用
close()
:服务器或客户端的应用程序,因为业务逻辑需要(比如用户退出、服务关闭),主动调用了socket的close()
方法。
- 底层过程:正如您所说,这会触发一个标准的TCP四次挥手过程。通信双方会通过交换FIN和ACK报文,来确保双方都已经知晓并同意关闭连接,并且都已将数据发送完毕。这是一个非常“绅士”的、有序的关闭流程。
2. 异常中断 (Abrupt Termination) —— “突然的意外”
这种情况通常是由于不可预期的错误或强制操作导致的。
a. RST报文复位连接
- 是什么?
RST
(Reset)是TCP协议中一个表示 “连接重置” 的标志位。它是一种粗暴的、单方面的关闭方式,收到RST的一方,会立即关闭连接,而不会进行四次挥手。 - 触发场景:
- 访问不存在的端口:客户端向一个服务器上并未监听的端口发起连接请求,服务器的TCP/IP协议栈会直接回一个RST报文。
- 程序崩溃或重启:一个进程在持有TCP连接的情况下突然崩溃或被强制杀死,操作系统在清理其资源时,可能会向对端发送RST报文,告知对方“我这边出事了,连接作废”。
- 长时间的连接中断后:一方在连接长时间中断后,突然收到了一个早已“过时”的、来自对方的数据包,它可能会因为无法识别这个包的序列号而回复一个RST。
- 是什么?
b. 重传超时
- 是什么? TCP是一个可靠的协议,它有超时重传机制。
- 触发场景:如果一方持续地向另一方发送数据,但始终收不到对方的ACK确认,并且在达到预设的最大重传次数后,依然没有收到任何响应。
- 后果:此时,发送方会认为网络已经彻底不可达,或者对方主机已经宕机。它会放弃这个连接,并通知上层应用连接已断开。
3. 超时中断 (Idle Timeout) —— “长时间不联系,就分手吧”
这种情况是为了防止大量空闲连接,无谓地占用服务器资源。
a. HTTP层面的
Keep-Alive
超时- 是什么? 在HTTP/1.1的长连接中,
Keep-Alive
机制允许在一个TCP连接上处理多个请求。但这个连接不会永久保持。 - 触发场景:Web服务器(如Nginx, Apache)通常会配置一个空闲超时时间(比如
keepalive_timeout 60s
)。如果一个TCP连接,在指定的时间内,没有任何新的HTTP请求到达,服务器就会主动发起四次挥手,关闭这个连接以释放资源。
- 是什么? 在HTTP/1.1的长连接中,
b. TCP层面的
Keepalive
机制- 是什么? 这是TCP协议自身的一个保活探测机制,与HTTP的
Keep-Alive
是两回事。 - 触发场景:当一个TCP连接上长时间没有任何数据交互时(这个时间通常很长,默认可能是2小时),TCP协议栈的一方会主动发送一个“保活探测包”给对方。如果连续发送多个探测包都得不到响应,它就会认为对方已不可达,从而中断这个连接。这个机制主要是为了清理那些因为物理网络断开(比如拔网线)、对方主机宕机等原因而产生的“僵尸连接”。
- 是什么? 这是TCP协议自身的一个保活探测机制,与HTTP的
总结一下,一个TCP连接的中断,既可能是由上层应用主动、优雅地发起的(四次挥手),也可能是因为网络异常或程序错误而被动、粗暴地中断的(RST、重传超时),还可能是因为长时间空闲而被策略性地清理的(超时中断)。理解这些不同的中断方式,对于我们排查网络问题和设计健壮的网络程序非常有帮助。
HTTP、Socket和TCP的区别
面试官您好,HTTP、Socket和TCP是网络编程中三个不同层面、但又紧密相关的概念。要理解它们的区别,最好的方式是把它们放在TCP/IP协议栈的框架里来看。
一个生动的比喻:寄快递
我们可以把一次网络通信,比作一次完整的“寄快递”过程:
- HTTP协议:就像是快递单上需要填写的“内容格式”。它规定了“寄件人地址”、“收件人地址”、“物品清单”这些栏目应该怎么写,写在哪里。它只关心 “信的内容和格式”。
- TCP协议:就像是快递公司本身。它负责提供一套可靠的运输服务,保证你的包裹(数据)能安全、不丢、不乱地从起点送到终点。它不关心包裹里装的是什么,只关心 “如何把包裹安全送达”。
- Socket:就像是快递公司的“服务窗口”或“电话”。它是你(应用程序)与快递公司(TCP/IP协议栈)进行交互的接口。你想寄快递,就得去窗口办理手续;你想查快递到哪了,就得打电话咨询。Socket就是我们用来 “使用” TCP/UDP服务的那个工具。
它们的核心区别与关系
1. 从“层次”上看:HTTP是上层,TCP是下层,Socket是桥梁
- HTTP:是应用层协议。它定义的是通信双方需要遵守的数据格式和交互规则。比如,GET请求是什么样的,POST请求是什么样的,
200 OK
状态码代表什么意思。 - TCP:是传输层协议。它不关心上层传输的是HTTP报文还是FTP数据,它的唯一职责是提供一个可靠的、面向连接的、端到端的数据传输通道。它负责数据的分段、排序、重传、流量控制等“体力活”。
- Socket:它不是一个协议,而是操作系统提供给应用程序的一个编程接口(API)。它处在应用层和传输层之间,像一个“插座”一样,将复杂的TCP/IP协议栈,封装成了一系列简单易用的函数(如
connect
,bind
,read
,write
),让应用程序可以方便地使用网络服务。
- HTTP:是应用层协议。它定义的是通信双方需要遵守的数据格式和交互规则。比如,GET请求是什么样的,POST请求是什么样的,
2. 从“关系”上看:HTTP“依赖”于TCP,而我们通过Socket来“使用”TCP
- HTTP over TCP:HTTP协议的可靠传输,是完全构建在TCP协议之上的。HTTP本身是无连接的,但它的通信过程,需要先通过TCP建立一个可靠的连接通道。
- App -> Socket -> TCP:我们的应用程序(比如一个Java程序)想要发起一次HTTP请求,它不能直接去“操作”TCP协议。它必须通过创建一个Socket对象,然后调用这个Socket对象提供的方法,来间接地使用操作系统内核中的TCP/IP协议栈,去完成连接的建立和数据的收发。
一个简要的流程
- 应用层(HTTP):我们的浏览器构造一个HTTP请求报文。
- 接口层(Socket):浏览器通过调用
socket()
API,请求操作系统创建一个TCP Socket。 - 传输层(TCP):通过这个Socket,浏览器发起
connect
请求,触发TCP的三次握手,与服务器建立一个可靠的连接。 - 数据传输:连接建立后,浏览器通过Socket的
write
方法,将HTTP请求报文写入TCP的发送缓冲区,由TCP协议负责将其分段、打包、发送出去。服务器端则通过Socket的read
方法来接收。
总结一下:
- TCP是实现数据可靠传输的协议规范。
- HTTP是构建在TCP之上,用于Web数据交换的应用层协议规范。
- Socket是我们程序员用来操作TCP/IP协议栈的那个编程工具/接口。
它们是网络通信中,不同抽象层次上、各司其职又紧密协作的三个核心概念。
DNS的全称了解么?
面试官您好,我了解DNS。它的全称是Domain Name System(域名系统)。
1. DNS是做什么的?—— 互联网的“电话簿”
- 核心作用:DNS在互联网中扮演着 “翻译官” 或 “电话簿” 的角色。它的核心任务,就是将我们人类容易记忆的域名(比如
www.google.com
),翻译成计算机网络能够理解的IP地址(比如172.217.160.78
)。 - 为什么需要它? 如果没有DNS,我们就只能靠记忆和输入一长串无规律的IP地址来上网,那将是一场灾难。
2. DNS的结构:一个全球性的、分层的、分布式的数据库
要理解DNS的工作原理,首先要理解它的域名结构。域名的结构是分层的,并且是 “从右到左,级别越高”。
一个生动的比喻:您用的“中外地址”的比喻非常贴切。域名的层级,就像是西方人写地址的顺序,从最小的单位开始,逐步到最大的单位。
域名的树状层级结构:
- 根域 (Root Domain):所有域名的“老祖宗”。在域名最后,其实有一个我们通常省略不写的点
.
,比如www.google.com.
,这个点就代表根域。全球只有13组根DNS服务器。 - 顶级域 (Top-Level Domain, TLD):根域的下一层。比如我们熟悉的
.com
,.org
,.net
(通用顶级域),以及.cn
,.us
(国家顶级域)。 - 二级域 (Second-Level Domain, SLD):这是我们通常注册的域名主体。比如
google.com
,baidu.com
。 - 子域 (Subdomain):比如
www.google.com
中的www
,或者mail.google.com
中的mail
。
- 根域 (Root Domain):所有域名的“老祖宗”。在域名最后,其实有一个我们通常省略不写的点
为什么要分层和分布式?
- 因为全球的域名数量极其庞大,不可能用一台服务器来存储所有的映射关系。通过这种分层的、树状的结构,DNS将整个解析的压力,分散到了全球成千上万台不同层级的DNS服务器上。
3. DNS的查询过程:一次“从上到下”的寻址之旅
当我们在浏览器输入www.google.com
并回车时,一次典型的DNS解析过程是这样的(以递归+迭代查询为例):
第一站:本地DNS服务器 (Local DNS Server)
- 我们的计算机会首先向我们网络设置中配置的本地DNS服务器(通常由ISP,如电信、联通提供)发起一个递归查询请求:“请帮我找到
www.google.com
的IP地址。” - 本地DNS服务器会先查自己的缓存,如果有,就直接返回。
- 我们的计算机会首先向我们网络设置中配置的本地DNS服务器(通常由ISP,如电信、联通提供)发起一个递归查询请求:“请帮我找到
第二站:根DNS服务器 (Root Server)
- 如果本地DNS缓存没有,它就会向全球13组根DNS服务器中的一台,发起一个迭代查询:“谁知道
www.google.com
?” - 根服务器不负责具体解析,但它像个“总调度”,它会说:“我不知道,但这是
.com
顶级域的事,你去问负责.com
的服务器吧。” 然后,它会返回.com
顶级域DNS服务器的地址列表。
- 如果本地DNS缓存没有,它就会向全球13组根DNS服务器中的一台,发起一个迭代查询:“谁知道
第三站:顶级域DNS服务器 (TLD Server)
- 本地DNS服务器拿到地址后,再向其中一台 .com顶级域DNS服务器发起迭代查询:“谁知道
www.google.com
?” .com
服务器说:“我也不知道,但这是google.com
这个域自己的事,你应该去问它自己的权威服务器。” 然后,它会返回google.com
的权威DNS服务器(也叫NS服务器)的地址。
- 本地DNS服务器拿到地址后,再向其中一台 .com顶级域DNS服务器发起迭代查询:“谁知道
第四站:权威DNS服务器 (Authoritative Name Server)
- 本地DNS服务器最后向
google.com
的权威DNS服务器发起迭代查询:“www.google.com
的IP地址到底是多少?” - 权威服务器里,保存着
google.com
这个域下所有记录的最终答案。它会查询自己的记录,找到www
这条A记录对应的IP地址,然后将这个最终的IP地址返回给本地DNS服务器。
- 本地DNS服务器最后向
最后一站:返回给客户端
- 本地DNS服务器拿到了最终的IP地址,它会一方面将这个结果缓存起来(以便下次有人再问时能快速回答),另一方面将结果返回给我们的计算机。
- 至此,一次完整的DNS查询结束,我们的浏览器就可以拿着这个IP地址,去发起HTTP连接了。
总结一下,DNS通过一个全球性的、分层分布式的树状数据库系统,和一套递归与迭代相结合的查询机制,高效、可靠地完成了将域名“翻译”成IP地址这一互联网的基石性任务。
DNS的端口是多少?
面试官您好,DNS服务使用的默认端口号是53。
补充说明:53端口与TCP/UDP
值得一提的是,DNS在进行查询和响应时,会根据不同的场景,在UDP的53端口和TCP的53端口之间进行选择。
绝大多数情况使用 UDP/53
- 原因:普通的域名解析请求和响应,数据包通常都非常小(小于512字节)。
- UDP的优势:UDP是一种无连接的、轻量级的协议,其开销远小于TCP。使用UDP可以获得更快的响应速度,并减少网络资源的消耗。
- 因此,对于绝大多数的客户端查询,都是通过UDP的53端口来进行的。
在特定情况下会使用 TCP/53
- 虽然UDP快,但它不可靠,并且有数据包大小的限制。在以下两种主要场景下,DNS会“升级”到使用更可靠的TCP协议:
- 当DNS响应包过大时:如果一次查询的响应数据(比如一个域名下有大量的解析记录)超过了512字节,UDP就无法承载了。在这种情况下,DNS服务器在返回响应时,会设置一个“TC”(Truncated,截断)标志位。客户端收到这个标志后,就会自动地切换到TCP模式,重新通过TCP的53端口发起一次完整的查询,以获取全部数据。
- 进行“区域传送”(Zone Transfer)时:这是主DNS服务器和辅DNS服务器之间进行数据同步的一种机制。因为区域传送需要传输整个域的完整数据,数据量非常大,并且要求绝对的可靠性,所以必须通过TCP的53端口来进行。
- 虽然UDP快,但它不可靠,并且有数据包大小的限制。在以下两种主要场景下,DNS会“升级”到使用更可靠的TCP协议:
总结一下,DNS的默认端口是53。它主要使用UDP/53来进行快速的、日常的域名查询,同时保留了使用TCP/53来处理大数据量传输和高可靠性同步的能力。
DNS的底层使用TCP还是UDP?
面试官您好,关于DNS底层使用的传输协议,最准确的回答是:它主要使用UDP,但在特定场景下,必须使用TCP。 这是一个在“效率”和“可靠性”之间做出的经典权衡设计。
1. 为什么绝大多数查询都使用UDP?(追求效率)
正如您所分析的,对于日常的、一次性的域名解析请求,UDP是最佳选择,其优势主要体现在:
a. 低延迟,响应快
- 无连接:UDP不需要像TCP那样进行“三次握手”来建立连接,它直接把查询报文“扔”出去。这减少了1.5个RTT(网络往返时延),对于DNS这种对响应速度要求极高的服务来说,至关重要。
b. 资源开销小,性能高
- 轻量级:UDP的头部只有8个字节,相比TCP的20个字节(无选项时)要小得多,网络传输效率更高。
- 无状态:服务器端无需为每个查询维护一个复杂的连接状态,可以处理更高的并发请求。
如何应对UDP的不可靠?
- DNS的查询机制本身就考虑了UDP的丢包问题。如果客户端在指定时间内没有收到响应,它会自动进行重传。所以,偶尔的丢包是可以被应用层逻辑所容忍的。
2. 为什么在某些场景下必须使用TCP?(追求可靠性与完整性)
虽然UDP快,但它有两个致命的限制:不可靠和数据包大小有限(通常为512字节)。当遇到以下两种情况时,DNS就必须切换到更可靠的TCP协议上来:
a. 当DNS响应报文过大时
- 场景:如果一个域名的解析结果非常复杂,比如它包含了大量的A记录、CNAME记录,或者在DNSSEC(DNS安全扩展)场景下,响应报文的大小超过了512字节。
- 切换机制:
- 此时,DNS服务器无法通过一个UDP包将所有数据发回。它会在UDP响应中,设置一个 “TC”(Truncated,即截断) 的标志位,并且只返回部分数据。
- 当客户端收到这个带有TC标志的响应后,它就会明白“数据没发完”。
- 然后,客户端会自动地重新发起一次查询,但这次会通过TCP来建立一个可靠的连接,以确保能够接收到完整的、未被截断的响应数据。
b. 进行“区域传送”(Zone Transfer)时
- 场景:这是主DNS服务器和辅DNS服务器之间进行数据同步的一种机制。
- 为什么必须用TCP?
- 数据量大:区域传送需要传输整个域的完整数据,数据量远超512字节。
- 绝对可靠:主从数据同步,要求数据必须是完整、准确、不丢包的。UDP的不可靠性在这里是完全不能接受的。
- 因此,所有的区域传送,都必须通过TCP连接来进行。
总结一下:
DNS的设计非常务实。它在日常的、大量的、小数据包的查询中,选择了UDP来追求极致的速度和效率;同时,它又保留了在大数据量传输和高可靠性要求的场景下,“升级”到TCP的能力。这是一个非常经典的、根据场景选择最合适协议的案例。
HTTP是不是无状态的?
面试官您好,您提出的这个问题非常核心。最准确的回答是:HTTP协议本身,在设计上是完全无状态的(Stateless)。但是,为了满足现代Web应用的业务需求,我们通过一些额外的机制,在应用层面实现了状态的维持。
1. HTTP的无状态本质 (What & Why)
它是什么?
- “无状态”意味着服务器不会记录任何关于客户端上一次请求的信息。每一个HTTP请求,对于服务器来说,都是一个全新的、独立的事件,它与其他任何请求都没有关联。服务器处理完请求后,就会“忘记”这个客户端的一切。
为什么当初要这样设计?
- 核心目标:简化协议,实现极高的可扩展性。
- 在互联网早期,Web的主要功能是浏览静态文档。一个无状态的服务器,不需要维护大量的客户端状态信息,这使得它的实现非常简单。
- 更重要的是,这使得服务器能够轻松地进行水平扩展。因为任何一台服务器都可以处理任何一个客户端的请求,我们可以在后端部署成千上万台无差别的服务器,通过负载均衡来分发请求,从而支撑海量的访问。如果服务器需要维护状态,扩展就会变得极其复杂。
2. 无状态带来的挑战
- 然而,随着Web应用的发展,我们需要处理像用户登录、购物车这样需要“记住”用户状态的业务。
- 如果完全无状态,那么用户每访问一个新页面,都需要重新登录一次;每往购物车里加一件商品,之前的商品就会被“忘记”。这在业务上是不可接受的。
3. 如何“维持”状态?—— Cookie与Session的协同工作
为了解决这个矛盾,Web开发者们发明了一套非常经典的机制,来在无状态的HTTP协议之上,构建一个“有状态”的会话。这个机制的核心,就是Cookie和Session的配合。
- 一个典型的登录流程:
- 第一次请求(登录):用户在登录页面输入用户名和密码,发送给服务器。
- 服务器创建Session:服务器验证用户名密码成功后,它会在自己这边(通常是在内存或Redis中),创建一个Session对象。这个Session对象就像一个专属的“储物柜”,里面存放着该用户的登录状态、购物车信息等。同时,服务器会为这个Session生成一个全局唯一的ID(Session ID)。
- 服务器通过Cookie下发“钥匙”:服务器在返回登录成功的HTTP响应时,会在响应头中,通过
Set-Cookie
字段,将这个独一无二的Session ID,像一张“储物柜的钥匙”,发送给客户端的浏览器。 - 浏览器保存Cookie:浏览器收到这个Cookie(即Session ID)后,会自动地将它保存在本地。
- 后续的每一次请求:在此之后,浏览器向该域名发起的所有HTTP请求,都会自动地在请求头中,带上这个Cookie(
Cookie: sessionId=...
)。 - 服务器识别身份:服务器在收到后续请求时,会从请求头中解析出这个Session ID,然后用它去自己那边的“储物柜区域”查找,找到对应的Session对象。这样,它就 “认出” 了是哪个用户,并可以获取到该用户的登录状态等信息,从而实现了状态的维持。
总结一下:
- HTTP协议本身,始终是无状态的。服务器并没有去“记住”某个TCP连接或IP地址的状态。
- 所谓的“状态”,实际上是服务器通过Session存储在自己这边,然后通过Cookie这张“身份证”,让客户端在每次请求时自报家门,从而实现了对用户状态的识别和跟踪。
这种设计,既保留了HTTP无状态带来的高可扩展性的优点,又通过Cookie和Session,满足了现代Web应用对会话保持的需求,是一个非常经典和优雅的工程解决方案。
携带Cookie的HTTP请求是有状态还是无状态的?Cookie是HTTP协议簇的一部分,那为什么还说HTTP是无状态的?
面试官您好,您提出的这个问题非常棒,它直击了HTTP协议设计哲学的一个核心。
最精确的答案是:即使一个HTTP请求携带了Cookie,HTTP协议本身,从根本上来说,依然是无状态的。
要理解这个看似矛盾的结论,我们需要弄清楚“状态”到底是由谁来维护的。
一个生动的比喻:健忘的图书管理员
我们可以把HTTP服务器想象成一个记忆力极差、“脸盲”且“健忘” 的图书管理员。
无状态的本质:这个管理员不会去记任何一个读者的脸,也不会记得任何一个读者之前借了什么书。每次有读者来找他,对他来说都是一个全新的、独立的事件。他处理完这次借阅后,立刻就会把这个读者忘得一干二净。这就是HTTP的无状态性。
Cookie扮演的角色:一张“借书卡”
- 现在,为了能提供持续的服务(比如记录借阅历史),图书馆想出了一个办法:给每个读者都办一张借书卡(Cookie),卡上有一个独一无二的编号(Session ID)。
- 图书馆要求:“我(管理员)不负责记你,但你每次来找我,都必须出示你的借书卡。”
- 工作流程:
- 读者第一次来借书,管理员办完手续,给了他一张借书卡。
- 读者第二次来,他必须主动出示这张借书卡。
- 管理员拿到卡,虽然他还是不认识这个读者,但他可以根据卡上的编号,去后台的档案柜(服务器端的Session存储) 里,查到这个读者的所有借阅记录。
- 办完这次手续,管理员再次忘记了这个读者,他只认卡不认人。
为什么说HTTP依然是无状态的?
从这个比喻中,我们可以清晰地看到:
- 服务器自身没有“记忆”:服务器(图书管理员)并没有在其内部状态中,去主动地、持久地关联某个客户端(读者)的身份。它处理完一个请求后,不会保留任何关于这个客户端连接或会话的上下文信息。
- 状态信息是由客户端“携带”并“提醒”服务器的:是客户端(浏览器)在每一次请求中,都主动地、重复地通过Cookie,将自己的“身份标识”告诉服务器。
- 每个请求都是“自包含”的:服务器处理任何一个请求,所需要的全部信息,要么包含在请求本身之内(如URL、Body),要么通过Cookie这样的机制由客户端提供。它无需去回忆“这个客户端上一次干了什么”。
总结一下:
- HTTP协议,作为一套通信的规则,它本身的设计是不包含任何状态维持机制的。
- Cookie,虽然是HTTP协议簇的一部分(定义在RFC规范中),但它是一种允许服务器向客户端“寄存”少量数据的机制。
- 我们利用Cookie这种机制,在应用层面构建了一套“会话保持”的方案。但这并没有改变HTTP协议本身 “每个请求都独立处理,服务器不保留上下文” 的无状态核心。
所以,可以说,我们是在一个无状态的协议上,通过一个有状态的“信物”(Cookie),实现了一个看起来“有状态”的应用会话。协议的本质,并未改变。
Cookie,Session,Token的区别?
面试官您好,Cookie、Session和Token,都是我们在Web开发中,为了解决HTTP无状态问题,而采用的用户身份认证和状态保持的技术。它们代表了三种不同时期、不同设计思想的解决方案。
我通常会按照它们的演进关系来介绍:
1. Cookie:最早的、客户端的“身份证”
- 它是什么? Cookie是服务器发送到客户端浏览器,并由浏览器保存在本地的一小段文本数据。
- 工作原理:当浏览器再次向同一个服务器发送请求时,它会自动地将这个Cookie附加在HTTP请求头中,一并发送过去。
- 优点:实现简单,是HTTP协议原生支持的。
- 缺点(正如您分析的):
- 不安全:数据直接暴露在客户端,容易被窃取和篡改。
- 容量和数量有限:单个Cookie通常不能超过4KB,且每个域名下的Cookie数量也有限制。
- 增加网络开销:每次请求都会携带,如果Cookie内容多,会增加不必要的网络流量。
- 结论:由于其不安全性,我们绝对不会用Cookie来直接存储用户的敏感信息。它更多地是扮演一个“信使”或“ID卡”的角色。
2. Session:服务端的“档案柜”,对Cookie的改进
为了解决Cookie不安全的问题,Session机制应运而生。
- 它是什么? 正如您所说,Session的数据是存储在服务器端的。它就像是服务器为每个用户都准备了一个专属的“档案柜”。
- 如何与Cookie协同工作?
- 当用户第一次访问或登录时,服务器会创建一个Session对象,并生成一个全局唯一的Session ID。
- 服务器将这个Session ID,通过Cookie的方式,发送给客户端浏览器(这张Cookie就像是“档案柜的钥匙”)。
- 之后,浏览器每次请求,都会自动带上这个存有Session ID的Cookie。
- 服务器接收到请求后,根据Cookie中的Session ID,就能找到对应的Session“档案柜”,从而获取到用户的状态信息。
- 优点:
- 安全性更高:敏感数据都保存在服务器端,客户端只持有一个无意义的ID。
- 缺点:
- 服务器资源开销:每个用户的Session都需要在服务器端占用内存。当在线用户数量巨大时,这对服务器是一个不小的负担。
- 不适合分布式/集群扩展:这是一个核心痛点。如果一个用户的Session存储在服务器A上,当他的下一个请求被负载均衡到服务器B上时,服务器B是找不到这个Session的。要解决这个问题,就需要实现Session共享,比如通过Session复制、或者将Session集中存储到Redis等,这都增加了架构的复杂度。
3. Token:现代化的、无状态的“通行令牌”
为了解决Session机制在分布式和跨域场景下的弊端,基于Token的认证方式成为了现代Web开发(特别是前后端分离、微服务架构)的主流。
- 它是什么? Token是一个加密的、包含了用户身份信息的字符串。它本身是无状态的。
- 工作原理:
- 用户登录成功后,服务器会根据用户信息,并加上一个密钥(secret),通过某种加密算法(如HMAC-SHA256),生成一个Token字符串,然后返回给客户端。
- 客户端收到Token后,通常会将其存储在本地(如
localStorage
)。 - 之后,客户端在每一次请求的HTTP头部(通常是
Authorization
头,以Bearer
为前缀),手动地带上这个Token。 - 服务器收到请求后,会用同样的密钥和算法,来验证这个Token的签名是否有效。如果验证通过,就确信这个用户是合法的,并可以从Token中解析出用户信息,无需再查询数据库或Session。
- 优点:
- 无状态,易于扩展:服务器端无需存储任何Session信息,这使得后端服务可以无限地水平扩展。任何一台服务器,只要拥有相同的密钥,就能验证同一个Token。
- 跨域与跨平台支持好:由于Token是通过HTTP头部传输,它没有Cookie的跨域限制,非常适合用于前后端分离的SPA(单页应用)、移动App等场景。
- 安全性高:Token的签名机制,可以有效防止数据被篡改。
- 缺点:
- 无法主动失效:由于Token是无状态的,一旦签发,在它过期之前,服务器端就无法主动让它失效。如果Token被泄露,就需要引入更复杂的黑名单机制。
- 体积可能稍大:如果往Token里存放了过多的信息(claims),可能会比单纯的Session ID要大一些。
总结对比
特性 | Cookie | Session | Token |
---|---|---|---|
存储位置 | 客户端 | 服务端 | 客户端 |
状态 | 有状态(在客户端) | 有状态(在服务端) | 无状态(服务端不存) |
依赖关系 | 无 | 依赖Cookie传递ID | 无 |
可扩展性 | - | 差(需Session共享) | 好 |
适用场景 | 简单数据存储 | 传统的、有状态的Web应用 | 前后端分离、微服务、移动App |
在我的实践中,对于传统的、紧密耦合的Web应用,可能会使用Session。但对于所有现代的、前后端分离或微服务的项目,我都会优先选择基于JWT(JSON Web Token)的Token认证方案。
如果客户端禁用了Cookie,Session还能用吗?
面试官您好,您提出的这个问题非常好,它触及了Web早期会话管理的一个核心依赖关系。
直接的答案是:在默认配置下,如果客户端禁用了Cookie,Session机制就会失效,无法正常使用。
1. 为什么会失效?—— Session对Cookie的依赖
要理解这一点,我们需要回顾一下标准的Session工作流程:
- 服务器端:当一个用户首次访问时,服务器会为他创建一个Session对象,并生成一个全局唯一的Session ID来标识它。
- 传递ID:服务器需要一种方式,把这个Session ID传递给客户端浏览器,以便浏览器在后续的请求中能“自报家门”。
- 默认机制:在绝大多数Web服务器(如Tomcat)的默认实现中,这个传递Session ID的唯一载体,就是HTTP Cookie。服务器会通过
Set-Cookie
响应头,将Session ID写入一个特殊的Cookie中(通常叫JSESSIONID
)。 - 后续请求:浏览器在后续的每次请求中,都会自动带上这个
JSESSIONID
的Cookie。服务器就是通过读取这个Cookie中的ID,来找到对应的Session数据。
所以,一旦客户端禁用了Cookie,这个传递“身份证”(Session ID)的通道就被切断了。服务器无法将会话标识发给客户端,客户端也无法在后续请求中携带它,服务器自然就无法识别这是哪个用户的会话了。
2. 有没有绕过的办法?—— “把身份证写在别的地方”
虽然默认机制失效了,但为了兼容那些极少数禁用Cookie的“古老”浏览器或特殊客户端,Web开发者们也设计出了一些“备用方案”。主要有两种:
方案一:URL重写 (URL Rewriting)
- 做法:这是一种“把身份证号直接贴在衣服上”的办法。服务器在生成所有返回给客户端的HTML页面时,会动态地将Session ID,作为参数追加到页面上所有的URL链接的末尾。
- 示例:一个链接从
href="/products/123"
变成了href="/products/123;jsessionid=ABCDEF123456"
。 - 缺点(非常明显):
- URL不美观、不友好。
- 安全性差:如果用户将这个带有Session ID的URL复制、分享给其他人,或者这个URL被搜索引擎收录,就会导致Session ID泄露,其他人就可以冒用这个用户的身份,这就是会话固定(Session Fixation) 攻击。
- 实现复杂:需要服务器端对所有输出的URL进行处理。
方案二:隐藏表单字段 (Hidden Form Fields)
- 做法:这是一种“把身份证藏在信封里”的办法。在每个需要维持会话的HTML表单中,都插入一个
<input type="hidden">
字段,其value
就是Session ID。 - 示例:
<input type="hidden" name="jsessionid" value="ABCDEF123456">
- 缺点:
- 适用范围极其有限:它只对通过表单提交(
POST
) 的请求有效。对于普通的链接点击(GET
请求)或Ajax请求,就无能为力了。
- 适用范围极其有限:它只对通过表单提交(
- 做法:这是一种“把身份证藏在信封里”的办法。在每个需要维持会话的HTML表单中,都插入一个
3. 现代Web开发的视角
在今天的Web开发实践中:
- Cookie基本是必需品:现代Web应用的功能,已经高度依赖于Cookie。禁用Cookie的用户,会发现绝大多数网站都无法正常登录或使用。
- Token认证的兴起:在前后端分离的架构中,我们更多地使用基于Token的无状态认证。虽然Token也通常存储在客户端(比如
localStorage
),并且需要客户端通过HTTP头部手动附加,但它从设计上就摆脱了对浏览器自动发送Cookie的依赖,更加灵活。
总结一下,虽然理论上可以通过URL重写等“奇技淫巧”来在禁用Cookie的情况下维持Session,但这些方案都存在严重的安全和可用性问题,在现代开发中几乎不再使用。因此,我们可以认为,在正常的、安全的Web应用中,Session的有效工作,是强依赖于客户端开启Cookie支持的。
如果我把数据存储到 localStorage,和Cookie有什么区别?
面试官您好,localStorage
和Cookie
都是浏览器提供的、用于在客户端本地存储数据的技术,但它们的设计目的、工作机制和特性截然不同,适用于完全不同的场景。
核心区别对比
正如您所分析的,它们的主要区别体现在以下几个方面:
特性 | Cookie | localStorage |
---|---|---|
1. 存储位置与目的 | 客户端,但设计初衷是为了与服务器通信 | 纯粹的客户端存储 |
2. 与服务器的交互 | 自动发送:每次HTTP请求,都会自动附加在请求头中发送给服务器。 | 不发送:数据仅存在于本地,除非你用JS手动取出并放入请求。 |
3. 存储容量 | 非常小 (约4KB) | 较大 (通常5-10MB,因浏览器而异) |
4. 生命周期 | 可配置:可设置过期时间,过期后自动删除;若不设置,则为会话Cookie,浏览器关闭时删除。 | 永久性:数据会一直存在,除非被用户手动清除浏览器缓存,或被JS代码显式删除。它不受浏览器关闭的影响。 |
5. API易用性 | 较差:原生JS操作Cookie比较繁琐,需要自己封装或使用库。 | 非常好:提供了非常简洁的同步API,如 localStorage.setItem() , localStorage.getItem() , localStorage.removeItem() 。 |
6. 安全性 | 较低:易受CSRF攻击,且因为随请求发送,可能暴露给中间人。 | 较高:数据不离开客户端,不易被网络窃听,但仍需防范XSS攻击。 |
一个生动的比喻:钱包里的“身份证” vs “储物柜”
- Cookie:就像是你钱包里的 “身份证”。
- 每次你进出大楼(向服务器发请求),保安(服务器)都会要求你出示一下。它体积小,方便携带,主要用来证明你的身份。
- localStorage:就像是你在大楼里租的一个 “私人储物柜”。
- 你可以把很多东西(大量数据)长期地存放在里面。这个储物柜是你私人的,你每次进出大楼,不需要把储物柜里的所有东西都抱出来给保安看。只有当你自己需要用的时候,才用钥匙去打开它。
选型与适用场景
基于以上区别,它们的适用场景非常明确:
什么情况下使用 Cookie?
- 核心场景:维持HTTP会话,身份认证。 最典型的就是存储服务器下发的Session ID或Token。因为Cookie有“自动发送”的特性,所以它能无缝地帮助服务器识别用户身份。
- 一些小量的、需要在前后端共享的状态信息。
什么情况下使用 localStorage?
- 核心场景:在客户端长期存储大量、非敏感的数据。
- 具体例子:
- 缓存一些不常变化的、但体积较大的静态资源或应用配置数据,以减少网络请求。
- 保存用户在前端的一些个性化设置,比如主题偏好、草稿箱内容等。
- 在单页应用(SPA)中,持久化一些应用状态,即使用户关闭了浏览器再打开,状态依然存在。
补充:与sessionStorage
的区别
- 值得一提的是,还有一个
sessionStorage
,它的API和localStorage
几乎一模一样。 - 唯一的区别在于生命周期:
sessionStorage
中存储的数据,是会话级别的。一旦用户关闭了浏览器标签页或浏览器,sessionStorage
中的数据就会被自动清除。它非常适合用来存储一些一次性会话的临时数据。
总结一下,我会根据数据的用途、大小、生命周期要求来做选择:
- 需要与服务器交互、用于身份认证的,用Cookie。
- 需要在客户端长期存储大量数据的,用localStorage。
- 只需要在单次会话期间临时存储数据的,用sessionStorage。
什么数据应该存在到Cookie,什么数据存放到 LocalStorage
面试官您好,当我们需要在客户端存储数据时,选择Cookie还是LocalStorage,我的决策过程主要基于对这份数据特性的分析。我会问自己几个关键问题:
问题一:这份数据,是否需要在每一次HTTP请求中,都自动地、无差别地发送给服务器?
是 -> 必须使用 Cookie
- 典型数据:用于身份认证的Session ID或Token。
- 为什么? 因为HTTP是无状态的,服务器需要一种方式来识别每一个请求来自哪个用户。Cookie的 “自动发送” 特性,完美地满足了这个需求。浏览器会自动地在每个请求的
Cookie
头中带上这个身份标识,我们无需手动处理。这是Cookie最核心、最不可替代的用途。
否 -> 优先考虑 LocalStorage
- 典型数据:用户在前端的个性化设置(如主题色、字体大小)、未提交的表单草稿、一些不常变化的应用配置信息或静态资源(如Base64编码的小图片)。
- 为什么? 这些数据纯粹是给前端自己使用的,服务器完全不关心。如果把它们放在Cookie里,每次请求都带着这些“累赘”数据,会白白地增加网络开销,降低性能。LocalStorage则完美地避免了这一点,它只是一个安靜的、纯粹的客户端仓库。
问题二:这份数据的敏感性如何?
敏感数据(如用户凭证)
- 虽然身份令牌通常放在Cookie中,但我们必须对其进行安全加固。比如,服务器在下发Cookie时,应设置
HttpOnly
属性,来防止JavaScript脚本读取,从而抵御XSS攻击。同时,也需要配合后端的CSRF Token等机制来防范CSRF攻击。
- 虽然身份令牌通常放在Cookie中,但我们必须对其进行安全加固。比如,服务器在下发Cookie时,应设置
非敏感数据
- LocalStorage相对更安全一些,因为它不参与网络通信。但它同样可能受到XSS攻击(JS可以直接读写),所以也不应该存放极其敏感的信息(如明文密码)。
问题三:这份数据的生命周期要求是怎样的?
需要精确的、自动的过期控制 -> 使用 Cookie
- 典型数据:比如“7天免登录”功能。
- 为什么? Cookie可以非常方便地设置一个
Expires
或Max-Age
属性,浏览器会自动地在指定时间后将其删除。
需要“永久”存储,直到用户或代码主动清除 -> 使用 LocalStorage
- 典型数据:用户的个性化主题偏好。
- 为什么?
localStorage
的数据不会因为浏览器关闭而消失,它会一直存在,除非被手动清除。这非常适合存储那些希望长期保留的用户设置。
问题四:这份数据的大小如何?
- 非常小(小于4KB) -> Cookie和LocalStorage都可以。
- 比较大(KB到MB级别) -> 只能使用 LocalStorage
- 为什么? Cookie有严格的大小限制(约4KB),而LocalStorage的容量要大得多(通常5-10MB),可以存储更大量的数据。
总结决策流程
所以,我的决策流程是这样的:
- 这个数据是用来给服务器做身份认证的吗?
- 是 -> 用 Cookie,并做好安全设置 (
HttpOnly
,Secure
,SameSite
)。 - 否 -> 进入下一步。
- 是 -> 用 Cookie,并做好安全设置 (
- 这个数据需要在浏览器关闭后依然保留吗?
- 是 -> 用 localStorage。
- 否(我只想在当前这次会话中临时用一下)-> 用 sessionStorage(这是另一个选择,生命周期是会话级别)。
通过这个清晰的决策流程,我们就能为不同类型的数据,选择最合适的客户端存储方案。
JWT令牌和传统方式有什么区别?
面试官您好,JWT(JSON Web Token)是一种现代的、轻量级的身份认证和授权规范。它与传统的、基于Session-Cookie的认证方式,在设计哲学和实现机制上,有着根本性的区别。
传统Session-Cookie方式:有状态的、依赖服务端的“身份证”模式
- 工作原理:服务器为每个登录用户创建一个Session(存储在服务端),并把一个与之对应的Session ID通过Cookie发送给客户端。客户端后续每次请求都携带这个ID,服务器通过ID来查找Session,从而识别用户。
- 核心特点:有状态 (Stateful)。用户的认证状态,是保存在服务器端的。
JWT令牌方式:无状态的、自包含的“数字签名通行证”
JWT则完全不同。它是一个自包含(Self-Contained)的、带有数字签名的字符串。这个字符串本身,就包含了所有必要的认证信息。
一个JWT通常由三部分组成,用.
隔开:Header.Payload.Signature
- Header(头部):包含了令牌的类型和所使用的加密算法。
- Payload(载荷):包含了声明(Claims),也就是我们想传递的数据,比如用户ID、用户名、角色、过期时间等。
- Signature(签名)。这是JWT安全性的核心。它是通过将Header和Payload,用一个只有服务器知道的密钥(secret),进行加密计算得到的。
JWT与传统方式的核心区别与优势
这种“无状态、自包含”的设计,带来了几大革命性的优势:
1. 无状态性与强大的可扩展性
- 传统方式的痛点:Session存储在单个服务器上,这在分布式或微服务架构中,会带来Session共享的难题。需要引入Redis等额外组件来集中存储Session,增加了架构复杂度。
- JWT的优势:服务器端完全不需要存储任何会话信息。任何一台服务,只要拥有相同的密钥,就能独立地验证一个JWT的有效性。这使得后端服务可以无限地水平扩展,完美契合了微服务和无状态架构。
2. 更高的安全性
- JWT的签名机制:由于Signature是使用服务器的私有密钥生成的,所以它可以有效防止数据被篡改。任何对Header或Payload的修改,都会导致最终计算出的签名与原始签名不匹配,从而验证失败。
- 防范CSRF攻击:传统基于Cookie的认证,容易受到CSRF(跨站请求伪造)攻击。而JWT通常是放在HTTP的
Authorization
头部中传输的,并且需要前端JS代码手动添加,它不依赖于浏览器的Cookie自动发送机制,因此天然地就能在很大程度上防范CSRF攻击。
3. 天生的跨域支持与多平台适用性
- 传统方式的痛点:Cookie存在跨域(CORS)限制,在一个域下设置的Cookie,无法被另一个域访问,这在前后端分离、主域名与子域名共存的场景下非常麻烦。
- JWT的优势:JWT不依赖Cookie。它可以轻松地在任何域、任何平台(Web、移动App、桌面应用)之间传递,因为它只是一个标准的字符串,可以通过HTTP头部、URL参数、POST请求体等任何方式传输。
JWT带来的新挑战
当然,JWT也并非银弹,它也带来了一些新的挑战:
- 令牌无法主动失效:由于其无状态性,一旦一个JWT被签发,在它过期之前,服务器端就无法主动地让它作废。如果一个用户的Token被泄露,攻击者就可以一直使用它直到过期。要解决这个问题,就需要引入Token黑名单机制,但这又在一定程度上违背了“无状态”的初衷。
- 续签问题:如何优雅地、安全地为用户刷新即将过期的Token(Refresh Token机制),也需要一套相对复杂的逻辑。
总结一下,JWT通过一种无状态、自包含、带签名的令牌机制,完美地解决了传统Session-Cookie模式在分布式扩展、安全性和跨域方面的核心痛点,是现代Web应用(特别是前后端分离和微服务架构)中,进行身份认证和授权的事实标准。
JWT 令牌为什么能解決集群部署,什么是集群部署?
面试官您好,您提出的这个问题,直击了现代Web架构从单体走向分布式过程中,身份认证方式演进的核心驱动力。
1. 首先,什么是集群部署?
集群部署,简单来说,就是为了应对高并发、保证高可用,我们将同一个应用程序,同时部署在多台服务器上,然后通过一个负载均衡器(Load Balancer),将用户的请求分发到这些不同的服务器实例上去处理。
2. 传统Session-Cookie方式在集群中的“困境”
在单机时代,传统的Session-Cookie方式工作得很好。但一旦进入集群部署,它就遇到了一个致命的难题——Session共享。
问题根源:Session是有状态的,它默认被存储在单个服务器的内存中。
一个生动的比喻:多位“健忘的”图书管理员
- 我们把集群中的每一台服务器,都想象成一个“健忘的”图书管理员。
- 场景:
- 读者张三的第一次借书请求,被负载均衡器分发给了管理员A。管理员A为他办理了手续,创建了一份借阅档案(Session),并给了他一张借书卡(Cookie里的Session ID)。这份档案,只存在于管理员A的抽屉里。
- 张三的第二次借书请求,被负载均衡器“随机”地分发给了管理员B。
- 张三向管理员B出示了他的借书卡。
- 困境出现:管理员B拿着这张卡,去自己的抽屉里查找,结果发现根本没有这张卡的档案!因为档案还在管理员A那里。
- 于是,管理员B只能认为张三是一个“新读者”,要求他重新办卡、重新登录。用户的登录状态就丢失了。
传统的解决方案:为了解决这个问题,我们需要让所有管理员都能访问到同一份档案。常见的方案有:
- Session复制:每当一个管理员更新了档案,就立刻复印一份,同步给所有其他管理员。缺点:同步开销大,网络延迟高。
- Session粘滞(Sticky Session):让负载均衡器变得“智能”,记住张三总是由管理员A服务的,以后所有张三的请求都只发给A。缺点:破坏了负载均衡,且一旦服务器A宕机,用户的会话依然会丢失。
- Session集中存储:这是最常用的方案。我们搞一个中央档案室(比如一台Redis服务器),所有管理员的档案都存放在这里。缺点:增加了架构的复杂度,并引入了一个新的单点依赖(Redis也需要做高可用)。
3. JWT如何优雅地解决这个问题?—— “把档案信息直接写在身份证上”
JWT(JSON Web Token)的出现,提供了一种完全不同的、无状态的解决思路。
核心思想:正如您所说,JWT是自包含(Self-Contained) 的。它不再需要服务器端存储任何Session信息。
比喻升级:
- 现在,我们不再给读者办“借书卡”了,而是给他发一张带有“数字签名”的、加密的“电子身份证”(JWT)。
- 这张电子身份证上,直接就写清楚了:“姓名:张三,ID:123,权限:可借阅所有书籍,有效期至:明天”。
- 工作流程:
- 张三的任何一次请求,无论被分发给管理员A、B还是C,他只需要出示这张电子身份证即可。
- 任何一个管理员拿到这张证件,无需去查任何档案柜。他只需要检查一下证件上的数字签名是否伪造,以及证件是否过期。
- 只要验证通过,他就完全信任这张证件上写的所有信息,并据此提供服务。
JWT的优势:
- 无状态,易于扩展:服务器端无需共享任何会话状态。我们可以无限地增加服务器实例(管理员),而无需对认证系统做任何改动。
- 架构简化:不再需要Session复制、粘滞会话,也不需要一个额外的集中式Session存储,大大降低了架构的复杂性和运维成本。
- 解耦:认证逻辑与业务逻辑服务器的状态完全解耦。
总结一下,JWT通过将认证信息和用户状态“自包含”在令牌本身,并用数字签名来保证其不可篡改,从而彻底摆脱了对服务端Session存储的依赖。这使得它能够完美地适应无状态、可水平扩展的集群和微服务架构,是现代分布式系统身份认证的事实标准。
JWT 令牌如果泄露了,怎么解决,JWT是怎么做的?
面试官您好,这个问题,直击了JWT(JSON Web Token)设计哲学中的一个核心权衡点。
JWT最大的优点是“无状态”,但这也正是它最大的“缺点”来源:一旦签发,在它过期之前,服务器端默认是无法主动让它失效的。
1. 问题的根源:无状态的设计
- 传统Session方式:如果一个用户的Session ID泄露了,我们只需要在服务器端的Session存储(如Redis)中,直接删除这个Session记录,那么这个Session ID就立刻作废了。
- JWT方式:JWT的验证,是不依赖任何服务端存储的。服务器只根据预置的密钥,来验证JWT的签名是否合法、是否过期。只要一个泄露的JWT,其签名正确且未到期,对于服务器来说,它就是一个完全合法的令牌。
所以,当JWT泄露时,我们就必须引入一些额外的机制,来“打破”这种纯粹的无状态,从而实现令牌的失效。
2. 解决方案
正如您所分析的,主要有以下几种方案:
方案一:缩短令牌有效期(预防为主)
- 做法:这是最简单、最基础的防御手段。我们可以将访问令牌(Access Token)的有效期设置得非常短,比如5到15分钟。
- 效果:这样,即使一个Token被泄露了,攻击者能够利用它的时间窗口也非常有限,大大降低了风险。
- 带来的问题:用户需要频繁地重新登录,体验不佳。这就引出了第二种方案。
方案二:引入刷新令牌(Refresh Token)机制(业界标准实践)
- 这是一个 “长短令牌结合” 的方案。
- 工作流程:
- 用户登录时,服务器会同时签发两个Token:
- 一个短有效期的
Access Token
(如15分钟),用于日常的API访问。 - 一个长有效期的
Refresh Token
(如7天或更长),它只用于一个目的——获取新的Access Token
。
- 一个短有效期的
- 客户端会将
Access Token
和Refresh Token
都保存起来。 - 当
Access Token
过期后,客户端会带着这个Refresh Token
,去访问一个专门的“刷新”接口。 - 服务器验证
Refresh Token
的有效性,如果通过,就签发一个新的Access Token
给客户端,实现“无感续签”。
- 用户登录时,服务器会同时签发两个Token:
- 如何解决泄露问题?
- 日常API请求只使用短期的
Access Token
,泄露风险窗口小。 Refresh Token
只在刷新时使用一次,并且服务器可以在服务端记录和管理每一个Refresh Token
的状态。- 当我们想让一个用户强制下线时(比如用户修改了密码,或者我们检测到泄露),我们只需要在服务端将他对应的那个
Refresh Token
给失效掉即可。 这样,他就再也无法获取到新的Access Token
了。
- 日常API请求只使用短期的
方案三:建立黑名单机制(主动失效)
- 这是一种更直接的“吊销”方案。
- 做法:正如您所说,我们在服务端,通常是利用Redis,来维护一个 “JWT黑名单”。
- 工作流程:
- 当我们想让某个JWT立即失效时(比如用户点击“退出登录”),我们就将这个JWT的唯一标识(比如它的
jti
声明)以及它的过期时间,存入Redis的黑名单中。 - 在我们的API网关或认证中间件中,每次接收到一个JWT,除了进行常规的签名和过期验证外,还需要额外地去查询一下Redis黑名单,看这个JWT是否已被吊销。
- 当我们想让某个JWT立即失效时(比如用户点击“退出登录”),我们就将这个JWT的唯一标识(比如它的
- 优缺点:
- 优点:能够实现令牌的立即、主动失效。
- 缺点:这在一定程度上破坏了JWT的“无状态” 优点。每一次请求都需要额外地查询一次Redis,增加了延迟和系统复杂度。
总结与选型
- 缩短
Access Token
有效期:是必须采取的基础安全措施。 - 刷新令牌(Refresh Token)机制:是现代JWT认证体系的标配,它在用户体验和安全性之间取得了很好的平衡。
- 黑名单机制:是一个可选的、增强的安全手段。对于那些需要支持“强制下线”、“单点登录”等高级功能的、对安全性要求极高的系统,可以引入黑名单。
在实践中,我们通常会采用 “短Access Token + 长Refresh Token + 可选的黑名单” 这套组合拳,来构建一个既安全又灵活的JWT认证系统。
前端是如何存储JWT的?
面试官您好,JWT通过一种无状态、自包含的方式,完美地解决了传统Session在分布式和跨域场景下的难题。
当服务器签发一个JWT给前端后,前端如何安全、有效地存储这个Token,就成了一个非常关键的问题。主要有三种存储位置,它们各有优劣。
1. 存储在localStorage
中 (最常见的方式)
- 如何做?
- 通过
localStorage.setItem('jwt_token', token)
将Token存入。 - 在每次发起API请求时,需要手动地从
localStorage
中取出Token,并放入HTTP的Authorization
请求头中。通常这会封装在axios或fetch的请求拦截器里。
- 通过
- 优点:
- 容量大:
localStorage
的存储空间较大(通常5-10MB),足以存放Token。 - API简单:
setItem
,getItem
API简单易用。 - 不自动发送,无CSRF风险:它不会像Cookie一样被浏览器自动附加在请求中,因此天然地能够防范CSRF(跨站请求伪造)攻击。
- 容量大:
- 缺点(致命):
- 易受XSS攻击:
localStorage
可以被任何同源的JavaScript代码访问到。如果网站存在XSS(跨站脚本攻击)漏洞,攻击者注入的恶意脚本,就可以轻松地读取到localStorage
中的Token,然后冒用用户身份。
- 易受XSS攻击:
2. 存储在Cookie
中
- 如何做?
- 服务器在响应头中通过
Set-Cookie
将Token写入Cookie。浏览器会自动保存,并在后续请求中自动携带。
- 服务器在响应头中通过
- 优点:
- 使用方便:浏览器会自动管理,无需JS手动处理。
- 可防范XSS:如果服务器在设置Cookie时,加上了
HttpOnly
属性,那么这个Cookie就无法被JavaScript访问。这极大地提升了安全性,有效防止了因XSS攻击导致的Token泄露。
- 缺点:
- 易受CSRF攻击:正是因为Cookie的“自动发送”特性,使得它容易受到CSRF攻击。攻击者可以引诱用户在一个已登录的浏览器标签页中,去点击一个指向恶意网站的链接,这个恶意网站可以向我们的服务器发起伪造的请求,而浏览器会自动带上用户的Cookie,从而完成攻击。
- 要缓解CSRF,需要在服务器端配合实现CSRF Token或设置Cookie的
SameSite
属性为Strict
或Lax
。
3. 存储在sessionStorage
中
- 特点:它的API和
localStorage
完全一样,但生命周期不同。 - 生命周期:
sessionStorage
是会话级别的。一旦用户关闭了浏览器标签页或浏览器,其中存储的数据就会被自动清除。 - 优缺点:与
localStorage
类似,同样存在XSS风险,但因为关闭标签页就失效,其泄露的风险窗口更小。
总结与最佳实践选型
存储方式 | 优点 | 缺点 | 安全性 |
---|---|---|---|
localStorage |
容量大、API简单、防CSRF | 易受XSS攻击 | XSS: ❌, CSRF: ✅ |
Cookie |
使用方便、HttpOnly 可防XSS |
易受CSRF攻击、容量小、有跨域问题 | XSS: ✅, CSRF: ❌ |
sessionStorage |
同localStorage, 但生命周期短 | 同localStorage | XSS: ❌, CSRF: ✅ |
我的选型策略与最佳实践:
这是一个典型的 “安全权衡” 问题。XSS和CSRF是两种最主要的Web攻击方式,我们需要在这两者之间找到平衡。
如果安全是第一要务:我会选择将Token存储在
HttpOnly
的Cookie中。- 这样做,可以从根本上杜绝因XSS漏洞导致Token被盗的风险,这是最常见的Token泄露方式。
- 对于CSRF风险,我会在后端同时启用
SameSite=Strict
Cookie策略和基于表单的CSRF Token校验,来构建一个纵深防御体系。
如果是构建纯粹的前后端分离SPA应用,且跨域场景复杂:
- 很多开发者为了方便,会选择
localStorage
。 - 但如果选择这种方案,就必须将防范XSS攻击作为最高优先级。这意味着:
- 对所有用户输入进行严格的过滤和转义。
- 使用成熟的前端框架(如React, Vue),并遵循其安全指南,它们能自动处理大部分XSS问题。
- 实施严格的内容安全策略(CSP) 来限制脚本的执行。
- 很多开发者为了方便,会选择
总而言之,没有绝对安全的方案,只有更安全的组合策略。我个人更倾向于 HttpOnly
Cookie + CSRF防护的方案,因为它能更有效地保护Token本身不被窃取。
为什么有HTTP协议了,还要用RPC?
面试官您好,您提出的这个问题非常好,它触及了后端服务间通信技术选型的一个核心。
直接的答案是:HTTP和RPC并不是“替代”关系,而是两种定位不同、各有专长的通信方式。 它们在不同的场景下,分别解决了不同的问题。
1. 首先,澄清一个概念:RPC是什么?
正如您所分析的,RPC(Remote Procedure Call,远程过程调用)本身,并不是一个具体的协议,而是一种调用方式或编程模型。
- 它的核心目标:让开发者能够像调用本地方法一样,去调用一个远程服务器上的方法,而无需关心底层网络通信的复杂细节(如序列化、网络传输、反序列化等)。
- 具体的协议:我们常说的
Dubbo
,gRPC
,Thrift
,这些都是实现了RPC思想的具体框架和协议。它们可以选择不同的传输层协议(通常是TCP),以及不同的序列化方式(如Protobuf, Avro)。
2. 为什么有了HTTP,还需要RPC?—— “专业的人做专业的事”
HTTP协议,特别是HTTP/1.1,虽然非常通用,但在一些特定场景下,其性能和开发效率并不理想。RPC的出现,正是为了解决这些痛点。
对比维度 | HTTP (特别是/1.1) | RPC (如 gRPC, Dubbo) |
---|---|---|
1. 定位与应用场景 | “对外”通信,人机交互。主要用于浏览器与服务器之间(B/S),或者作为开放API,需要很好的可读性和通用性。 | “对内”通信,机器间交互。主要用于分布式、微服务架构下,服务与服务之间的高效调用。 |
2. 传输协议与效率 | 通常基于TCP。HTTP/1.1是文本协议,头部信息冗余,性能较低。 | 通常也基于TCP。但其上层协议通常是自定义的、高度优化的二进制协议。传输效率远高于HTTP/1.1。 |
3. 序列化方式 | 通常是JSON或XML等文本格式。可读性好,但性能差,序列化/反序列化开销大,体积也大。 | 通常使用Protobuf, Avro, Kryo等高性能的二进制序列化框架。性能极高,体积小,但可读性差。 |
4. 开发效率与服务治理 | HTTP接口通常需要手动定义URL、参数、返回值,并编写文档。服务治理能力(如服务发现、负载均衡、熔断)需要借助额外组件(如Spring Cloud)实现。 | RPC框架通常与IDL(接口定义语言)配合,可以自动生成客户端和服务端的代码桩(stub),开发体验非常流畅。并且,像Dubbo, gRPC这样的框架,原生就集成了丰富的服务治理能力。 |
3. 性能演进与未来趋势
- 在很长一段时间里,RPC的性能是远超HTTP/1.1的。这也是为什么绝大多数公司,在内部微服务之间,都会选择使用RPC框架。
- HTTP/2的崛起:
- HTTP/2通过引入二进制分帧、头部压缩、多路复用等技术,极大地提升了HTTP的性能,使其在很多方面已经不亚于甚至超过了一些传统的RPC框架。
- gRPC就是构建在HTTP/2之上的一个现代RPC框架,它完美地结合了RPC的开发体验和HTTP/2的高性能。
- 为什么RPC不会被完全取代?
- 历史原因:大量的现有系统已经在使用RPC,技术栈迁移成本巨大。
- 生态与服务治理:像Dubbo这样的成熟RPC框架,提供了极其完善和强大的服务治理能力,这是纯粹的HTTP调用+服务注册中心所无法比拟的。
- 专注性:RPC框架始终是为“服务间调用”这个特定场景而生的,其设计和优化都更具针对性。
总结一下,在我的技术选型中:
- 对外暴露的API,需要考虑通用性、易用性和跨平台性,我会选择基于HTTP/JSON的RESTful风格。
- 内部微服务之间的通信,对性能、开发效率、服务治理要求极高,我会毫不犹豫地选择高性能的RPC框架,比如Dubbo或gRPC。
HTTP和RPC将会在各自最擅长的领域,长期地共存和发展下去。
HTTP长连接与WebSocket有什么区别?
面试官您好,HTTP长连接和WebSocket虽然都是基于TCP协议的、并且都能保持长期的连接,但它们在通信模式、协议开销和应用场景上,有着根本性的区别。
我们可以把它们比作两种不同的 “对讲机”:
- HTTP长连接:就像一个 “一问一答” 的对讲机。必须我按住说话,你说完了,我才能按住说话。我不能在你说话的时候插嘴。
- WebSocket:则像一部 “双向电话”。我们两个人可以同时说话,互相都能听到,实现了真正的实时对话。
1. 通信模式的区别:半双工 vs. 全双工
这是两者最本质的区别。
HTTP/1.1长连接:
- 它是一种半双工(Half-duplex) 的通信模式。
- 它的本质,依然是严格的“请求-响应”模型。在一个TCP连接上,客户端发送一个请求,必须等待服务器的响应回来之后,才能发送下一个请求。
- 服务器永远无法主动向客户端推送数据,只能被动地等待客户端的“提问”。
WebSocket:
- 它是一种全双工(Full-duplex) 的通信模式。
- 一旦WebSocket连接建立成功,客户端和服务器之间就建立了一个平等的、双向的数据通道。
- 任何一方都可以在任何时候,主动地向对方发送数据,而无需等待对方的响应。
2. 连接建立方式的区别:直接建立 vs. “升级”建立
HTTP长连接:
- 连接的建立就是标准的TCP三次握手,然后就可以开始发送HTTP报文了。
WebSocket:
- 它的连接建立过程非常巧妙,它需要 “借用”HTTP协议来完成一次“升级”握手。
- 流程:
- 客户端首先会发送一个特殊的HTTP请求。这个请求的头部会包含
Upgrade: websocket
和Connection: Upgrade
等字段,明确告诉服务器:“我不仅仅是一个HTTP请求,我想把这个连接升级成WebSocket连接。” - 服务器如果同意升级,就会返回一个HTTP 101 Switching Protocols的状态码,表示“好的,我们切换协议吧!”
- 客户端首先会发送一个特殊的HTTP请求。这个请求的头部会包含
- 一旦这个“升级握手”完成,这条底层的TCP连接的“上层建筑”,就从HTTP协议,切换成了WebSocket协议。之后的所有通信,都将遵循WebSocket的数据帧格式,与HTTP再无关系。
3. 协议开销的区别
HTTP长连接:
- 虽然连接是复用的,但每一次请求和响应,都必须携带完整的、可能很庞大的HTTP头部。在频繁通信时,这部分开销不可忽视。
WebSocket:
- 只有在第一次握手时,需要借助HTTP协议,有一次HTTP头的开销。
- 一旦连接建立成功,后续传输的数据帧,其帧头非常小(最小只有2个字节),相比HTTP头要轻量得多,大大减少了协议开销。
总结与应用场景
特性 | HTTP长连接 | WebSocket |
---|---|---|
通信模式 | 半双工 (请求-响应) | 全双工 (双向实时) |
服务器主动推送 | 否 | 是 |
连接建立 | TCP握手 | TCP握手 + HTTP升级握手 |
协议开销 | 每次请求都有HTTP头,较大 | 只有握手时有,后续帧头极小 |
基于这些区别,它们的适用场景非常明确:
HTTP长连接:
- 适用于所有常规的、由客户端发起的资源请求场景。比如浏览网页、请求API数据等。
- 对于一些需要“服务器推送”的场景,可以通过长轮询、短轮询等技术来模拟,但效率和实时性都不如WebSocket。
WebSocket:
- 适用于那些需要高实时性、双向、频繁交互的场景。
- 正如您所说,典型的例子包括:
- 在线聊天室、即时通讯
- 实时数据推送(如股票行情、体育比分)
- 多人在线协作(如在线文档)
- 网页游戏
Nginx有哪些负载均衡算法?
面试官您好,Nginx作为业界最主流的高性能反向代理和负载均衡器,它提供了非常丰富和灵活的负载均衡算法(或称为策略),以适应不同的业务场景。
我通常会把这些算法分为两大类:Nginx内置的核心算法和需要额外配置或第三方模块支持的扩展算法。
一、 Nginx内置的核心算法
这些是Nginx开箱即用的、最基础也是最常用的算法。
1. 轮询 (Round Robin) —— 默认策略
- 工作原理:这是最简单、也是Nginx的默认负载均衡策略。它会按照
upstream
中配置的服务器列表顺序,将接收到的请求依次地、循环地分配给后端的每一台服务器。 - 优点:实现简单,能保证所有后端服务器的请求量绝对平均。
- 缺点:它完全不考虑后端服务器当前的负载和性能差异。如果某台服务器性能较差或负载较高,轮询策略依然会给它分配同样多的请求,可能导致这台服务器被压垮。
- 工作原理:这是最简单、也是Nginx的默认负载均衡策略。它会按照
2. 加权轮询 (Weighted Round Robin)
- 工作原理:这是对简单轮询的一个重要增强。它允许我们为每一台后端服务器指定一个权重(
weight
)。权重越高的服务器,被分配到请求的概率就越大。upstream backend { server backend1.example.com weight=3; server backend2.example.com weight=1; }
- 适用场景:这是最常用的策略之一,非常适合后端服务器硬件配置或处理能力不均的场景。我们可以给性能好的服务器配置更高的权重,让它“能者多劳”。
- 工作原理:这是对简单轮询的一个重要增强。它允许我们为每一台后端服务器指定一个权重(
3. IP哈希 (IP Hash)
- 工作原理:它不再是轮询,而是根据请求来源的客户端IP地址,进行一次哈希计算,然后根据哈希结果,将这个客户端的所有请求,都固定地分配给某一台后端服务器。
- 适用场景:非常适合那些需要保持会话(Session)一致性的场景。比如,一个用户登录后,如果不希望他的会话信息在多个服务器之间同步,就可以用IP哈希,来保证他的所有后续操作,都落在同一台服务器上。
- 缺点:
- 可能会导致负载不均。如果某个IP段的访问量特别大,就会导致对应的后端服务器压力过大。
- 如果后端某台服务器宕机,那么原本分配到这台服务器上的所有客户端,都需要重新计算哈希,可能会被重新分配到不同的服务器,导致会话丢失。
二、 扩展算法(需额外配置或模块)
4. 最少连接 (Least Connections)
- 工作原理:这是一种更“智能”的策略。Nginx会将新的请求,分配给当前活跃连接数最少的那台后端服务器。
- 适用场景:非常适合那些请求处理时间长短不一的场景。比如,某些请求可能需要进行复杂的计算或耗时的I/O。使用最少连接策略,可以避免请求都堆积在某台正在处理慢请求的服务器上,能更好地实现真正的负载均衡。
5. URL哈希 (URL Hash) / 一致性哈希
- 工作原理:这是通过第三方模块(如
ngx_http_upstream_hash_module
)实现的。它根据请求的URL进行哈希,然后将同一URL的请求,固定地分配给某一台后端服务器。 - 适用场景:非常适合用作后端缓存服务器的负载均衡。比如,有多台Varnish或Squid缓存服务器。通过URL哈希,可以保证同一个URL的请求,总是命中同一台缓存服务器,从而极大地提高缓存的命中率。
- 工作原理:这是通过第三方模块(如
6. 最短响应时间 (Least Time)
- 这是Nginx Plus(商业版)或通过第三方模块才能支持的更高级的算法。它会综合考虑服务器的平均响应时间和活跃连接数,将请求分配给综合表现最优的服务器。
总结一下,在我的实践中:
- 如果后端服务器性能相近,我会使用默认的轮询。
- 如果后端服务器性能有差异,我会使用加权轮询。
- 如果需要保持会话,我会考虑IP哈希,但会注意其可能带来的负载不均问题。
- 对于需要做后端缓存的场景,URL哈希是最佳选择。
- 而最少连接,则是在处理耗时不均的请求时,一个非常优秀的动态均衡策略。
Nginx位于七层网络结构中的哪一层?
面试官您好,Nginx工作在OSI七层网络模型中的最高层——第七层,即应用层。
也正因为如此,我们通常称Nginx为 “七层负载均衡器” 或 “应用层负载均衡器”。
1. 为什么说Nginx工作在应用层?
这个定位,是由Nginx的工作能力决定的。它之所以能工作在应用层,是因为它能够完全理解和解析应用层协议的内容,特别是我们最常用的HTTP协议。
一个生动的比喻:
- 我们可以把网络数据包想象成一封封的“快递”。
- 四层负载均衡器(如LVS),就像一个只看“快递单” 的调度员。它只关心这封快递的目标IP地址和端口号(TCP/UDP层的信息),然后就把它转发走,它完全不拆开包裹,也不知道里面装的是什么。
- 而Nginx,则像一个能“拆开包裹验货” 的、更高级的调度员。它不仅能看到快递单,还能打开包裹,看到里面具体的“货物清单”(即HTTP请求的头部和内容)。
Nginx能做什么?
- 正是因为能“看懂”HTTP报文,Nginx才能实现各种强大的、基于应用层内容的路由和处理逻辑:
- 根据URL路径进行转发:比如,将
/api/
的请求转发给A服务,将/static/
的请求转发给B服务。 - 根据HTTP头部信息进行决策:比如,根据
User-Agent
头,将移动端和PC端的请求分发到不同的后端。 - 根据请求方法(GET/POST)进行分流。
- 甚至修改HTTP报文:比如,添加/删除/修改HTTP头部,或者对响应内容进行压缩(Gzip)。
- 根据URL路径进行转发:比如,将
- 正是因为能“看懂”HTTP报文,Nginx才能实现各种强大的、基于应用层内容的路由和处理逻辑:
这些操作,都必须在理解了HTTP协议内容的基础上才能完成,因此,Nginx是名副其实的应用层设备。
2. 七层负载均衡 (Nginx) vs. 四层负载均衡 (LVS)
通过与四层负载均衡的对比,我们可以更清晰地看到Nginx的定位:
特性 | 四层负载均衡 (如LVS, F5) | 七层负载均衡 (如Nginx, HAProxy) |
---|---|---|
工作层次 | 传输层 (L4) | 应用层 (L7) |
理解内容 | 只解析IP地址和端口号 | 能完整解析HTTP/HTTPS等应用层协议 |
转发方式 | 基于IP+Port进行转发,性能极高 | 基于URL、HTTP头、Cookie等应用层信息进行转发 |
功能 | 主要是流量分发 | 流量分发、内容路由、反向代理、静态资源服务、安全过滤等 |
性能 | 更高(因为逻辑简单,不涉及应用层解析) | 相对较低(但对于绝大多数场景已足够快) |
灵活性 | 较低 | 极高 |
总结
所以,Nginx凭借其在应用层的强大处理能力,不仅仅是一个负载均衡器,更是一个功能丰富的Web服务器、反向代理服务器、以及API网关。它通过理解HTTP协议,为我们提供了极其灵活和强大的流量控制与内容路由能力,是现代Web架构中不可或缺的核心组件。
参考小林 coding