介绍
TCP是一个面向连接的协议,所以在连接双方发送数据之前,都需要先建立一条连接。这和IP协议,IMCP协议完全不同。IP协议,IMCP协议都只是发送数据而已,大多数都不关心发送的数据是不是送到,UDP协议尤其明显,因为UDP都不用考虑数据分片。
正文
首先介绍TCP包(如图1),然后通过TCP包来了解它的连接和中止过程。
上图中的字段需要重点介绍一下:
(1)源端口、目标端口:计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通信。源端口、目标端口是用16位表示的,可推算计算机的端口个数为2^16个
(2)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(3)确认号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。也就是告诉发送发:我希望你(指发送方)下次发送的数据的第一个字节数据的编号是这个确认号。
(4)数据偏移:表示TCP报文段的首部长度,共4位,它指出TCP报文段的数据起始处离距离TCP报文段的起始处有多远。
(5)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
A、URG:紧急指针(urgent pointer),只有当URG=1时才有效。
B、ACK:确认序号有效,只有当ACK=1时,前面的确认序号才有效。
C、PSH:接收方应该尽快将这个数据交给应用层,为接收后续数据腾出空间。如果结果为1,则表示对方应当立即把数据提交给应用层,而不是缓存,如果不收走数据,就会一直停留在TCP接收缓冲区。
D、RST:重新连接。如果收到RST=1的报文,说明与主机连接出现了严重错误,必须释放连接,然后重新建立连接。
E、SYN:发起一个新的连接。当SYN=1,ACK=0时,这是一个请求建立连接的报文段;当SYN=1,ACK=1时,表示对方同意建立连接。
F、FIN:释放一个连接。当FIN=1,即告诉对方:“我的数据已发送完毕,你可以释放连接了”。
(6)窗口大小:从本报文段的确认号开始允许对方发送的数据量。
(7)校验和:提供额外的可靠性。
(8)紧急指针:标记紧急数据在数据字段中的位置。
(9)选项部分:其最大长度可根据TCP首部长度进行推算。
需注意的是:
①、不要将确认序号Ack与标志位中的ACK搞混淆;
②、确认方Ack=发起方Req+1,两端配对。
结束了对TCP包的首部了解,现在我们开始进行TCP的连接和中止。简单来说TCP连接的建立可以称为三次握手,而连接的中止则可以叫做四次挥手。
三次握手
所谓三次握手即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
过程:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为y+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
状态切换:
客户端:
Closed--> SYN-SENT --> ESTAB-LISHED
关闭----> 同步已发送 --> 已建立连接
服务器端:
Closed--> LISTEN --> SYN-RCVD --> ESTAB-LISHED
关闭----> 收听 --> 同步收到 --> 已建立连接
简单来说就是:在建立连接的时候,客户端首先向服务器申请打开某一个端口(用SYN段等于1的TCP报文),然后服务器端发回一个ACK报文通知客户端请求报文收到,客户端收到确认报文以后再次发出确认报文确认刚才服务器端发出的确认报文(绕口么),至此,连接的建立完成。这就叫做三次握手。如果打算让双方都做好准备的话,一定要发送三次报文,而且只需要三次报文就可以了。
四次挥手
所谓四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,如下图:
过程:
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
状态切换:
客户端:
ESTAB-LISHED --> FIN-WAIT-1 -- > FIN-WAIT-2 --> TIME-WAIT(等待2MSL) --> CLOSED
建立连接状态 终止等待1 终止等待2 时间等待 关闭
服务器端:
ESTAB-LISHED --> CLOSE-WAIT --> LAST-ACK --> CLOSED
建立连接状态 关闭等待 最后确认 关闭
TCP的连接是全双工(可以同时发送和接收)连接,因此在关闭连接的时候,必须关闭传和送两个方向上的连接。客户机给服务器一个FIN为1的TCP报文,然后服务器返回给客户端一个确认ACK报文,并且发送一个FIN报文,当客户机回复ACK报文后(四次握手),连接就结束了。
上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:
流程和状态在上图中已经很明了了,在此不再描述,可以参考前面的四次挥手解析步骤。
OK~是不是很难懂的感觉?那我们来说的“人性化点的”吧
三次握手流程:
客户端发个请求“开门呐,我要进来”给服务器
服务器发个“进来吧,我去给你开门”给客户端
客户端有很客气的发个“谢谢,我要进来了”给服务器
四次挥手流程:
客户端发个“时间不早了,我要走了”给服务器,等服务器起身送他
服务器听到了,发个“我知道了,那我送你出门吧”给客户端,等客户端走
服务器把门关上后,发个“我关门了”给客户端,然后等客户端走(哎呦喂~矫情啊)
客户端发个“我知道了,我走了”,之后自己就走了
附加题:为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,乙方也未必全部数据都发送给对方了,所以乙方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,乙方ACK和FIN一般都会分开发送。