TCP状态机

其实,网络连接是一个很抽象的东西,它其实没有一个实体把两端连接起来。TCP所谓的连接,只不过是在通讯的双方维护一个连接的状态,让它看上去好像有连接一样。所以,TCP的状态机是非常重要的。 下面,让我们一起来看看,在三次握手和四次挥手的过程中,连接状态是怎么变化的。

  1. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2332713/1615089383755-ef4c28c3-9328-4937-bb2a-9df6c47e8717.png#height=488&id=egI6Y&margin=%5Bobject%20Object%5D&name=image.png&originHeight=976&originWidth=875&originalType=binary&ratio=1&size=528854&status=done&style=none&width=437.5)

下面说一下几个需要注意的点:

  • 关于ISN的初始化,ISN就是发起第一个seq的值,这个值是不能hard code的。根据RFC793的规范,ISN会和一个假的时钟绑定在一起,这个时钟会每4ms对ISN做加1操作,直到超过2^32, 又重新从0开始。一个ISN的周期是大约是4.55h。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过MSL。所以,只要MSL的值小于4.55h。 那么,我们就就不会得到重复的ISN。
  • 还有一个问题,那就是如果拿到了一个重复的ISN,会出现什么问题?我一直没有想明白,后面要补充一下
  • 关于建立连接时SYN超时。试想一下,如果server端收到client后,回复SYN&ACK后,client掉线了(或者由于某些原因一直不回复),server端没有收到client的ACK。那么,连接就会一直处于一个中间状态。没有成功,也没有失败。于是,server端如果在一定时间内没有收到ACK,会重发SYN-ACK。在linux下,默认重试的次数为5次。重试的时间间隔从1s开始,采用指数退避的算法,所以5次的间隔时间为1s,2s, 4s,8s,16s, 总共31s。第5次发布后还要等32s才知道超时了。所以,总共需要1s + 2s + 4s + 8s + 16s + 32s = 2^6 - 1 = 63s,TCP才会把这个连接断开。
  • 关于SYN Flood攻击,一些恶意的人给服务器发送了一个SYN之后就下线了,于是服务器需要等默认63s之后才会断开连接。这样,攻击者就可能把syn连接的队列耗尽,让正常的连接请求不能处理。于是,linux下给了一个叫tcp_synccookies的参数来应对这个事。当SYN的队列满了之后,TCP会通过源地址端口。目标地址端口和时间戳打造出一个特别的seq number发回去。如果是攻击者则不会有响应,如果是正常连接,客户端会把这个SYN发回来,然后服务端可以通过cookie建立连接(即使不在SYN队列中)。但是,千万不能用tcp_syscookies来处理正常的大负载连接情况。因为synccookies是一种妥协的TCP版本,贸然使用会造成不良的后果。所以,对于正常的请求,有三个TCP参数可供选择,第一个是:tcp_synack_retries,可以用来减少重试的次数;第二个是:tcp_max_syn_backlog, 可以增到SYN的连接数;第三个是:tcp_abort_on_overflow. 对于处理不过来的请求就干脆直接拒绝掉。
  • 关于MSL和TIME_WAIT, 在TCP的状态图中,从TIME_WAIT状态到CLOSED状态,有一个超时设置,这个超时设置是2*MSL(RFC793定义了MSL为2分钟,Linux设置成了30s),翻译过来的意思就是TCP报文最大的存活时间。所以为什么会有这个状态呢?有两个原因:1. TIME_WAIT确保有足够的时间让对端收到了ACK, 如果被动关闭的那方没有收到ACK, 就会触发被动端重发FIN,一来一去刚好2MSL 2. 有足够的时间让这个连接不跟后面的连接混在一起(有些路由器会自做主张,缓存IP数据包。如果连接被重用了,那么这些延迟收到的包就有可能会跟新连接混在一起??? 不理解)
  • TIME_WAIT数量过多会怎么办?暂时不关注问题,有空再回来看

参考

TCP 的那些事儿(上)
为什么会有TIME_WAIT