前言:前面一篇文章《手把手写C++服务器(15):网络编程入门第一个TCP项目》介绍了一个简单入门级的TCP项目,这一篇文章重点讲一讲面试常见的TCP协议相关的十个问题,都是后端开发程序员必知必会的经典知识点。
目录
问题一:讲一下TCP三次握手四次挥手的过程
三次握手:
四次挥手:
问题二:TCP和UDP之间有什么区别?
问题三:TCP拥塞控制有哪几种方法?什么是拥塞避免?什么是快速恢复?什么是拥塞发生?
拥塞控制常用方法:
拥塞窗口
拥塞避免
快速恢复
问题四:什么是慢启动算法?
问题五:什么是TCP黏包和拆包问题?为什么会产生?如何解决?
问题六:什么是滑动窗口机制?
问题七:什么是拥塞控制?什么是超时重传机制?
拥塞控制
超时重传机制
问题八:什么是SACK选择性确认?
问题九:什么是SYN泛洪攻击?如何防御SYN泛洪攻击?
问题十:怎样保证TCP可靠性?
参考:
开始客户端和服务器都处于CLOSED状态,然后服务端开始监听某个端口,进入LISTEN状态
第一次握手(SYN=1, seq=x),发送完毕后,客户端进入 SYN_SEND 状态
第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1), 发送完毕后,服务器端进入 SYN_RCVD 状态。
第三次握手(ACK=1,ACKnum=y+1),发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手,即可以开始数据传输。
第一次挥手(FIN=1,seq=u),发送完毕后,客户端进入FIN_WAIT_1 状态
第二次挥手(ACK=1,ack=u+1,seq =v),发送完毕后,服务器端进入CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态
第三次挥手(FIN=1,ACK=1,seq=w,ack=u+1),发送完毕后,服务器端进入LAST_ACK 状态,等待来自客户端的最后一个ACK。
第四次挥手(ACK=1,seq=u+1,ack=w+1),客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
TCP提供面向连接的、可靠的数据流传输,而UDP提供的是非面向连接的、不可靠的数据流传输。
TCP传输单位称为TCP报文段,UDP传输单位称为用户数据报。
TCP有拥塞控制和流量控制保证数据传输的安全性;UDP没有拥塞控制,网络阻塞不会影响源主机的发送效率。
TCP是点对点的两点间服务,UDP支持一对一、一对多、多对一、多对多的交互通信。
TCP首部开销大,20字节;UDP首部开销小,8字节。
TCP对应的协议和UDP对应的协议
TCP对应的协议:
(1) FTP:定义了文件传输协议,使用21端口。
(2) Telnet:一种用于远程登陆的端口,使用23端口,用户可以以自己的身份远程连接到计算机上,可提供基于DOS模式下的通信服务。
(3) SMTP:邮件传送协议,用于发送邮件。服务器开放的是25号端口。
(4) POP3:它是和SMTP对应,POP3用于接收邮件。POP3协议所用的是110端口。
(5)HTTP:是从Web服务器传输超文本到本地浏览器的传送协议。
UDP对应的协议:
(1) DNS:用于域名解析服务,将域名地址转换为IP地址。DNS用的是53号端口。
(2) SNMP:简单网络管理协议,使用161号端口,是用来管理网络设备的。由于网络设备很多,无连接的服务就体现出其优势。
(3) TFTP(Trivial File Transfer Protocal),简单文件传输协议,该协议在熟知端口69上使用UDP服务。
慢启动
拥塞避免
拥塞发生
快速恢复
发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口小于拥塞窗口。
拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。这样拥塞窗口按线性规律缓慢增长。
无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。
1.当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3,然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个“老”的数据包离开了网络。
2.再收到重复的ACK时,拥塞窗口增加1。
3.当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。
在 TCP 刚刚连接好,开始发送 TCP 报文段时,先让拥塞窗口 cwnd = 1,即一个最大报文段长度 MSS。而在每收到一个对新的报文段的确认后,将 cwnd 加倍,即刚开始会增大一个 MSS。用这样的方法逐步增大发送方的拥塞窗口 cwnd,可以使分组注入到网络的速率更加合理。例如,A 向 B 发送数据,当发送时 A 的拥塞窗口为 2,那么 A 一次可以发送两个 TCP 报文段,当经过一个 RTT 后,A 收到 B 对刚才两个报文的确认,于是就把拥塞窗口调整为 4,即下一次发送时就可以发送 4 个报文段。
使用慢开始算法后,每经过一个传输轮次,拥塞窗口 cwnd 就会加倍,即 cwnd 的大小呈指数形式增长。这样慢开始一直把拥塞窗口 cwnd 增大到一个规定的慢开始门限 ssthresh(阈值),然后改用拥塞避免算法。
TCP是面向流,没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。
为什么会产生粘包和拆包呢?
要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包;
接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包;
要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包;
待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。即TCP报文长度-TCP头部长度>MSS。
解决方案:
发送端将每个数据包封装为固定长度
在数据尾部增加特殊字符进行分割
将数据分为两部分,一部分是头部,一部分是内容体;其中头部结构大小固定,且有一个字段声明内容体的大小。
滑动窗口协议的基本工作流程就是由接收方通告窗口的大小,这个窗口称为提出窗口,也就是接收方窗口。接收方提出的窗口则是被接收缓冲区所影响的,如果数据没有被用户进程使用那么接收方通告的窗口就会相应得到减小,发送窗口取决于接收方窗口的大小。可用窗口的大小等于接收方窗口减去发送但是没有被确认的数据包大小。
所谓的拥塞控制就是为了防止过多的数据注入网络中,这样可以使网络中的路由器或链路不会过载。当出现拥塞时,端点并不能了解到拥塞发生的细节,对通信连接的端点来说,拥塞往往表现为通信时延的增加。拥塞控制和流量控制相似的地方是通过控制发送方发送数据的速率来达到效果。
拥塞控制与流量控制的区别:拥塞控制是让网络能够承受现有的网络负荷,它是一个全局性的过程,涉及所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。而流量控制往往使指点对点通信量的控制,即接收端控制发送端,它所做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
为了更好地对传输层进行拥塞控制,有以下四种算法:慢开始、拥塞避免、快重传、快恢复。发送方在确定发送报文段的速率时,既要根据接收方的接收能力,又要从全局考虑不要使网络发生拥塞。因此,TCP 协议要求发送方维护以下两个窗口:
(1) 接收窗口 rwnd,接收方根据目前接收缓存大小所许诺的最新的窗口值,反映了接收方的容量。有接收方根据其放在 TCP 报文的首部的 "窗口" 字段通知发送方。
(2) 拥塞窗口 cwnd,发送方根据自己估算的网络拥塞程度而设置的窗口值,反映了网络当前容量。只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减少一些,以减少注入网络中的分组数。发送窗口的上限值应当取接收窗口 rwnd 和拥塞窗口 cwnd 中较小的一个,即:Min[rwnd, cwnd]。
TCP 每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到期但还没有收到确认,就要重传这一报文段。当检测到接收数据有错误时,会采取直接丢弃出错的数据,发送端等待接收端的确认超时后,会自动重发该报文段。
由于 IP 数据报在传输的时候选择的路由变化很大,因此传输层的往返时延的方差很大。为了计算超时计时器的重传时间,TCP 采用一种自适应算法,它记录一个报文段发出的时间,以及收到相应确认的时间,这两个时间之差叫做报文段的往返时间(RTT)。TCP 保留了 RTT 的一个加权平均往返时间 RTTs,当第一次测量 RTT 样本时,RTTs 值就为所测量到的 RTT 样本的值,但之后每测量一个新的 RTT 样本,就按下式重新计算一次 RTTs:
新的 RTTs = ( 1- a ) * (旧的 RTTs) + a(新的RTT样本)
在上式中 0 <= a < 1。若 a 很接近于零,表示新的 RTTs 值和旧的 RTTs 值相比变化不大,而受新的 RTT 样本影响不大(RTT值更新较慢)。若 a 接近于 1,则表示新的 RTTs 值受新的 RTT 样本的影响较大(RTT值更新较快)。[RFC 2988] 推荐的 a 值为 0.125。
所以超时计时器设置的超时重传时间(RTO)应略大于上面得出的加权平均往返时间 RTTs。即 RTO = RTTs + 4RTTd。其中 RTTd 是 RTT 的偏差的加权平均值,它与 RTTs 和新的 RTT 样本之差有关。当第一次测量时,RTTd 取为测量到的 RTT 样本值的一半,以后测量中,使用下式计算:新的 RTTd = (1-β) *(旧的 RTTd) + β*|RTTs - 新的 RTT 样本|,其中 β 是个小于 1 的系数,它的推荐值是 0.25。
在 TCP 头部「选项」字段里加一个 SACK
的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK
信息发现只有 200~299
这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。
SYN Flood是一种典型的DoS (Denial of Service,拒绝服务) 攻击,它在短时间内,伪造不存在的IP地址,向服务器大量发起SYN报文。当服务器回复SYN+ACK报文后,不会收到ACK回应报文,导致服务器上建立大量的半连接半连接队列满了,这就无法处理正常的TCP请求啦。
主要有 syn cookie和SYN Proxy防火墙等方案应对。
syn cookie:在收到SYN包后,服务器根据一定的方法,以数据包的源地址、端口等信息为参数计算出一个cookie值作为自己的SYNACK包的序列号,回复SYN+ACK后,服务器并不立即分配资源进行处理,等收到发送方的ACK包后,重新根据数据包的源地址、端口计算该包中的确认序列号是否正确,如果正确则建立连接,否则丢弃该包。
SYN Proxy防火墙:服务器防火墙会对收到的每一个SYN报文进行代理和回应,并保持半连接。等发送方将ACK包返回后,再重新构造SYN包发到服务器,建立真正的TCP连接。
这是一个综合性的问题,涉及上述讲好几个问题。
从以下几个方面回答:
参考:
- https://blog.csdn.net/qq_41895747/article/details/104699853
- https://blog.csdn.net/BIggyGuan/article/details/108410136
- https://blog.csdn.net/qq_38289815/article/details/100176632
- https://zhuanlan.zhihu.com/p/101134290
- https://mp.weixin.qq.com/s/xa--iwKsIBFqr1CLqRr6qA
- 《计算机网络》
- 《图解TCP/IP》