计算机网络——TCP篇(学习笔记)

前言

本文章为网络编程TCP篇的学习笔记,文章中的图片,文字部分引用小林coding阿秀的学习笔记知识星球如有侵权,请联系删除。

TCP简介

TCP是面向连接的、可靠的、基于字节流的传输层通信协议。它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
面向连接:就是两个单位一对一的连接
可靠的:就是TCP连接确保了通信之间报文一定能够到达,无论网络链路中出现了什么样的变化
字节流:指消息传输时会被分组成多个TCP报文,这个报文是有序的,如果前一个报文没有收到那么即使收到了后一个报文,应用层也不会去处理,并且重复的TCP报文会自动删除。
一个TCP连接中包含着一些信息组合,这些信息包括Socket(由IP地址和端口号组成),序列号(解决网络包发送过程中乱序的问题),窗口大小(用来实现流量控制)。
TCP连接中是通过四元组来唯一确定一个连接的,四元组包括:源地址,源端口,目标地址,目标端口。

TCP和UDP的区别

1、TCP是面向连接的传输层协议,传输数据前先要建立连接,而UDP是不需要连接的,即刻传输数据。
2、TCP是一对一的两点服务,一条连接只能由两个端点,而UDP是支持一对一,一对多,多对多的交互通信。
3、TCP非常可靠,数据可以无差错,不丢失,不重复的到达,并且由流量控制和拥塞控制等手段确保数据的安全性,UDP只是数据传输速度特别快,但是不保证数据的可靠交付(可以基于UDP实现一个QUIC协议可以实现数据的可靠交付)
4、TCP首部长度比会变化并且比较大,而UDP首部只有8个字节不会变化,传输数据开销小。
5、两者的传输方式也不同,TCP是流式传输没有边界,UDP是一个包一个包发送,有边界,但可能会出现丢包和乱序。
6、两者的分片方式不同,如果传输的数据过大,TCP是在传输层进行分片,目标主机同样在传输层组装TCP数据包,而UDP是在IP层分片,在IP层组装。

TCP连接建立

TCP三次握手建立过程

三次握手过程中服务器和客户端的状态变化和数据的收发图片如下:
图片来源于小林coding
1、握手前,客户端和服务端都是处于close状态。
2、服务端先改变状态,监听某一个端口,服务器改变状态变为LISTEN状态
3、如果有客户端想要建立连接,那么客户端发送第一个报文SYN报文,在这个报文里面客户端会随机初始化一个序列号(client_isn),并将序列号放入TCP首部的序列号字段中,并把标志位中的SYN置为1,表示向服务端发起连接,在这个报文中不包含应用层数据,发送之后,客户端会处于SYN-SENT状态。
4、服务端收到客户端发送来的SYN报文后,服务端会回发一个SYN+ACK报文,在这个报文里面,首先服务端也会随机初始化一个序列号(server_isn)将序列号放入TCP首部,然后把TCP首部的确认应答号字段填入client_isn + 1,再将SYN和ACK标志位置为1,把报文发送给客户端,这个报文也不包含应用层的数据,发送出去之后,服务端会处于SYN-RCVD状态。
5、客户端收到SYN-ACK报文之后还要再给服务端发送一个ACK报文,在这个报文里将ACK标志位置为1,将确认应答号字段填入server_isn + 1,再将数据发送给服务端,这次的报文可以携带数据,发送之后客户端处于ESTABLISHED状态。
6、服务端收到ACK报文也进入了ESTABLISHED状态。

为什么要三次握手

只有三次握手才可以初始化Socket,序列号和窗口大小并建立TCP连接,并且可以阻止重复的历史连接的初始化(主要原因),避免资源的浪费。
重复的历史连接的初始化就是指:已经不需要的老的数据比新的数据早到服务端,服务端发送报文后客户端可以验证拒绝此次连接发送RST报文给服务端,从而避免了旧SYN报文建立了TCP连接,在这里如果只有两次TCP连接那么旧的数据就会建立一个我们并不需要的TCP连接,这会造成资源的浪费。

某一次握手失败会发生什么

1、第一次握手失败,服务端收不到客户端发送来的报文,那么客户端会触发超时重传机制,不断重发SYN报文,一般重发5次,每一次重发的时间间隔是上一次时间间隔的两倍,如果都失败就断开连接。
2、第二次握手失败,客户收不到服务端发送的SYN-ACK报文,这时客户端会不断重新发送SYN报文直达达到重发上限,服务端这里也会触发超时重传机制,不断重新发送SYN-ACK报文。
3、第三次握手失败,服务端收不到客户端的ACK报文,会触发超时重传机制不断发送SYN-ACK报文,直到建立连接或是达到重传上限,这里ACK报文是不有重传的,如果ACK报文丢失了就由对方重发对应的报文。

什么是SYN攻击,如何避免?

SYN攻击就是攻击者短时间伪造不同的SYN报文,服务端每收到一个SYN报文就会发送一个SYN-ACK报文,但因为攻击者不会发送ACK报文,那么发送的SYN报文就会占满服务端的半连接队列,使得服务端不能正常服务用户。
TIPS:半连接队列也成为SYN队列,全连接队列,也成为accept队列。正常工作时,SYN报文放入半连接队列,服务端发送SYN-ACK报文如果收到ACK报文就从半连接队列中取出,放入accept队列(全连接队列),上层应用通过调用accept函数,从accept队列中取出连接对象,不管是半连接还是全连接队列,只要超过长度限制都会丢弃报文。
解决SYN攻击的方法:
1、调大netdev_max_backlog,这是一个保存从网卡接受数据的队列,调大这个队列的上限可以存储更多的数据供内核逐步处理。
2、增大TCP的半连接队列
3、开启net.ipv4.tcp_syncookies,开启后可以在不使用半连接队列的情况下建立连接,在SYN队列满了之后,后续的SYN报文不会丢弃,而是计算出一个cookie,将cookie放入第二次握手报文的序列号中,发送给客户端,服务端收到客户端的应答后会验证ACK的合法性,如果合法就放入accept队列中。
4、减少 SYN+ACK 重传次数,减少重传次数可以加快处于SYN-REVC状态的TCP连接的断开从而释放SYN队列的部分内存。

TCP的四次挥手

TCP断开连接通过四次挥手的方式实现,四次挥手的图片如下所示:
图片来源于小林coding
1、当客户端打算关闭连接时,就会发送一个FIN报文,在报文里面TCP首部的FIN标志位置为1,随后客户端进入FIN_WAIT_1状态。
2、服务端收到该报文后,向客户端发送ACK应答报文,接着服务端进入CLOSE_WAIT状态。
3、客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
4、等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
5、客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态。
6、服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
注:主动关闭连接的,才会有TIME_WAIT状态。

为什么要四次挥手

在客户端发送FIN包时只是代表客户端不再发送数据了,还是要接受数据的。
而服务端收到FIN报文后先回应ACK报文,但是还是要等接受的数据全部处理完成才发送FIN报文给客户端来同意断开连接。
如果被动关闭方「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么第二和第三次挥手就会合并传输,这样就出现了三次挥手。
TIPS:TCP延迟确认机制就是指:当有响应数据要发送时,ACK 会随着响应数据一起立刻发送给对方。当没有响应数据要发送时,ACK 将会延迟一段时间,以等待是否有响应数据可以一起发送。如果在延迟等待发送 ACK 期间,对方的第二个数据报文又到达了,这时就会立刻发送 ACK。

某一次挥手失败会发生什么

1、第一次挥手失败,那么客户端迟迟收不到被动方的 ACK 的话,也就会触发超时重传机制,重传 FIN 报文,达到重传上限时,会直接进入到close状态。
2、第二次挥手失败,因为ACK不会重传,那么第二次挥手失败,客户端会触发超时重传机制,重传FIN报文,直到握手成功或是达到重传上限。如果达到重传上限会再等一段时间客户端会直接断开连接。
3、第三次挥手失败,服务端处于 CLOSE_WAIT 状态时,调用了 close 函数,内核就会发出 FIN 报文,同时连接进入 LAST_ACK 状态,等待客户端返回 ACK 来确认连接关闭。如果收不到客户端发来的ACK,服务端就会重发FIN报文,重发到上限还没收到,服务器就会断开连接,客户端也会因为长时间没有收到FIN报文而断开连接。
4.第四次挥手失败,服务端就会重发 FIN 报文,重发到上限会自动断开连接,而客户端在收到FIN报文后会开启时常为2MSL的定时器,如果中途再次收到,就重置定时器,等待2MSL后会自动断开连接。
TIPS:为什么要等待2MSL,因为MSL是报文最大生存时间,而网络中的数据包一来一回需要等待2倍的时间,所以2MSL至少允许报文丢失一次。
为什么要TIME_WAIT状态:只有发起断开连接的一方才会有TIME_WAIT状态,是为了防止历史连接中的数据,被后面相同的四元组的连接错误的接受从而建立起了不需要的TCP连接(有了TIME_WAIT会等待2MSL这样,原来连接中的数据包都会在自然中消失),保证被动关闭连接的一方,能正确被关闭。

TCP的重传机制

TCP的重传机制主要是针对数据包丢失的问题,,数据包丢失了会触发重传机制,主要有四种分别是:超时重传,快速重传,SACK,D-SACK。
超时重传:发送数据时设置了一个定时器,当超过指定的时间后没有收到ACK应答报文,会重发数据。并且每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。
快速重传:快速重传不以时间为驱动,而是以数据驱动重传,发送方收到了三个同样的ACK就会触发快速重传,重传之前丢失的信号。但是会有问题是不知道该重传一个报文还是重传所有报文。
SACK方法(选择性确认):这种方式需要在TCP选项字段里加SACK的东西,可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
Duplicate SACK:使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。这有几个好处,可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;可以知道是不是「发送方」的数据包被网络延迟了;可以知道网络中是不是把「发送方」的数据包给复制了。

滑动窗口

窗口概念的引入:本来双方通讯应答是一方发送等一方接受并且回复后,才会继续发送,这样效率很低,就引入了窗口的概念,设置窗口的大小,实际上就是操作系统开辟了一个缓存空间,在发送方主机等到确认应答返回之前,把数据在缓冲区中保留,如果收到确认应答,这个数据就可以清除。
窗口大小的设置:是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
发送方的滑动窗口:由四部分组成分别是已发送并收到 ACK确认的数据,已发送但未收到 ACK确认的数据,未发送但总大小在接收方处理范围内(接收方还有空间),未发送但总大小超过接收方处理范围(接收方没有空间)。
接收方的滑动窗口:由三部分组成已成功接收并确认的数据(等待应用进程读取),未收到数据但可以接收的数据,未收到数据并不可以接收的数据。
接收方和发送方的滑动窗口大小是约等于的关系。

流量控制

TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。流量控制是通过滑动窗口来实现的。
在数据传输中为了防止数据大小超过了接受窗口的大小使得数据丢失,TCP 规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少缓存,这样就可以避免了丢包情况。
如果接受窗口变为0会关闭窗口,这时只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。如果接收窗口仍然为 0,那么收到这个报文的一方就会重新启动持续计时器;如果接收窗口不是 0,那么就可以正常通信了。

拥塞控制

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大…
所以就有了拥塞控制,为了避免发送方的数据填满整个网络。所以发送方定义了一个拥塞窗口cwnd,它会根据网络的拥塞程度动态变化的。拥塞窗口的变化规则是只要网络中没有出现拥塞,cwnd 就会增大;但网络中出现了拥塞,cwnd 就减少。
网络中出现拥塞的判断方法:只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。
拥塞控制的控制算法主要分为四个分别是慢启动,拥塞避免,拥塞发生,快速恢复。
慢启动:TCP 在刚建立连接完成后,首先是有个慢启动的过程,当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。拥塞窗口的大小是成指数上涨的1,2,4,8…直到上涨到慢启动门限ssthresh (slow start threshold),一般来说 ssthresh 的大小是 65535 字节。当cwnd <ssthresh使用慢启动,当cwnd >= ssthresh使用拥塞避免算法。
拥塞避免算法:每当收到一个 ACK 时,cwnd 增加 1/cwnd。这样cwnd就从指数增长变成了线性增长。就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制,也就进入了「拥塞发生算法」。
拥塞发生算法:当网络出现拥塞,也就是会发生数据包重传主要有超时重传和快速重传两种,不同的重传机制会有不同的拥塞发生算法。当发生超时重传时使用拥塞发生算法,这时ssthresh 设为 cwnd/2,cwnd恢复为初始值,接着会重新开始慢启动,这种反应会很强烈,会造成网络的卡顿。
当发生快速重传时的拥塞发生算法:这是TCP会认为情况不严重,cwnd = cwnd/2 ,ssthresh = cwnd;进入快速恢复算法。
快速恢复算法:拥塞窗口 cwnd = ssthresh + n(n是有n个数据包收到了,一般为3的倍数因为使用快速恢复算法是因为触发了快速重传),再重传丢失的数据包,如果再收到重复的 ACK,那么 cwnd 增加 1;如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。
图片来源于小林coding

首先,快速恢复是拥塞发生后慢启动的优化,其首要目的仍然是降低 cwnd 来减缓拥塞,所以必然会出现 cwnd 从大到小的改变。其次,过程2(cwnd逐渐加1)的存在是为了尽快将丢失的数据包发给目标,从而解决拥塞的根本问题(三次相同的 ACK 导致的快速重传),所以这一过程中 cwnd 反而是逐渐增大的。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值