深入学习TCP协议

一.什么是TCP协议?
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接(连接导向)的、可靠的、 基于IP的传输层协议。在OSI模型中,TCP协议工作在自顶向下的第四层——传输层。
二.TCP头部协议格式
深入学习TCP协议

  • 首先源端口号和目的端口号,各占16位,用于区分主机不同的进程,而IP地址用来区分不同的主机,源端口号,源主机IP地址,目的端口号,目的主机IP地址这四者就可以确定唯一的一个TCP连接。
  • 序号,用来标识从TCP发送端到TCP接收端的数据字节流,TCP为发送的每一个字节都编上序号,这里存储每一个数据包的第一个字节的序号,用来解决网络报乱序的问题,如果不编号,怎么知道先来后到呢。
  • 确认序号,为了安全,发送端告诉接收端希望它接收到的下一个数据包的第一个字节的序号,用来解决不丢包问题。
  • 首部长度:表明数据距包头有多少个32位字节,也就是首部有多少字节。
  • 保留的6位:TCP首部中有6个标志比特,它们中的多个可同时被设置为1,主要是用于操控TCP的状态机的,依次 为URG,ACK,PSH,RST,SYN,FIN。每个标志位的意思如下:
  • URG:紧急比特,当URG=1的时候,表明紧急指针字段有效,告诉报文中有紧急数据需要传送,应尽快传送。
  • ACK:确认比特,只有ACK=1的时候确认字段才有效。
  • PSH:代表push操作,代表数据到达接收端以后,立即传送给应用程序,没有在缓冲区停留。
  • RST:复位比特,当RST=1时,表示TCP连接中出现严重错误,需要断开连接,再重新建立连接。
  • SYN:连接比特,当SYN=1时,代表这是一个连接请求或者连接接收报文。SYN标志位和ACK标志位搭配使用,当连接请求的时候,SYN=1, ACK=0;连接被响应的时候,SYN=1,ACK=1。
  • FIN:终止比特,当FIN=1时,用来终止一个连接。

三.TCP三次握手
过程可以类似理解为下面人之间的对话
A:你好,我是A
B:你好A,我是B
A:你好B
通信状态图如下:
深入学习TCP协议
刚开始客户端A和服务器B都处于CLOSED状态,先是服务器B注定监听一个端口号,处于LISTEN状态,A发起一个SYN连接,并告诉B发送的数据包从***x开始,之后处于SYN-SENT状态,B接收到SYN后返回一个SYN,并ACK(确认)SYN,并告知A希望接受的下一个数据包***为x+1,并处于SYN_RCVD状态,A此时接收B的SYN和ACK后,发送ACK的ACK,此时A已经一发一收了,处于ESTABLISHED状态,B在收到A的ACK后也完成一发一收了,也处于EXTABLISHED状态。
四.为什么有三次握手
为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

在书中同时举了一个例子,如下:

"已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。"这就很明白了,防止了服务器端的一直等待而浪费资源。
五.四次挥手
可以理解为下面对话:比如两个人在玩游戏
A:B,我不想玩游戏了
B:哦,你不想玩了啊,我知道了
此时只是A主动断开连接,B可能还有未发完的数据,要等B也主动关闭
B:哦,那我也不玩了,拜拜
A:好的,拜拜
深入学习TCP协议FIN_WAIT_1: 其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等 待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时, 它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报 文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK 报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。 (主动方)

CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN 报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实 际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个 SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关 闭连接。(被动方)

LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报 文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)

TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。 如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无 须经过FIN_WAIT_2状态。(主动方)

CLOSED: 表示连接中断。
断开的时候,当 A 说不玩了,就进入 FIN_WAIT_1 的状态,B 收到 A 不玩了的消息后,进入 CLOSE_WAIT 的状态。

A 收到 B 说知道了,就进入 FIN_WAIT_2 的状态,如果 B 直接跑路,则 A 永远处与这个状态。TCP 协议里面并没有对这个状态的处理,但 Linux 有,可以调整 tcp_fin_timeout 这个参数,设置一个超时时间。

如果 B 没有跑路,A 接收到 B 的不玩了请求之后,从 FIN_WAIT_2 状态结束,按说 A 可以跑路了,但是如果 B 没有接收到 A 跑路的 ACK 呢,就再也接收不到了,所以这时候 A 需要等待一段时间,因为如果 B 没接收到 A 的 ACK 的话会重新发送给 A,所以 A 的等待时间需要足够长。

六.为什么有四次挥手
TCP是一种全双工模式,也就是当A说他想中断连接的时候,它发出FIN报文段,这时只是它自己不想传输数据了,并不代表B也没有数据传输了,B在回复ACK报文后,已经知道A没有数据传输了,但是自己还可以继续给A传数据,在B发送FIN报文段时,代表B也没有数据传输了,告诉了A,此时A回复ACK,确认B知道了我要中断传输,就完成了整个连接的关闭。
七.time-wait状态产生的原因,危害,如何避免
1.如何产生
由上面四次挥手的状态图可知,在首先发起close关闭的一方,在最后发送确认后就进入time-wait状态,并要等待2MSL才能恢复状态。MSL代表数据包在网络中生存的最大时间。在这个2MSL期间,定义这个TCP连接的四元组不能被使用。
2.产生该状态的本质原因
1)为实现TCP全双工连接的可靠释放
考虑这样一种情况,假设主动关闭连接的一方(client)最后发送的ACK丢失,TCP重传机制会让执行被动关闭的一方(server)重新发送FIN,在FIN到达client之前,client必须维持这条连接,也就是说这条TCP连接的资源(client方的ip和port)不能被立即释放或重新分配,直到另一方重发的FIN到达之后,client重发ACK,并且在2MSL期间没有收到另一方的FIN,该TCP连接才能恢复初始的close状态。如果主动关闭一方不维持这个time-wail的状态,那么当被动关闭一方的FIN到达后,主动给关闭一方的TCP传输层会用RST包响应,这会被对方认为有错误发生,但是实际只是正常的连接关闭。
2)为使旧的数据包因网络过期而消失
假设TCP四次挥手的过程没有time-wait状态,假设当前TCP连接因为某种原因结束连接,并很快建立一条相同的TCP连接,那么TCP协议栈是无法区分这两条TCP连接是不同的,中间的先释放在建立的过程对其来说是感知不到的,就可能会发生下面的情况,前一条TCP连接的传送到接收方的数据包会被新的TCP连接当作当前TCP连接的正常数据传给上层应用层,这样会引发数据错乱,本不应该上传的数据被传到上层了,后果会可想而知。
3.如何避免
首先服务器可以设置SO_REUSEADDR套接字选项来通知内核,如果端口忙,但TCP连接位于TIME_WAIT状态时可以重用端口。在一个非常有用的场景就是,如果你的服务器程序停止后想立即重启,而新的套接字依旧希望使用同一端口,此时SO_REUSEADDR选项就可以避免TIME_WAIT状态。