常见的TCP通信灵魂二十问-面试必备

一、OSI 参考模型与 TCP/IP 参考模型

如图所示，OSI 参考模型为 7 层模型，从上到下依次为应用层、表示层、会话层、传输层、网络层、数据链路层及物理层，TCP/IP 模型是在 OSI 参考模型的基础上做了一定的精简，形成一个 4 层模型。在层次关系上，两者都采用了分层体系结构，都是对等的层间通信，不同之处在于 TCP/IP 参考模型比 OSI 参考模型层次更清晰简练，在功能上，两者差别不大，都是为了实现两个或多个终端之间的通信。

二、TCP 通信位于网络模型的哪一层？

TCP（Transmission Control Protocol）是一种面向连接的、可靠的、基于字节流的传输层通信协议，无论是 OSI 参考模型，还是 TCP/IP 参考模型，TCP 都位于传输层，TCP 是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。

三、如何理解面向连接、可靠、字节流？

面向连接：意味着 TCP 是点对点之间的通信，不能像 UDP 那样可以一个主机同时向多个主机发送消息，也就是无法实现一对多的情形。

可靠的：无论网络链路如何变化，TCP 都可以保障报文能够到达接收端。

字节流：基于字节流，意味着无论我们消息有多大都可以进行传输。并且消息是有序的，当前一个消息没有收到的时候，即使它先收到了后面的字节已经收到，那么也不能扔给应用层去处理，同时对重复的报文会自动丢弃。

四、为什么需要 TCP 协议？

因为 IP 层是不可靠的，它不保证网络包的一定交付、不保证按序交付、也不保证完整交付。因此如果需要保证网络数据包的可靠性，就必须要通过上层即传输层的 TCP 协议来保证。

五、TCP 与 UDP 之间区别及联系？

TCP 与 UDP 都属于传输层协议，区别如下：

连接机制
TCP 是面向连接的传输层协议
UDP 是不需要连接

服务对象
TCP 是一对一的两点服务
UDP 支持一对一、一对多、多对多

可靠性
TCP 保证数据不丢失、不重复、按需到达
UDP 是尽最大努力交付，不保证交付数据

拥塞控制、流量控制
TCP 有拥塞控制和流量控制机制
UDP 则没有拥塞控制和流量控制机制

六、TCP 首部报文分析

TCP 的首部至少占用 20 个字节，包含的内容有源端口号、目标端口号、序列号、应答号、控制位、校验和等，具体如下所示：

七、简要说明 TCP 三次握手

服务器与客户端均处于 CLOSE 状态

服务器先主动监听某端口，处理 LISTEN 状态

客户端发送 SYN 报文，seq=x，SYN=1

服务器回复 SYN+ACK 报文，seq=y，ack=x+1，SYN=1，ACK=1

客户端回复 ACK 报文，ack=y+1，ACK=1

八、简要说明 TCP 四次挥手

服务器与客户端均处于 ESTABLISHED 状态

客户端打算关闭连接，发送一个 FIN 报文，进入 FIN_WAIT_1 状态。

服务端回复 ACK 报文，进入 CLOSED_WAIT 状态。

客户端收到 ACK 应答报文后，进入 FIN_WAIT_2 状态。

服务端处理完数据后，向客户端发送 FIN 报文，进入 LAST_ACK 状态。

客户端回复一个 ACK 应答报文，之后进入 TIME_WAIT 状态

服务器收到 ACK 应答报文后，进入了 CLOSE 状态，服务端完成连接的关闭。

客户端在经过 2MSL 一段时间后，自动进入 CLOSE 状态，客户端也完成连接的关闭。

九、TCP 握手为什么刚好是三次？

TCP 建立连接时，通过三次握手能防止历史连接的建立，能减少双方不必要的资源开销，能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

不使用「两次握手」和「四次握手」的原因：

两次握手：无法防止历史连接的建立，会造成双方资源的浪费，也无法可靠的同步双方序列号；

四次握手：三次握手就已经理论上最少可靠连接建立，所以不需要使用更多的通信次数。

十、TCP 挥手为什么需要四次？

回顾下四次挥手双方发 FIN 包的过程，就能理解为什么挥手需要四次：

关闭连接时，客户端向服务端发送 FIN 时，仅仅表示客户端不再发送数据了但是还能接收数据。

服务器收到客户端的 FIN 报文时，先回一个 ACK 应答报文，而服务端可能还有数据需要处理和发送，等服务端不再发送数据时，才发送 FIN 报文给客户端来表示同意现在关闭连接。

由于服务端通常需要等待完成数据的发送和处理，所以服务端的 ACK 和 FIN 一般都会分开发送，从而比三次握手导致多了一次。

十一、四次挥手中的 TIME_WAIT 状态？

首先要明确一点：主动关闭连接的一方，才会有 TIME_WAIT 状态。
之所以需要 TIME_WAIT 状态，主要是两个原因：

防止相同的端口重新连接时，服务器收到的是停留在网络间的数据包

保证被动关闭连接的一方能被正确的关闭，即保证最后的 ACK 能让被动关闭方接收，从而帮助其正常关闭

十二 、为什么 TIME_WAIT 时间为 2MSL？

MSL 是 Maximum Segment Lifetime，报文最大生存时间，它是任何报文在网络上存在的最长时间，超过这个时间报文将被丢弃。比如如果被动关闭方没有收到断开连接的最后的 ACK 报文，就会触发超时重发 FIN 报文，另一方接收到 FIN 后，会重发 ACK 给被动关闭方，一来一去正好 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内，因为客户端的 ACK 没有传输到服务端，客户端又接收到了服务端重发的 FIN 报文，那么 2MSL 时间将重新计时。在 Linux 系统中，2MSL 默认值是 60 秒。

十三、什么是 TCP 的保活机制？

定义一个时间段，在这个时间段内，如果没有任何连接相关的活动，TCP 保活机制会开始作用，每隔一个时间间隔，发送一个探测报文，该探测报文包含的数据非常少，如果连续几个探测报文都没有得到响应，则认为当前的 TCP 连接已经死亡，系统内核将错误信息通知给上层应用程序。

十四、已经建立连接，客户端故障怎么办？

这种情况就会触发 TCP 的保活机制，对应的参数包括保活时间、保活探测的次数、保活探测的时间间隔，其中保活时间默认为 7200 秒，保活探测次数为 9，保活探测时间间隔为 75 秒。

也就意味着，如果客户端突然故障，会经过 7200+75*9=7875 秒即 2 小时 11 分 15 后，服务器才会判断该连接失效，以上参数可以手动设置。

十五、TCP/IP 协议与 Socket 之间是什么关系？

网络有一段关于 Socket 和 TCP/IP 协议关系的说法比较容易理解：

TCP/IP 只是一个协议栈，就像操作系统的运行机制一样，必须要具体实现，同时还要提供对外的操作接口。这个就像操作系统会提供标准的编程接口，比如 Win32 编程接口一样，TCP/IP 也要提供可供程序员做网络开发所用的接口，这就是 Socket 编程接口。

所以，Socket 跟 TCP/IP 并没有必然的联系，Socket 编程接口在设计的时候，就希望能适应其他的网络协议。Socket 的出现只是可以更方便的使用 TCP/IP 协议栈而已，其对 TCP/IP 进行了抽象，形成了一些最基本的函数接口，比如 Send,Listen 等。

十六、什么是 SYN 攻击？

我们都知道 TCP 连接建立是需要三次握手，假设攻击者短时间伪造不同 IP 地址的 SYN 报文，服务端每接收到一个 SVN 报文，就进入 SYN_RCVD 状态，但服务端发送出去的 ACK + SYN 报文，无法得到未知 IP 主机的 ACK 应答，久而久之就会占满服务端的 SYN 接收队列（未连接队列），使得服务器不能为正常用户服务。

十七、如何避免 SYN 攻击？

避免 SYN 攻击的两个方案：

通过修改内核参数，控制队列大小，并确定好当队列满之后应该如何处理，比如队列满之后，对新的 SYN 直接回复 RST，丢弃连接。

当 SYN 队列满了之后，后续收到的 SYN，不直接进入 SYN 队列，而是先计算 Cookie 值，再发送，后续可以验证 ACK 包的合法性。

十八、TCP 服务器 Socket 编程流程

服务端初始化 Socket，得到文件描述符

服务端调用 Bind，将绑定在 IP 地址和端口

服务端调用 Listen，进行监听

服务端调用 Accept，建立客户端连接

通过 Send 向客户端发送消息

通过 Receive 接收客户端消息

十九、TCP 客户端 Socket 编程？

客户端初始化 Socket，得到文件描述符

客户端调用 Connect，连接服务器

连接成功调用 Send 向客户端发送消息

通过 Receive 接收客户端消息

二十、Listen 中的 backlog 参数什么意义？

Linux 内核中会维护两个队列：
未完成连接队列（SYN 队列）：接收到一个 SYN 建立连接请求，处于 SYN_RCVD 状态； 已完成连接队列（Accpet 队列）：已完成 TCP 三次握手过程，处于 ESTABLISHED 状态；

在内核 2.2 之后，backlog 变成 Accept 队列，也就是已完成连接建立的队列长度，所以现在通常认为 backlog 是 Accept 队列。