完了,又被腾讯面试官拷打了 。。。

2023-11-24 12:57:51 浏览数 (1)

图解学习网站:xiaolincoding.com

大家好,我是小林。

最近有个同学跟我反馈,去面腾讯的时候,又被腾讯面试官拷打了 。

因为他是搞 Go 后端的,没怎么接触过 socket 编程,结果问了好几个网络编程的问题,直接懵逼了。

第一个问题

服务端调用listen之后sleep休眠,请问客户端的connect能不能成功?

这个问题实际上在考察大家对于 TCP 三次握手的底层原理,很多同学对于 TCP 三次握手的理解只停留在SYN和ACK报文的交互,而关于三次握手过程中,内核做了哪些事情?socket 编程和三次握手有什么关系?都不太明白,那这样在遇到这种问题就比较难回答了。

我们先看看客户端连接服务端时,发生了什么?

  • 服务端和客户端初始化 socket
  • 服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口;
  • 服务端调用 listen,进行监听,这个时候服务端内核就会有 TCP 半连接队列和 TCP 全连接队列了。
  • 客户端调用 connect,向服务端的地址和端口发起连接请求,这时候就会发生 TCP 第一次握手,发送 syn 报文,并告诉服务端当前发送序列号 client_isn,客户端进入 SYN_SENT 状态;
  • 服务端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn 1,表示对 SYN 包 client_isn 的确认,同时服务端也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务端进入 SYN_RCVD 状态,然后会把这个连接放到 TCP 半连接队列中
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务端的 SYN 包进行应答,应答数据为 server_isn 1;
  • ACK 应答包到达服务端后,服务端的 TCP 连接进入 ESTABLISHED 状态,同时服务端协议栈使得 accept 阻塞调用返回,这个时候服务端到客户端的单向连接也建立成功,内核会把连接从半连接队列移除,然后创建新的完全的连接,并将其添加到 TCP 全连接队列,等待进程调用 accept 函数时把连接取出来。至此,客户端与服务端两个方向的连接都建立成功。

从上面的描述过程,我们可以得知客户端 connect 成功返回是发送完第三次握手后,服务端 accept 成功返回是在三次握手成功之后。

所以,服务端调用listen之后sleep休眠,并不会影响 TCP 三次握手的过程,当服务端调用listen后,操作系统内核会开始监听指定端口上的连接请求。即使服务端进入sleep休眠状态,内核仍然会继续监听连接请求,并将其放入到TCP全连接队列中,等待服务端调用 accpet 来获取连接。

我说不影响就不影响吗?

肯定也不一定对的嘛,还是给大家做个实验验证下我的想法对不对。

写了一个简单的服务端代码,服务端调用完 listen 后,直接进入长达 100000 年的休眠(夸张手法)。

然后编译 启动程序!

ok,很顺利,服务端监听了 8080 端口之后就睡觉去了。

接下来,用 linux 的 nc 工具来模仿客户端连接这个端口。

代码语言:javascript复制
nc 127.0.0.1 8888
  • 如果这个命令立即返回,就当无事发生的话,说明出问题了,可能是无法通信,可能是客户端没有打开;
  • 如果正在等待,就说明连接成功,可以在服务器这个窗口中输入任意内容,在客户端会显示出来

来看看结果如何?

结果是正在等待,说明连接是正常建立成功的!

我们可以通过 netstat 命令来确认连接状态。

完全没问题,状态是ESTABLISHED!

说明服务端调用listen之后sleep休眠,客户端的connect是可以成功的,能顺利完成 TCP 三次握手,我前面的理论分析是没问题的。

针对这个问题,还有另外一种问法,服务端没有执行没有 accept,能成功建立 TCP 连接吗?

答案很明显,也是可以的,因为 accept 并不参与三次握手的过程,只是负责从 TCP全连接队列中取出连接。

第二个问题

如果两边建立了连接,然后有一端sleep休眠,请问write和send能不能成功?

我直接结论,是可以成功的。

因为发送方调用 wirte 发送数据的时候,当把数据从应用层拷贝到 socket 发送缓冲区之后,函数就会返回成功了,至于什么时候发数据,发多少数据,这个后续由内核自己做决定。

来源:小白 debug

并不是等接收方调用 read 函数读到数据后,发送方的 write 函数才返回。因此,接收方的进程即使处于睡眠状态,也不会影响发送方执行 wirte。

并且即使发送方进程即使处于睡眠状态,内核依然也是可以正常接收数据,接收到的数据,会被先放到接收缓冲区,等待发送方进程调用 read 从接收缓冲区读取数据。

最后

这两个问题就分析到这里,本质上就是考察大家对 Linux 内核在 TCP 握手和数据传输阶段做了什么事情。

话说回来,感觉腾讯确实很喜欢问网络编程的底层原理啊,以前也分享过好几个同学在面腾讯遇到的网络编程的问题,去图解网站(xiaolincoding.com)翻一翻就可以看到啦。

讲完了,收工,溜了!(是不是像极了下班的样子)

历史好文:

【后端训练营】这一个月惊喜满满!!

准备很久,还是被小红书虐了。。。

不愧是字节,把我吊打了。。。

不愧是微信啊,问的范围贼广!

最累的一场面试,还得是腾讯!

0 人点赞