Go 面试复盘

先上总结:

  • 面经光看没用,只有自己不断的面试,然后总结,自己理解性的进行描述才有意义,否则只是像八股文一样去背记,从而未能理解真正的含义。那么,可能下一次面试的时候,上一次面试的问题依然处于一种遗留的状态,这样就无法在技术的关键节点进行成长。
  • 尤其是算法题,如果一两个月不复盘算法问题,那么算法思维存在,但是代码熟练度会下降,从而导致写算法的时间变长,以及相应的心态焦虑。至于算法的时间复杂度与空间复杂度放在下文具体说明。
  • 以下为自己答的不好与没答出的问题。

1、TCP 三次握手

答:TCP的三次握手为:

​ 1、客户端发送的报文为 SYN 报文,并选择一个初始的 Seq 序号,之后客户端进入监听状态(SYN-SENT)。

​ 2、服务器在接受到客户端第一次发送的 ACK 报文之后,如果同意连接,即向客户端发送连接确认报文,即 SYN + ACK 报文,也附加一个自选的初始 Seq 序号,并且此序号与客户端的序号无关,之后服务器端继续维持监听状态(SYN-REVD)

​ 3、客户端在接受到服务器发送回的报文之后,再向服务器端发送确认报文,确认号为服务器初始的 Seq 序号 + 1,序号为自己初始的 Seq 序号 + 1。

​ 4、此后服务器与客户端正式建立连接,开始发送数据,双方状态为ESTABLISHED。

此处引用哔哩哔哩 up 主掌芝士zzs的图片

tcp3

2、TCP 需要三次握手的原因

答:采用第三次握手的原因是:

​ 1、如果第一次客户端的报文中途出现延迟,而客户端开始重发第一次的报文,并且重发报文被服务器正确接收。

​ 2、而此时,第一次客户端发送的报文又到达服务端,服务端接受后,又返回一个报文,相当于服务器同一个客户端建立了两个连接,而客户端只认为自己建立的一个连接,造成了状态不一致,同时服务器的资源也被浪费了。

​ 3、故为了尽可能保证连接的建立及时、有效且资源节约,故采用 TCP 三次握手。

3、TCP 四次挥手

答:TCP 的四次挥手过程为

​ 1、首先由客户端向服务器端发送关闭连接请求报文,即 FIN 报文,此时客户端由 ESTABLISHED 状态转变为 FIN-WAIT-1 状态,此时还能继续接受服务器所发送的数据。

​ 2、服务器在接受到客户端向服务器端发送的 FIN 包后,向客户端发送确认报文,即 ACK 报文,此时服务器的状态由 ESTABLISHED 状态转变为 CLOSED-WAIT状态,此时服务器端还能继续把未能发送完的数据继续发送。

​ 3、客户端在接受了服务器端发回的 ACK 确认报文之后由 FIN-WAIT-1 转变为 FIN-WAIT-2 状态,并且能继续接受数据,直到服务器发送终止报文,即 FIN 包为止。

​ 4、服务器向客户端发送 FIN 包,此时服务器端由 CLOSED-WAIT 状态转变为 LAST-ACK 状态,等待客户端返回最后一次确认报文,即 ACK 为止。

​ 5、客户端接收到 FIN 包后,由 FIN-WAIT-2 变为 TIME-WAIT 状态,超过一定的时间后自动转变为 CLOSED 状态。

​ 6、服务器端收到客户端的 ACK 报文后,即由 LAST-ACK 状态转变为 CLOSED 状态,不再发送与接受客户端的数据。

此处引用哔哩哔哩 up 主 掌芝士zzs 的图片

tcp4

4、TCP 需要四次挥手的原因

答:采用第四次挥手的原因是:

​ 1、如果客户端第四次发送 ACK 报文后就直接进入 CLOSED 状态,那么如果第四次发送的ACK报文在传输的过程中丢失,服务器由于一直未能接收客户端发送的 ACK 报文,再次向客户端发送相应的 FIN 报文,而此时客户端已经关闭,接受不到服务器发送的 FIN 报文。即造成了服务器的资源浪费

​ 2、故为了保证通信尽可能的可靠,采用 TCP 四次握手,但是在考研中,有种特殊的情况,在确保第三次握手能成立的情况下,第四次握手可以被省略。若将一个往返视为 RTT 的情况下,最短的释放连接所需要的时间为 1.5 个 RTT 即可,不需要 2 个 RTT,所以这也是为了节省资源所考虑的情况,并不一定视为错误答案。

5、TCP粘包问题

答:TCP 对比 UDP,前者主要是以字节流的形式传输数据,而UDP则以报文的形式传输数据。

​ 其中报文与字节流的区别主要是,字节流传输是以字节为单位进行数据传输,与每一个数据中的独立的内容无关,而报文传输为传输以报文为单位,保留了报文内容的边界。所以就会导致TCP传输字节时候,有可能区分不了数据的边界,导致最后解包数据所造成的解码乱码现象,即TCP的粘包的问题。

​ 解决 TCP 粘包问题的办法有许多种,其中在之前所学的Zinx框架中,刘丹冰老师(后面称为 Aceld 老师)解释了一种 TLV 格式的封包解包办法(如下图),即使用 datalen、msgID、data作为封包解包的字段,首先读取一遍封包的头部长度,即 datalen 与 msgID 字段的大小,此处按照自己设置的来。一般设置是两个 uint32 类型,即为 8 Byte 的。

​ 其中解析开头的 datalen为 data字段的数据长度,msgID 为相应的数据包编号。然后根据开头的 datalen 的具体数值来读取之后的 data 段的数据。至于TCP发送数据流中出现的错误,利用好 TCP 的错误重传机制就好,go 语言也有 TCP 包的实现,具体以后看底层代码来解释,本篇博客暂时不讨论此问题。

TLV

6、写代码:channel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//多多练习一下代码能力,要脱离相应的视频项目开发,变为生产实际开发,有些东西,一段时间没用就会忘记,比如算法时间复杂度,以及相应的代码熟练度

//实现两个channel,一个 channel 输出 ping,一个channel 输出 pang,并发执行。

//22-5-13 日再回头看,发现问题很显然了,自己当时写一个经典的并发案例没写出来就是属于基础不够好,这个代码也不符合面试官需要达到的要求,只是效果达到了。

package main

import "fmt"

func main() {
p := make(chan string)
q := make(chan string)

go func() {
p <- "ping"
}()

go func() {
q <- "pang"
}()

c := <-q
a := <-p

/*
若没有IDE,在vim 看 c 与a 的类型要使用如下
*/
fmt.Printf("type of c : %T\n", c)
fmt.Printf("type of a : %T\n", a)

for {
fmt.Println(a)
fmt.Println(c)
}
}

7、Go 中 map 查询的时间复杂度为 O(1)

答:查看了相应的各种博客,了解到:

  • 1、java 中 map 的底层源代码实现为数组 + 链表 + 红黑树,所以在数据量极小的情况下,相应的**查询时间复杂度为 O(1)**,而在数据量较多的情况,map 的查询时间复杂度应该是大于 O(1),小于 O(N),接近 O(logN) 的时间复杂度的!

  • 2、所以说关于 map 的查询时间复杂度是一个很老的问题了,一般使用情况默认为 O(1) 的时间复杂度。但是这并不意味着就要否认 map 的查询时间复杂度是 O(logN) 的说法。

  • 3、不懂不理解的问题,一定需要事后进行相关资料的查询,以及总结。

关于map 查询的时间复杂度,StackOverFlow 上给出的说法挺多

mapsearch

mapAnswer1

mapAnswer2

8、GMP 模型

强烈推荐 Aceld 老师的 GMP 模式详细解释,可以明白调度器的调度行为。
GMP 模型已经单独作为一篇博客存在,所以此处不再阐述。

9、Go GC 在什么情况下性能比较低

1、内存泄露。

2、小对象,结构体比指针的好


————END 如果本博客存在误解的问题,恳请大家指出!—————-


Go 面试复盘
https://chaggle.github.io/2022/02/12/go/interview/Interview/
作者
chaggle
发布于
2022年2月12日
许可协议