TCP详解
在TCP/IP协议五层模型中,传输层有着两大主角:TCP和UDP。本篇来分析TCP协议。
我们知道,TCP是面向连接的协议,在两端通信时,必须先建立链接,然后才能传输数据。而建立连接采用的就是三次握手法,断开连接采用的是四次挥手法,现在我们先来看一下三次握手的具体实现。
首先,我们来看一下TCP的一个数据包结构:
TCP报头源端口号:表示发送端端口号,字段长为16位。
目标端口号:表示接收端口号,字段长为16位。
序列号(seq):为当前端成功发送的数据位数(由计算机生成的一个随机数作为其初始值,以后再将每次成功转发过去的字节数累加到初始值上表示数据的位置 )。
确认号:为当前端成功接收的数据位数+1。(表示下一次应该收到的数据位置,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收)
首部长度:该字段长度为4位,单位为4字节(32位)。TCP首部长度不包括选项的话,是20个字节,20/4=5,5的二进制序列:0101,报头长度也叫数据偏移,所以该字段可以设置为5,选项字段最大的是40字节,所以,TCP首部长度为最大为20+40=60字节,该字段可以设置的最大长度为60/4=15。
保留位:该字段主要是为了以后扩展时使用,其长度为6位。一般设置为0,即使收到的包在该字段不为0,此包也不会丢弃。
标志位:字段长为6,每一位从左到右分别为:URG、ACK、PSH、RST、SYN、FIN。当对应的值为1,表示有具体含义:
URG:紧急指针是否有效。为1,表示某一位需要被优先处理。
ACK:确认号是否有效,一般置为1。
PSH:提示接收端应用程序立即从TCP缓冲区把数据读走。
RST :对方要求重新建立连接,复位。
SYN:为1表示请求建立连接,并在其序列号的字段进行序列号的初始值设定。
FIN:希望断开连接。
窗口大小:接收缓冲区的大小,TCP不允许发送超过此处所示大小的数据。
校验和:发送端填充,CRC校验,接收校验不通过,则认为数据有问题。和UDP的区别是,UDP校验的是数据本身,TCP校验的不仅包含TCP首部,而且包含TCP数据部分。
紧急指针:只有在URG为1时有效,该字段为1表示本报文的段中的紧急数据的指针。
选项:用于提高TCP的传输性能。需要根据首部长度进行控制,其最大长度为40字节。
数据:真实有效数据
在三次握手中和四次挥手中,我们只需要关注序列号、确认号、标志位即可。
三次握手:
三次握手过程上面是三次握手过程的示意图,根据图上我们知道,整个过程总共发送了3个数据包,可能有的小伙伴不是很理解这个图,没关系,我们再来换其他的图,看看这3个数据包到底是什么样子。
第一次握手:
客户端-->服务端首先,当客户端需要与服务端建立连接的时候,会发送一个标志位SYN=1(表示请求建立连接),序列号=X(随机生成的初始化值)的数据包给服务端主机,此时客户端会进入SYN_SENT状态,等待服务器确认。
第二次握手:
服务端-->客户端然后,服务端收到请求后,必须确认客户的数据包,并且会给客户端发送一个标志位ACK=1和SYN=1的数据包,数据包中序列号=Y(随机生成的初始化值),确认号=X+1(表示确认接收了一个序列号为X的数据包,同时也表示期望接收的下一个序列号为X+1的数据包),此时服务器进入SYN_RECV状态。
第三次握手:
客户端-->服务端最后,当客户端收到数据包之后,知道了之前序列号=X的数据包已经被服务端收到,但是此时,服务端还不知道客户端有没有收到自己的确认包,所以,客户端会再次发送一个数据包,用来通知服务端自己已经收到它的确认包了,所以这个包里面的标志位只有ACK=1,表示确认包,并且序列号=X+1,确认号=Y+1,为什么都是+1 ?因为这三个包是特殊的包,专门用来建立连接的,所以并没有真实的有效数据,如果有有效数据,则还需要加上接收到的数据长度。此包发送完毕,客户端和服务器进入ESTAB_LISHED(TCP连接成功)状态,完成三次握手,接下来就可以传输数据了。
未连接队列:在三次握手协议中,服务器会维护一个未连接队列,该队列为每个客户端的SYN包开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,当收到客户的确认包后才删除该条目,服务器进入ESTAB_LISHED状态。
那么问题来了:
1.为什么需要三次握手,两次不可以吗?
首先,假如两次可以的话,我们来分析这种情况:
客户端首先发出一个SYN包,服务端收到后,会为此SYN包开设一个条目,同样会发一个数据包给客户端,如果此数据包在网络中丢失了,客户端没有接收到,则认为该连接没有建立,会进行重传,假设服务端每次发送的数据包都丢失,则会导致客户端一直SYN,服务端就会产生多个无效的连接,占用资源,这个时候服务器可能会挂掉,这个现象也就是我们听过的“SYN的洪水攻击”。 即使客户端收到了数据包,由于是2次握手,服务端也不知道它收没收到,所以它可能会创建多个连接,浪费开销。而三次握手就确保了一个请求只有一个连接的结果。
四次挥手:
四次挥手过程由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
第一次挥手:
首先,客户端发送一个FIN,用来关闭客户端到服务器的数据传送,然后等待服务器的确认。其中终止标志位FIN=1,序列号seq=x。
第二次挥手:
服务器收到这个FIN,它发送一个ACK,确认ack为收到的序号+1。
第三次挥手:
关闭服务器到客户端的连接,发送一个FIN给客户端。
第四次挥手:
客户端收到FIN后,并发回一个ACK报文确认,并将确认序号seq设置为收到序号+1。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
状态详解:
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看到。
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对 方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
那么问题又来了:
1.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
2.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可能不会马上关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
TCP的优点:
可靠,稳定 。TCP的可靠体现在TCP在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制,在数据传完后,还会断开连接用来节约系统资源。
TCP的缺点:
慢,效率低,占用系统资源高,易被攻击, TCP在传递数据之前,要先建连接,这会消耗时间,而且在数据传递时,确认机制、重传机制、拥塞控制机制等都会消耗大量的时间,而且要在每台设备上维护所有的传输连接,事实上,每个连接都会占用系统的CPU、内存等硬件资源。