每日八股文6.28
每日八股-补充
- 网络篇
- 1.介绍计算机网络模型
- 2.tcp和udp区别,tcp为什么是安全的,UDP的应用场景
- 3.UDP如何实现有序传输
- 4.HTTP和HTTPS对比
- 5. HTTPS 的连接建立过程是怎样的?能详细描述一下吗?
- 6.能解释一下什么是对称加密和非对称加密吗?它们都有哪些常见的算法?从密钥的角度分析一下它们的区别?
- 7.三次握手和四次挥手的原因,为什么要三次握手,四次挥手?
- 8.Get和Post的区别
- 9.为什么 POST 更安全?
- 10. gin框架如何解析前端传过来的json?
网络篇
1.介绍计算机网络模型
OSI 参考模型是一个理论上的网络分层模型,它将网络通信的不同功能划分为七个不同的层次。从上到下依次是:应用层、表示层、会话层、传输层、网络层、数据链路层和物理层。
每一层都有其特定的职责和协议。比如,在应用层,我们常见的协议有 HTTP、HTTPS 等,它们直接为应用程序提供网络服务。传输层则负责端到端的数据传输,主要的协议是 TCP 和 UDP。网络层负责在网络中进行路由和寻址,IP、ICMP 和 ARP 协议就工作在这一层。这种分层的方式使得网络设计更加模块化,每一层专注于特定的功能,并通过清晰的接口与上下层交互,降低了复杂性,也提高了灵活性。
但实际上,OSI 模型更多的是一个理论框架,我们在实际应用中更常用的是 TCP/IP 模型。
TCP/IP 网络模型通常有两种划分方式,一种是四层模型,从上到下分别是:应用层、传输层、网络层和网络接口层。另一种是五层模型,将网络接口层进一步拆分为数据链路层和物理层,这样就是:应用层、传输层、网络层、数据链路层和物理层。
2.tcp和udp区别,tcp为什么是安全的,UDP的应用场景
TCP 和 UDP 是两种非常基础但又很重要的传输层协议,它们之间有很多关键的区别。首先,TCP 是面向连接的,在正式传输数据之前需要通过三次握手建立连接,而 UDP 是无连接的,可以直接发送数据,不需要建立连接的过程。其次,在可靠性方面,TCP 提供了很多机制来保证数据的可靠传输,比如超时重传、流量控制和拥塞控制,而 UDP 则不保证数据的可靠性,它尽最大努力交付,但不保证顺序和完整性。第三,TCP 传输的数据是基于字节流的,没有明确的边界,而 UDP 是基于数据报的,每次发送一个完整的包,有明确的边界。
总的来说,TCP 的优势在于可靠性高,适用于对数据完整性要求高的场景,但可能会牺牲一些实时性。UDP 的优势在于简单高效、实时性好,适用于对实时性要求高但可以容忍少量丢包的场景。
对实时性要求高的应用(如游戏、直播、视频通话)更倾向于UDP,而对可靠性和完整性要求高的应用(如网页浏览、文件下载、点播视频)更倾向于TCP。
3.UDP如何实现有序传输
首先,我们需要明确一个核心概念:UDP (User Datagram Protocol) 本身是一种无连接、不可靠、不保证顺序的传输层协议。 这意味着,UDP 协议本身不提供任何机制来确保数据包(Datagram)按发送顺序到达目的地。数据包在网络中可能经过不同路径,导致先发出的包后到;也可能因为网络拥塞而丢失,UDP 对此完全不处理。
然而,尽管 UDP 自身不支持,我们完全可以在应用层构建逻辑来为数据传输增加有序性保障。
要在 UDP 之上实现有序传输,应用层通常需要实现以下几个关键机制:
-
添加序列号
这是最核心的一步。在发送数据时,为每一个发出的数据包添加一个自定义的头部,该头部中必须包含一个唯一的、单调递增的序列号 sn。
-
接收方缓存与排序
接收方在收到 UDP 数据包后,不能立即将其交给应用程序处理,因为它们可能是乱序到达的。
-
开辟缓存区 (Buffer): 接收方需要维护一个缓存区来存放收到的数据包。
-
按序重排: 当收到一个数据包时,解析出其序列号,并将其放入缓存区的正确位置。
-
交付应用: 接收方会有一个期望收到的序列号 expected_sn。只有当序列号为 expected_sn 的数据包到达后,才能将该数据包及其后连续的数据包从缓存区中取出,按顺序交付给上层应用。同时,更新 expected_sn。
-
-
确认与重传
仅有序列号和排序还不够,因为 UDP 数据包可能会丢失。如果一个包丢失了,接收方将永远等待那个丢失的序列号。因此,必须引入确认和重传机制,这也是实现“可靠性”的关键,而可靠性是有序性的前提。
-
ACK (Acknowledgement): 接收方在收到数据包后,需要向发送方回复一个确认包(ACK),告知它已经收到了哪些数据包。ACK 包中通常会包含已成功接收并排序的最大连续序列号。
-
SACK (Selective Acknowledgement): 更高效的方式是使用选择性确认。SACK 不仅告诉发送方最大连续序列号,还会告知它收到了哪些不连续的“孤岛”数据块。这使得发送方可以精确地只重传那些真正丢失的数据包,而不是所有未被确认的包。
-
超时重传 (Timeout Retransmission): 发送方在发出一个数据包后会启动一个计时器。如果在指定时间内没有收到对该数据包的 ACK,就认为该数据包已经丢失,并重新发送它。
-
4.HTTP和HTTPS对比
HTTP 和 HTTPS 最主要的区别可以用一句话概括,就是 HTTPS 比 HTTP 更安全。具体来说,首先在安全性方面,HTTP 采用明文传输,数据很容易被窃听或篡改,而 HTTPS 通过 SSL/TLS 协议对数据进行加密,保证了传输的安全性。其次,在建立连接时,HTTP 在完成 TCP 三次握手后就可以传输数据,而 HTTPS 在 TCP 握手之后,还需要进行额外的 SSL/TLS 握手来协商加密参数。第三,它们的端口号也不同,HTTP 默认使用 80 端口,而 HTTPS 默认使用 443 端口。最后,也是很重要的一点,HTTPS 需要使用由受信任的第三方机构颁发的 数字证书 来验证服务器的身份,确保用户连接的是真正的服务器,而 HTTP 没有这个机制。
5. HTTPS 的连接建立过程是怎样的?能详细描述一下吗?
HTTPS 的建立过程可以分为两个主要阶段。首先是建立 TCP 连接,这需要客户端和服务器之间进行三次握手。
在 TCP 连接建立之后,会进行 TLS/SSL 握手,通常是四次握手(在 TLS 1.3 中可以优化为三次)。
第一次握手是客户端发送 Client Hello 消息,其中包含客户端支持的 TLS 版本、密码套件列表以及一个客户端随机数。
第二次握手是服务器收到后,会回复 Server Hello 消息,确认 TLS 版本,选择一个密码套件,并发送一个服务器随机数。紧接着,服务器还会发送 Server Certificate 消息,包含服务器的数字证书,以及 Server Hello Done 消息,表示服务器的握手信息发送完毕。
第三次握手是客户端验证服务器证书的合法性后,会生成一个 预主密钥(pre-master secret),并使用服务器证书中的公钥对其进行加密,通过 Client Key Exchange 消息发送给服务器。
第四次握手实际上包含两个步骤。客户端发送 Change Cipher Spec 消息,通知服务器后续将使用协商好的加密方式进行通信,然后发送 Finished 消息,包含对之前握手信息的加密摘要,用于验证完整性。服务器收到后,也会发送 Change Cipher Spec 和 Finished 消息给客户端,进行同样的确认。
在完成这四次握手后,客户端和服务器双方就拥有了三个随机数(客户端随机数、服务器随机数和预主密钥),它们会基于这三个随机数生成最终的对称会话密钥,用于后续 HTTP 请求和响应数据的加密和解密。
6.能解释一下什么是对称加密和非对称加密吗?它们都有哪些常见的算法?从密钥的角度分析一下它们的区别?
对称加密就像一把锁和一把钥匙,加密和解密都使用同一个密钥。数据发送方用这个密钥加密,接收方也用同一个密钥解密。常见的对称加密算法有 AES算法(高级加密标准)和 HS256 等。它的主要特点是速度快,但密钥需要安全地共享。
非对称加密则使用一对密钥,分别是公钥和私钥。公钥可以公开给任何人,用于加密数据,而私钥只有持有者自己知道,用于解密用对应公钥加密的数据。反过来,私钥也可以用于签名,公钥用于验证签名。常见的非对称加密算法有 RSA 。它的主要特点是安全性高,因为私钥不需要传输,但加解密速度相对较慢。
从密钥的角度来看,对称加密的关键在于密钥的保密,一旦泄露,加密的数据就不安全了。而非对称加密则是公钥公开,私钥保密,通过这种方式实现了更安全的通信。
7.三次握手和四次挥手的原因,为什么要三次握手,四次挥手?
TCP 三次握手是建立可靠连接的关键步骤。首先,客户端会向服务器发送一个 SYN 报文,这个报文里会包含客户端的初始序列号,并且客户端的状态会变成 SYN_SENT。接着,服务器收到这个 SYN 报文后,会回复一个 SYN-ACK 报文,这个报文里会包含服务器的初始序列号,并且确认了客户端的序列号(加 1),同时服务器的状态会变成 SYN_RCVD。最后,客户端收到服务器的 SYN-ACK 报文后,会发送一个 ACK 报文,这个报文里会确认服务器的序列号(加 1),客户端的状态变成 ESTABLISHED,服务器收到这个 ACK 报文后,状态也会变成 ESTABLISHED。至此,TCP 连接就建立完成了。
TCP 需要三次握手主要是为了解决两个核心问题。首先是为了防止历史连接的重复建立,避免造成资源浪费。举个例子,如果一个迟到的 SYN 包在网络中很久之后才到达服务端,如果只有两次握手,服务端可能会误认为这是一个新的连接请求并建立连接。而通过三次握手,客户端可以根据收到的 SYN-ACK 中的确认号来判断是否是期望的响应,如果不是,可以发送 RST 包断开连接。
其次,三次握手还可以同步双方的初始序列号,并确认双方的发送和接收能力。第一次握手时,客户端告诉服务端自己能发送数据;第二次握手时,服务端告诉客户端自己能接收数据也能发送数据;第三次握手时,客户端告诉服务端自己能接收数据。这样一来,双方都确认了彼此的发送和接收能力,才能进行可靠的数据传输。如果只有两次握手,客户端就无法确认服务端是否成功接收到了自己发送的连接请求。
补充:为什么两次握手不够?我第二次握手就已经发送过来了syn ack的确认号呀,那么第三次连接直接传输数据不可以吗?
假设只有两次握手:
-
客户端发送 SYN (seq=X):客户端发出连接请求,携带初始序列号 X。
-
服务器收到 SYN,发送 SYN-ACK (seq=Y, ack=X+1):服务器收到 SYN 后,认为连接请求有效,返回 SYN-ACK,其中包含自己的初始序列号 Y,并确认收到了客户端的 X(ack=X+1)。此时,服务器认为连接已建立,并为之分配了资源。
现在问题来了:
客户端收到 SYN-ACK 后,即使发现 ack=X+1 是针对一个“旧的”或“迷途的”SYN 包的,它会怎么做?
- 客户端可能会发送一个 RST(Reset)包来拒绝这个连接。
关键是:服务器是否能可靠地收到这个 RST 包? 如果这个 RST 包在网络中丢失了呢?
如果 RST 包丢失,或者在非常慢的网络中,服务器已经认为连接建立并开始向客户端发送数据,但客户端根本不理会,甚至已经关闭了那个端口。服务器就会一直傻傻地等待,浪费资源。
三次握手如何解决这个问题?
第三次握手(客户端发送 ACK)的作用,就是给服务器一个明确的、来自客户端的“我准备好了”的最终确认。
-
客户端发送 SYN (seq=X)
-
服务器收到 SYN,发送 SYN-ACK (seq=Y, ack=X+1):服务器此时认为它“可能”要建立连接了,但它知道还需要等待客户端的最终确认。它会分配一些临时资源,但不会像两次握手那样直接认为连接已建立。
-
客户端收到 SYN-ACK:
-
如果是正常的连接请求: 客户端发现 ack=X+1 正确,就发送第三次 ACK (ack=Y+1)。
-
如果是“迷途”的 SYN-ACK: 客户端发现 ack=X+1 对应的是一个它早已不关心的旧 SYN 包,它会发送一个 RST 包,明确告诉服务器:“这个连接不对劲,请关闭它!”
-
关键点在于: 即使这个 RST 包丢失了,服务器也因为没有收到第三次握手的 ACK,会因为超时而放弃这个连接,重新进入监听状态。它不会像两次握手那样,因为“已经认为连接建立”而持续消耗资源或尝试发送数据。
TCP 的四次挥手是断开连接的过程,它比三次握手多了一步,主要是因为 TCP 是全双工的。假设是客户端主动关闭连接:
首先,客户端会发送一个 FIN 报文给服务端,表示客户端已经没有数据要发送了,然后客户端进入 FIN_WAIT_1 状态。
服务端收到这个 FIN 报文后,会先回复一个 ACK 应答报文,告诉客户端已经收到了关闭连接的请求,但服务端可能还有数据没有发送完,所以服务端会先进入 CLOSE_WAIT 状态。客户端收到这个 ACK 后,会进入 FIN_WAIT_2 状态,等待服务端也发送完数据并请求关闭连接。
当服务端确认已经没有更多的数据要发送时,它会发送一个 FIN 报文给客户端,请求关闭从服务端到客户端这个方向的连接,然后服务端进入 LAST_ACK 状态。
客户端收到服务端的 FIN 报文后,会回复一个 ACK 应答报文,并进入 TIME_WAIT 状态。服务端收到这个 ACK 应答后,就进入 CLOSE 状态,连接关闭。客户端在 TIME_WAIT 状态会等待 2MSL 的时间,以确保最后一个 ACK 报文能够到达服务端,并且防止网络中残留的旧连接的报文干扰新的连接,等待结束后,客户端也会进入 CLOSE 状态,至此,整个 TCP 连接就完全关闭了。
TCP 需要四次挥手主要是因为 TCP 连接是全双工的,这意味着连接的两端可以同时进行数据的发送和接收。当一方(比如客户端)完成数据发送想要关闭连接时,它发送的 FIN 报文只是表示自己不再发送数据了,但仍然可以接收对方(服务端)发送的数据。服务端收到这个 FIN 报文后,会先回复一个 ACK 报文,表示已经知道了客户端不再发送数据了。但是,服务端可能还有一些数据没有发送完,因此它不能立即发送 FIN 报文来关闭连接,而是需要等待自己的应用程序处理完所有数据后再发送 FIN 报文。这就导致了服务端发送 ACK 和 FIN 报文这两个动作可能是分开进行的,从而形成了四次挥手。如果只有三次挥手,当服务端收到客户端的 FIN 并回复 ACK 后就立即发送 FIN,那么如果服务端还有数据没发完,就会导致数据丢失。
8.Get和Post的区别
-
从用途上讲:GET 的设计初衷就是从服务器获取指定的资源,比如看一篇文章、加载一张图片。而 POST 的初衷是向服务器提交数据,希望服务器对这些数据进行处理,比如用户注册、提交订单。
-
最重要的区别是幂等性:
GET 是幂等的,意思是发送 N 次相同的 GET 请求,对服务器资源产生的影响应该和发送 1 次是完全一样的。比如你刷新(重新GET)一个网页100次,网页内容不会因此改变。
POST 不是幂等的,意思是每发送一次 POST 请求,都可能在服务器上产生新的变化。比如你点击“提交订单”按钮(POST请求)两次,你很可能会创建两个订单。所以浏览器在你刷新一个 POST 提交的页面时,会警告你“是否要重新提交表单”。
-
从数据传输上讲:
GET 请求把数据拼接在 URL 后面(?key=value&key2=value2),所以你在地址栏、浏览器历史记录里都能看到,而且因为 URL 长度有限,GET 能传输的数据大小也有限。
POST 请求把数据放在请求体里,对用户不可见,也没有大小限制。
9.为什么 POST 更安全?
-
数据对普通用户不可见,防止“意外泄露”
这是最直接的原因。因为 POST 的数据在请求体里,所以:
-
不会显示在地址栏:如果用 GET 提交密码 ?password=123456,你旁边的人、甚至你自己都能直接看到。POST 则不会。
-
不会保存在浏览器历史记录中:别人翻看你的浏览器历史时,看不到你提交过的敏感数据。
-
不会直接记录在服务器的访问日志 (Access Log) 里:很多服务器默认只记录请求的 URL,不会记录 POST 的请求体,这在一定程度上减少了敏感数据在服务器上以明文形式暴露的风险。
一句话总结:POST 避免了数据在非传输过程中的“物理暴露”和“历史记录暴露”。
-
-
“更安全”不等于“安全”——真正的安全靠 HTTPS
但是,我必须强调,这种‘安全’是非常脆弱的,它绝对不意味着 POST 请求本身是加密的或无法被窃取的。
如果只使用普通的 HTTP,无论是 GET 还是 POST,所有数据在网络传输时都是明文的。一个中间人(比如黑客、网络运营商)可以通过抓包工具,清清楚楚地看到你提交的所有信息,包括密码。
真正的安全解决方案是使用 HTTPS。
HTTPS 会对整个 HTTP 通信进行加密,它就像一个保险箱。无论你是用 GET 还是 POST,整个请求(包括 URL、请求头、请求体)都会被加密。在这种情况下:
-
HTTPS + POST:敏感数据被放在加密的请求体里,这是提交敏感信息的标准和最佳实践。
-
HTTPS + GET:虽然请求也被加密了,但敏感数据依然会留在 URL 里,依然存在上面说的“历史记录暴露”等风险,所以不推荐用 GET 传输敏感数据。
-
10. gin框架如何解析前端传过来的json?
在 Gin 中,解析 JSON 的核心思想是将请求体中的 JSON 数据直接绑定到你定义的 Go 结构体(struct)上。
-
定义 Go 结构体
首先,你需要定义一个 Go 结构体来匹配前端发送的 JSON 数据的结构。使用 json:“fieldName” 标签来指定 JSON 字段名与 Go 结构体字段的映射关系。
package main// 定义一个结构体来接收前端的 JSON 数据
// 注意:字段名首字母大写才能被外部包访问(包括Gin的绑定器)
type User struct {Name string `json:"name" binding:"required"` // binding:"required" 表示该字段为必填Email string `json:"email"`Age int `json:"age"`
}// 假设前端可能发送的产品信息
type Product struct {ID string `json:"id"`Name string `json:"product_name" binding:"required"`Price float64 `json:"price" binding:"min=0"` // 价格不能为负数
}
-
结构体字段首字母大写: 这是 Go 语言的导出规则,只有首字母大写的字段才能被 Gin 的绑定器访问并赋值。
-
json:“fieldName” 标签: 这是标准库 encoding/json 的用法,告诉 Gin(实际上是其底层使用的 JSON 解析库)如何将 JSON 键映射到 Go 结构体字段。例如,前端发送 { “product_name”: “Laptop” },会被映射到 Product.Name 字段。
-
binding:“rule” 标签(可选但推荐): 这是 Gin 提供的验证功能。你可以添加规则来确保数据的有效性,比如 required(必填)、min、max 等。如果数据不符合这些规则,Gin 会自动返回错误。
- 在 Gin 路由中解析 JSON
在 Gin 的路由处理函数中,你可以使用 c.ShouldBindJSON() 或 c.BindJSON() 方法来解析 JSON。
package mainimport ("log""net/http""github.com/gin-gonic/gin"
)func main() {router := gin.Default()// 定义一个 POST 接口来接收用户数据router.POST("/users", func(c *gin.Context) {var user User // 声明一个 User 结构体变量来接收数据// c.ShouldBindJSON() 会尝试将请求体中的 JSON 绑定到 user 结构体// 如果绑定失败(例如 JSON 格式错误或必填字段缺失),它会返回一个 errorif err := c.ShouldBindJSON(&user); err != nil {// 如果绑定失败,通常是 JSON 格式不正确或验证失败c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}// 成功解析 JSON 后,user 结构体中就包含了前端传递的数据log.Printf("Received User: Name=%s, Email=%s, Age=%d\n", user.Name, user.Email, user.Age)// 进行业务逻辑处理...// 例如:保存到数据库、进行身份验证等// 返回成功响应c.JSON(http.StatusOK, gin.H{"message": "User data received successfully!", "user": user})})// 定义另一个 POST 接口来接收产品数据router.POST("/products", func(c *gin.Context) {var product Productif err := c.ShouldBindJSON(&product); err != nil {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})return}log.Printf("Received Product: ID=%s, Name=%s, Price=%.2f\n", product.ID, product.Name, product.Price)c.JSON(http.StatusOK, gin.H{"message": "Product data received successfully!", "product": product})})router.Run(":8080") // 启动服务,监听 8080 端口
}
-
c.ShouldBindJSON(&structVar): 这是 Gin 最推荐的解析 JSON 的方法。
-
它会检查请求的 Content-Type 是否为 application/json。
-
它会自动尝试将请求体中的 JSON 数据解析并绑定到你提供的结构体指针上。
-
如果解析或验证过程中出现任何错误,它会返回一个 error。这使得错误处理非常方便。
-
-
错误处理: 在 if err := c.ShouldBindJSON(&structVar); err != nil 语句中,务必处理返回的 err。当解析失败时,通常返回 http.StatusBadRequest(400 错误),并告知客户端具体的错误信息。Gin 的错误信息通常很详细,可以直接返回给前端。
-
c.BindJSON(&structVar): 这是一个更“宽松”的版本。如果绑定失败,它也会返回错误,但通常在内部会尝试进行一些默认的错误处理(例如直接返回 400 响应)。建议优先使用 c.ShouldBindJSON(),因为它可以让你更精细地控制错误响应。