TCP(Transmission Control Protocol) 传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP是一种面向连接(连接导向)的、可靠的基于字节流的传输层通信协议。TCP将用户数据打包成报文段,它发送后启动一个定时器,另一端收到的数据进行确认、对失序的数据重新排序、丢弃重复数据。
TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种断点我们叫作套接字(socket),将端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.3.4.16 而端口号为80,那么得到的套接字为192.3.4.16:80。
TCP的特点有:
- TCP是面向连接的运输层协议;
- 每一条TCP连接只能有两个端点,每一条TCP连接只能是点对点的;
- TCP提供可靠交付的服务;
- TCP提供全双工通信。数据在两个方向上独立的进行传输。因此,连接的每一端必须保持每个方向上的传输数据序号;
- 面向字节流。【面向字节流的含义:虽然应用程序和TCP交互是一次一个数据块,但TCP把应用程序交下来的数据仅仅是一连串的无结构的字节流。】
TCP报文格式图:
TCP报文首部:
1、源端口和目的端口,各占2个字节,分别写入源端口和目的端口;
2、序号,seq序号,sequence number,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从401开始;
3、确认号,ack序号,acknowledge number,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。例如,B收到了A发送过来的报文,其序列号字段是501,而数据长度是200字节,这表明B正确的收到了A发送的到序号700为止的数据。因此,B期望收到A的下一个数据序号是701,于是B在发送给A的确认报文段中把确认号置为701;
4、数据偏移,占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;
5、保留,占6位,保留今后使用,但目前应都位0;
6、标志位:(共6个,即URG、ACK、PSH、RST、SYN、FIN等)
- 紧急URG,当URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;
- 确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;
- 推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1;
- 复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;
- 同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
- 终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
7、窗口,占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;
8、检验和,占2字节,校验首部和数据这两部分;
9、紧急指针,占2字节,指出本报文段中的紧急数据的字节数;
10、选项,长度可变,定义一些其他的可选的参数。
需要注意的是:
(a)不要将确认序号ack与标志位中的ACK搞混了。
(b)确认方的确认号码等于发起方的顺序号码加上数字1:ack=seq+1,两端配对。
TCP在传输之前会进行三次沟通,一般称为“三次握手”(Three-Way Handshake),传完数据断开的时候要进行四次沟通,一般称为“四次挥手”。
“三次握手”过程讲解:
首先,TCP服务器 B 进程先创建传输控制块TCB,时刻准备接受客户进程 A 的连接请求,此时服务器 B 就进入了LISTEN(监听)状态。
然后【第一次握手】:TCP客户进程 A 也是先创建传输控制块TCB,然后向服务器 B 发出连接请求报文,这时报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
接着【第二次握手】:TCP服务器 B 收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1(因为已经收到了序列号为x的数据包,准备接收序列号为x+1的包,所以ack=x+1),同时也要为自己初始化一个序列号 seq=y(B告诉A自己的初始序列号)。此时,TCP服务器 B 进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
再然后【第三次握手】:TCP客户进程 A 收到确认后,还要向服务器 B 给出确认。确认报文的ACK=1,ack=y+1(ack=y+1是表示A正准备接收B序列号为y+1的数据包),自己的序列号seq=x+1。此时,TCP连接建立,客户端 A 进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
最后,当服务器 B 收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
为什么A在第三次握手中还要发送一次确认呢?
这主要是为了防止已失效的连接请求报文段突然又传送到了B,B确认后一直长时间等待A发送请求而浪费B的资源。
所谓“已失效的连接请求报文段”是这样产生的:
正常情况是:A发出连接请求,但因连接请求报文丢失而未收到确认。于是A再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。A共发送了两个连接请求报文段,其中第一个丢失,第二个到达了B。没有“已失效的连接请求报文段”。
现假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达B。本来这是一个早已失效的报文段,但B收到此失效的连接请求报文段后,就误认为是A发出一次新的连接请求;于是B就向A发出确认报文段,同意建立连接。那么,由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据。这样的话,B的许多资源就这样白白浪费了。
采用三次握手的办法可以防止上述现象的发生。例如在刚才的情况下,A不向B的确认发出确认,B就由于收不到确认,就认为A并没有要求建立连接。
“四次挥手”过程讲解:
所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。
一、如果是一方主动关闭,另一方被动关闭,那么整个流程如下图所示:(下图以客户端主动发起关闭请求为例)
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
上图描述的即是如此。挥手过程如下:
(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状态,完成四次挥手。
二、实际中还会出现同时发起主动关闭的情况,具体流程如下图:
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为:在建立连接时,服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,己方ACK和FIN一般都是分开发送给对方:当收到对方的FIN报文时,仅仅表示对方不再发送数据了,但是对方还能接收己方发送过去的数据;己方如果尚未把全部数据都发送给对方,并且己方如果不立即close,还可以继续发送一些数据给对方,发送完毕后才发送FIN报文给对方来表示同意现在关闭连接。
为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。