面试问题汇总-网络

从这里就要开始最后的准备了,把前人遇到的面试问题进行汇总,并用自己的语言总结一下,也从这里开始查漏补缺吧。

我大概看了一下所有公司的面试问题,每个人的简历不同,面试的岗位不同,面试官提到的问题不尽相同。不过主要还是围绕项目+技术点+人事来进行提问的。所以,我主要也是分为这三个方面进行总结,并且定时复习。

需要注意的是,毕竟是面试问题,所以动嘴说是必须的,但是hexo并没有实现在线录音的插件,这里只能对照问题使用手机录音,之后再分析自身的问题了。

技术点问题

网络

socket编程步骤简述一下?

  1. 创建socket对象:使用socket函数创建一个socket对象。
  2. 绑定socket到地址和端口:使用bind函数将socket绑定到一个指定的地址和端口。
  3. 监听连接请求(仅限于TCP):如果使用TCP协议,使用listen函数开始监听来自客户端的连接请求。
  4. 接受连接请求(仅限于TCP):如果使用TCP协议,使用accept函数接受客户端的连接请求,这将返回一个新的socket对象,用于与客户端进行通信。
  5. 建立连接(仅限于TCP):如果使用TCP协议,客户端使用connect函数建立与服务器的连接。
  6. 发送数据:使用send函数向对端发送数据。
  7. 接收数据:使用recv函数从对端接收数据。
  8. 关闭连接:使用close函数关闭与对端的连接。

使用网络套接字(socket)传输的过程

思路:拆成两点分析,从用户空间到内核空间分别阐述。

用户空间:简单说明建立连接和传输步骤带上相应函数

内核空间:首先指出内核中sock是什么,之后举例TCP从建立连接和传输数据两个方面分别说明

创建socket对象(关闭连接),绑定socket到地址和端口(bind),监听连接请求(listen),接受连接请求(accept),建立连接(connect),发送数据(send),接收数据(recv),关闭连接(close)。

sock是什么

当我们使用socket进行数据的传输时,在操作系统内核空间里,实际实现网络传输功能的结构是sock,基于不同的协议和应用场景,会被泛化为各种类型的xx_sock,它们结合硬件,共同实现了网络传输功能。为了将这部分功能暴露给用户空间的应用程序使用,于是引入了socket层,同时将sock嵌入到文件系统的框架里,sock就变成了一个特殊的文件,用户就可以在用户空间使用文件句柄,也就是socket_fd来操作内核sock的网络传输能力。

这个socket_fd是一个int类型的数字。socket套接字可以将它理解为一用于连的数

图片

基于内核空间

而基于内核空间分析传输过程,我将它分为两阶段,分别是建立连接数据传输

对于TCP,要传数据,就得先在客户端和服务端中间建立连接

在客户端,代码执行socket提供的connect方法时,会通过sockfd句柄找到对应的文件,再根据文件里的信息指向内核的sock结构。通过这个sock结构主动发起三次握手。

在服务端握手次数还没达到”三次”的连接,叫半连接,完成好三次握手的连接,叫全连接。它们分别会用半连接队列全连接队列来存放,这两个队列会在你执行listen()方法的时候创建好。当服务端执行accept()方法时,就会从全连接队列里拿出一条全连接。

至此,连接就算准备好了,之后,就可以开始传输数据

为了实现发送和接收数据的功能,sock结构体里带了一个发送缓冲区和一个接收缓冲区,说是缓冲区,但其实就是个链表,上面挂着一个个准备要发送或接收的数据。

当应用执行send()方法发送数据时,同样也会通过sock_fd句柄找到对应的文件,根据文件指向的sock结构,找到这个sock结构里带的发送缓冲区,将数据会放到发送缓冲区,然后结束流程,内核看心情决定什么时候将这份数据发送出去。

接收数据流程也类似,当数据送到linux内核后,数据不是立马给到应用程序的,而是先放在接收缓冲区中,数据静静躺着,卑微的等待应用程序什么时候执行recv()方法来拿一下。

说一下TCP协议层?

TCP是一种面向连接的传输层协议,它提供了可靠的、有序的、基于流的数据传输。TCP协议工作在 OSI参考模型的第四层,即传输层。

TCP协议层主要包括以下几个部分:

  1. 应用层接口:TCP提供了一组接口,供上层应用程序使用。通过这些接口,应用程序可以向TCP传输数据并接收数据。
  2. 报文封装与分段:TCP将上层应用程序的数据封装成TCP报文段。TCP报文段包括报文头和报文体两部分。报文头包含源端口、目的端口、序号、确认号等信息,报文体则包含上层应用程序的数据。
  3. 连接管理:TCP协议通过三次握手建立连接,并通过四次挥手断开连接。连接建立时,客户端向服务器发送一个SYN报文,服务器收到后回复一个SYN-ACK报文,最后客户端再发送一个ACK报文确认连接建立。连接断开时,一方发送一个FIN报文,另一方收到后回复一个ACK报文,然后再发送一个FIN报文。
  4. 流量控制:TCP通过滑动窗口实现流量控制。发送方发送的数据量不能超过接收方的可用窗口大小,接收方会不断向发送方发送窗口大小信息,从而控制发送方的数据发送速度。
  5. 拥塞控制:TCP通过拥塞窗口实现拥塞控制。当网络拥塞时,TCP会降低拥塞窗口大小,从而减少数据发送量,防止网络拥塞加剧。
  6. 数据校验:TCP通过校验和检测报文的完整性。发送方会将校验和附加到TCP报文段中,接收方收到报文段后也会计算校验和,如果校验和不匹配,则说明报文段有错误,应该丢弃。

总之,TCP协议层提供了可靠的、有序的、基于流的数据传输,同时还具有连接管理、流量控制、拥塞控制、数据校验等功能。这些功能使得TCP成为应用广泛的传输层协议。

说说TCP协议怎样建立连接(三次握手原理)?

思路:明确通过三次握手进行连接,解释TCP报文结构,解释TCP的三次握手

TCP协议是通过三次握手建立连接的,在解释三次握手我先说一下TCP首部的报文结构,其中的一些参数是三次握手建立连接过程中必要的。

image-20230311155955608

TCP首部报文结构包括源端口号、目标端口号,主要是传输层定义的一种地址为了区分不同的上层应用,更具体点说端口是用来表示具体的进程,告诉操作系统当前的数据要交给哪一个进程处理。

序列号确认序列号,TCP本身是基于字节流的传输协议 “基于字节流”的含义是:虽然应用程序和 TCP 的交互式一次一个数据块(大小不等),但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。TCP 并不知道所传送的字节流的含义 ,这个序列号其实就是发送缓冲区中每一个字节的数据的编号。而确认序列号是TCP保证传输可靠性,加入了确认机制,因为TCP是全双工的,接受方也是可以给发送方传递信息的,所以这个确认报文中是允许携带数据的。确认报文中,这个确认序列号就至关重要了,它表明的是接受方期望收到发送方发送的下一个字节的序号。同时也代表接受方已经收到了确认序号之前的所有字节,这种确认模式我们称为累积确认

其中还有首部长度,表示TCP头部的长度,单位是字节,还有保留的字段,用0来填充。

之后就是6位的标记位,每一位都代表一定的功能

  • URG置1,代表当前TCP报文段中包含需要紧急处理的数据,同时激活紧急指针字段。这个紧急与否是由上层应用来定义的,而TCP则需要将紧急数据告知对端上层应用。TCP会将需要紧急处理的数据放置在数据的最前方,并使用紧急指针用来指出紧急数据中的最后一个字节,和我们普通数据进行区分。

  • ACK置1,代表该TCP报文段有确认功能,激活确认序列号。

  • PSH置1,则代表该TCP报文段的数据需要直接推给进程。正常情况下,TCP的报文段到达接受方之后,需要在接受方设置的缓存空间中进行等待处理,而这个标记位之一则可以直接将数据推送到进程,进行处理。
  • RST置1,将强制重置TCP连接,导致TCP连接中断。
  • SYN置1,代表希望建立TCP连接,并且位序列号字段随机设定一个初始值。
  • FIN 置1,表示后续将不再有数据需要发送,希望断开TCP连接。

最后还有实现流量控制的窗口大小,保证传输数据完整性校验的校验和,以及紧急指针。

现在可以说一说三次握手了,我们一般将TCP建立连接的过程称为三次握手,这个过程一般是由三个报文的交互来完成的,过程就是同步一些上面提到的参数。而连接过程的开始是通讯双方的其中一方发起的,一般认定发起连接的为客户端,另一方则为服务端。TCP连接一旦建立就是一个双向的会话,通讯双方都可以接收和发送信息。

image-20230311155955608

第一次握手:客户端首先发起TCP连接,而刚开始双方的状态客户端处于CLOSED,服务端处于LISTEN状态,监听连接请求。因为目前是连接的过程,TCP要在连接建立完成后才能发送数据,所以客户端会发出一个不含数据的TCP报文。在这个报文中SYN标记位置1,同时会设定一个序列号的初始值,称为client_isn,而此时客户端的状态变为SYN_SENT状态。

第二次握手:服务端收到客户端发送的SYN报文段后,首先会给这个TCP连接分配缓存空间,来存储TCP的数据流,并定义初始的变量。这一步很重要,也是服务端记录TCP连接的关键。 因为任意一个客户端可以向服务端的同一个端口号发起TCP连接请求,服务端要给不同的连接分配缓存空间,定义不同的初始变量。 这点也会成为SYN泛洪攻击的利用点。SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用半连接队列,导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。不过我们也可以将报文中PSH标记位置1,该TCP报文段的数据直接推给进程。然后,服务端需要向客户端发送第二个报文,这个TCP报文中ACK标记位置1,代表服务端确认客户端之前发送的SYN请求,允许客户端建立连接。同时,激活确认序列号,并填充client_isn+1。服务端发送的TCP报文段中,也会将SYN标记位置1,同时设定一个序列号的初始值,称为server_isn,同样服务端发送的报文端中也是不包含数据的。此时服务端处于SYN_RCVD状态。

第三次握手:客户端在接收到服务端发送的SYN+ACK的报文段后,也会为TCP连接分配缓存和变量。同时发送第三个TCP报文段。这个报文段中,将ACK标记位置1,代表确认了服务端的SYN请求,同时激活确认序列号,并填充server_isn+1,而客户端发送的报文段的序列号将使用服务端规定的client_isn+1。此时客户端处于ESTABLISED状态,而服务端在接收到客户端ACK报文后,也处于ESTABLISED状态。至此,双方就建立起了TCP连接。

tcp三次握手失败,服务端会如何处理?

握手失败的原因有两种,第一种是服务端没有收到SYN,则什么都不做;第二种是服务端回复了SYN+ACK后,长时间没有收到ACK响应,则超时后就会发送RST重置连接报文,释放资源

为什么必须是三次握手,而不是两次或者四次

四次握手无非就是服务器用来确认的ACK报文和用来请求的SYN报文分开来发送,但能合在一起并不会造成任何额外的问题,而且还可以节约资源。三次握手其实就是四次握手的一个简化。

其中最主要的一个问题就是两次握手无法在发送数据之前识别出是历史连接,而造成资源浪费。三次握手可以防止旧的重复连接造成混乱。这个是在RFC文档中指出的。

三次握手过程中可以携带数据吗

第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手绝对不可以携带数据

假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据,然后疯狂重复发 SYN 报文的话(因为攻击者根本就不用管服务器的接收、发送能力是否正常,它就是要攻击你),这会让服务器花费很多时间、内存空间来接收这些报文。

而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以是能正常发送/携带数据了。

四次挥手原理

思路:先解释为什么要四次挥手,解释报文中FIN标记位,具体分析四次分手

在解释四次挥手前,我先说一下为什么要四次挥手。主要是由于TCP的半关闭特点,TCP连接是全双工的,因此每个方向都必须单独进行关闭。通俗的来说,两次挥手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次挥手。客户端或服务端均可主动发起挥手动作

刚开始双方都处于ESTABLISED的连接状态,我们假设时客户端先发起的连接请求。

第一次挥手:客户端发送发送一个TCP报文,这个报文段中的FIN标记位置1,报文中会指定一个序列号,并停止发送数据。和三次握手不同,FIN报文段是允许携带数据的。所以,报文段中可能包含整个数据量中最后一段数据。此时客户端处于FIN_WAIT_1 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;状态,等待服务端的确认。

第二次挥手:服务端收到客户端的FIN断开请求的报文段,回复一个ACK报文段,并且把客户端的序号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了。此时服务端处于CLOSE_WAIT 等待从本地用户发来的连接中断请求;状态。

此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2 从远程TCP等待连接中断请求(终止等待 2)状态,等待服务端发出的连接释放报文段。

第三次挥手:等待服务器把需要发送的数据都处理完毕之后,和客户端第一次挥手一样,服务器也会发送一个FIN断开请求的报文段给客户端,并且指定一个序列号。此时服务端处于LAST_ACK状态,等待客户端的确认。

第四次挥手:客户端收到FIN后,也会回复一个ACK报文作为应答,且把服务端的序列值+1作为自己ACK报文的序列号。此时客户端处于TIME_WAIT 等待足够的时间以确保远程TCP接收到连接中断请求的确认时间等待状态。

需要注意的是,这个时候由服务端带客户端的TCP连接并未释放,需要TIME_WAIT等待计时器设置的时间2MSL后才会进入CLOSED状态。

为什么需要TIME_WAIT状态?又为什么TIME_WAIT的时间是2MSL?

TIME_WAIT存在并且等待时间是2MSL的意义是保证连接能够正常被关闭。

如果没有这个等待的时间,或者这个时间很短,假设客户端发送的最后一个ACK在网络中丢失了,而客户端不等待或者等待时间很短,就直接关闭了TCP连接。可是服务端并没有收到最后的确认,就一直停留在LAST_ACK的状态。服务端长时间接收不到ACK,就会触发重传机制,服务端会再发送一个FIN断开的请求。但是客户端此时已经早早处于CLOSED状态,并且关闭了和服务端连接的通道,服务端只能把RST置1,将强制重置TCP连接,导致TCP连接中断,而这样会陷入异常的断开。

由此可见,TIME_WAIT等待的状态是必要的。而且时间的长短也很重要,2MSL简单来说就是一个报文来回的时间,在Linux系统中,2MSL默认是60S。如果等待的时间小于2MSL就意味着没有客户端没有等待服务端报文传输完成就关闭了连接,进入CLOSED状态了。

我们也可以设置2MSL的TIME_WAIT时间来应对一种情况,就是防止旧链接的数据包造成的错乱

简单来说,如果服务器发送的数据报文阻塞了,但是我们的等待时间过短,客户端已经断开了TCP连接,但是阻塞的数据段还在网络中。恰巧客户端此时又发起了一个新的连接,使用相同的端口号的TCP连接被重用后,。而之前这个数据包的序列号又恰好在新连接序号范围内,则将被客户端接收,造成数据错乱的后果。

所以,设计这2MSL的等待时间,足以让两个方向上的数据报都被丢弃,再出现新的连接时,不至于被历史报文造成数据错乱。

当然,这个时间也不能设置过长。时间太长会导致内存资源的占用。

为什么TCP的挥手需要四次而不能是三次

TCP是有三次挥手的,TCP第二次和第三次挥手中间是可能有数据传输的,第三次挥手就是服务端告诉客户端,没有数据要发送了。这种情况下第二和第三次挥手是有可能合并传输的,这样就是三次挥手。TCP还有延迟确认的机制,接收方可以合并确认,这样可以将第二次挥手和第三次挥手以及数据传输放在一起发送。

TCP保证传输可靠的原因

TCP与UDP协议有什么的区别?

  1. TCP面向连接;UDP是无连接的,即发送数据之前不需要建立连接
  2. TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保 证可靠交付
  3. TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流,。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。;UDP是面向报文的,UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。
  4. TCP首部开销20字节;UDP的首部开销小,只有8个字节
  5. UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
  6. 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
  7. TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

你听说过TCP粘包吗?如何解决呢

因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。

如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。

如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。

解决方法

  • 发送端将每个包都封装成固定的长度,比如100字节大小。如果不足100字节可通过补0或空等进行填充到指定长度;
  • 发送端在每个包的末尾使用固定的分隔符,例如\r\n。如果发生拆包需等待多个包发送过来之后再找到其中的\r\n进行合并;例如,FTP协议;
  • 将消息分为头部和消息体,头部中保存整个消息的长度,只有读取到足够长度的消息之后才算是读到了一个完整的消息;
  • 通过自定义协议进行粘包和拆包的处理。

参考文章