TCP是一个传输层协议,提供可靠传输,支持全双工,是一个连接导向的协议。
双工/单工
在任何一个时刻,如果数据只能单向发送,就是单工。
如果在某个时刻数据可以向一个方向传输,也可以向另一个方向反向传输,而且交替进行,叫作半双工;半双工需要至少 1 条线路。
如果任何时刻数据都可以双向收发,这就是全双工,全双工需要大于 1 条线路。
TCP 是一个双工协议,数据任何时候都可以双向传输。
这就意味着客户端和服务端可以平等地发送、接收信息。
TCP协议的主要特点
-
TCP是面向连接的运输层协议;所谓面向连接就是双方传输数据之前,必须先建立一条通道,例如三次握手就是建议通道的一个过程,而四次挥手则是结束销毁通道的一个其中过程。
-
每一条TCP连接只能有两个端点(即两个套接字),只能是点对点的;
-
TCP提供可靠的传输服务。传送的数据无差错、不丢失、不重复、按序到达;
-
TCP提供全双工通信。允许通信双方的应用进程在任何时候都可以发送数据,因为两端都设有发送缓存和接受缓存;
-
面向字节流。虽然应用程序与TCP交互是一次一个大小不等的数据块,但TCP把这些数据看成一连串无结构的字节流,它不保证接收方收到的数据块和发送方发送的数据块具有对应大小关系,例如,发送方应用程序交给发送方的TCP10个数据块,接收方的TCP可能只用收到的4个数据块字节流交付给上层的应用程序
TCP的可靠性原理
可靠传输有如下两个特点:
- 传输信道无差错,保证传输数据正确;
- 不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据;
首先,采用三次握手来建立TCP连接,四次握手来释放TCP连接,从而保证建立的传输信道是可靠的。
其次,TCP采用了连续ARQ协议(回退N(Go-back-N);超时自动重传)来保证数据传输的正确性,使用滑动窗口协议来保证接方能够及时处理所接收到的数据,进行流量控制。
最后,TCP使用慢开始、拥塞避免、快重传和快恢复来进行拥塞控制,避免网络拥塞。
报文段
TCP虽面向字节流,但传送的数据单元为报文段
报文段 = 首部 + 数据2部分
TCP的全部功能体现在它首部中各字段的作用
端口:
源端口号和目地端口各占16位两个字节,也就是端口的范围是2^16=65535
另外1024以下是系统保留的,从1024-65535是用户使用的端口范围
seq序号:占4字节,TCP连接中传送的字节流中的每个字节都按顺序编号
例如:一段报文的序号字段值是107,携带的数据是100个字段,下一个报文段序号从107+100=207开始。
ack确认号:4个字节,是期望收到对方下一个报文段的第一个数据字节的序号。
例如:B收到A发送的报文,其序号字段是301,数据长度是200字节,表明B正确收到A发送的到序号500为止的数据(301+200-1=500),B期望收到A下一个数据序号是501,B发送给A的确认报文段中把ack确认号置为501。
数据偏移:头部有可选字段,长度不固定,指出TCP报文段的数据起始处距离报文段的起始处有多远。
保留:保留今后使用的,被标为1。
控制位:由8个标志位组成。每个标志位表示一个控制功能。
其中主要的6个:
-
URG紧急指针标志,为1表示紧急指针有效,为0忽略紧急指针。
-
ACK确认序号标志,为1表示确认号有效,为0表示报文不含确认信息,忽略确认号字段,上面的确认号是否有效就是通过该标识控制的。
-
PSH标志,为1表示带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将该报文段交给应用程序,而不是在缓冲区排队。
-
RST重置连接标志,重置因为主机崩溃或其他原因而出现错误的连接,或用于拒绝非法的报文段或非法的连接
-
SYN同步序号,同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。
-
FIN终止标志,用于释放连接,为1时表示发送方没有发送了。
窗口:滑动窗口大小,用来告知发送端接收端缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。
校验和:奇偶校验,此校验和是对整个的TCP报文段(包括TCP头部和TCP数据),以16位进行计算所得,由发送端计算和存储,接收端进行验证。
校验和:奇偶校验,此校验和是对整个的TCP报文段(包括TCP头部和TCP数据),以16位进行计算所得,由发送端计算和存储,接收端进行验证。
选项:其长度可变,定义其他的可选参数。
粘包与拆包
TCP是面向字节流的协议,把上层应用层的数据看成字节流,所以它发送的不是固定大小的数据包,TCP协议也没有字段说明发送数据包的大小。而且TCP不保证接受方应用程序收到的数据块和发送应用程序发送的数据块具有对应的大小关系
TCP粘包/拆包解决策略
由于TCP无法理解上一层的业务数据特点,所以TCP是无法保证发送的数据包不发生粘包和拆包,这个问题只能通过上层的协议栈设计来解决,解决思路有一下几种:
-
消息定长:每个发送的数据包大小固定,比如100字节,不足100字节的用空格补充,接受方取数据的时候根据这个长度来读取数据
-
消息末尾增加换行符来表示一条完整的消息:接收方读取的时候根据换行符来判断是否是一条完整的消息,如果消息的内容也包含换行符,那么这种方式就不合适了。
-
将消息分为消息头和消息尾两部分,消息头指定数据长度,根据消息长度来读取完整的消息,例如UDP协议是这么设计的,用两个字节来表示消息长度,所以UDP不存在粘包和拆包问题。
三次握手
第一次握手:
客户端将TCP报文标志位SYN
置为1,随机产生一个序号值seq=J
,保存在TCP首部的序列号字段里,指明客户端打算连接的服务器的端口,并将该数据包发送给服务器端,发送完毕后,客户端进入SYN_SENT
状态,等待服务器端确认。
第二次握手:
服务器端收到数据包后由标志位SYN=1
知道客户端请求建立连接,服务器端将TCP报文标志位SYN和ACK都置为1,ack=J+1
,随机产生一个序号值seq=K
,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD
状态。
第三次握手:
客户端收到确认后,检查ack是否为J+1
,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1
,并将该数据包发送给服务器端,服务器端检查ack是否为K+1
,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED
状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
-
小写的ack代表的是头部的确认号Acknowledge number, 缩写ack,是对上一个包的序号进行确认的号,
ack=seq+1
。 -
大写的ACK,则是我们上面说的TCP首部的标志位,用于标志的TCP包是否对上一个包进行了确认操作,如果确认了,则把ACK标志位设置成1
四次挥手
挥手请求可以是Client端,也可以是Server端发起的,我们假设是Client端发起:
-
第一次挥手: Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入
FIN_WAIT_1
状态,这表示Client端没有数据要发送给Server端了。 -
第二次挥手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设为seq加1,Client端进入
FIN_WAIT_2
状态,Server端告诉Client端,我确认并同意你的关闭请求。 -
第三次挥手: Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入
LAST_ACK
状态。 -
第四次挥手 : Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入
TIME_WAIT
状态,Server端收到Client端的ACK报文段以后,就关闭连接,此时,Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,那好,Client端也可以关闭连接了
流量控制
RTT和RTO
RTT:发送一个数据包到收到对应的ACK,所花费的时间
RTO:重传时间间隔(TCP在发送一个数据包后会启动一个重传定时器,RTO即定时器的重传时间)
开始预先算一个定时器时间,如果回复ACK,重传定时器就自动失效,即不需要重传;如果没有回复ACK,RTO定时器时间就到了,重传。
RTO是本次发送当前数据包所预估的超时时间,RTO不是固定写死的配置,是经过RTT计算出来的。
滑动窗口
TCP的滑动窗口主要有两个作用:
-
保证TCP的可靠性
-
保证TCP的流控特性
TCP报文头有个字段叫Window,用于接收方通知发送方自己还有多少缓存区可以接收数据,发送方根据接收方的处理能力来发送数据,不会导致接收方处理不过来,这便是流量控制。
发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。
发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。
不同的滑动窗口协议窗口大小一般不同。
发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧
绿色
:发送成功并已经ACK确认的数据黄色
:发送成功等待ACK确认的数据(占用滑动窗口大小)紫色
:滑动窗口剩余大小可以发送的字节数量(滑动窗口可用大小)灰色
:后续数据编码
接收窗口的大小就是滑动窗口的最大值,数据传输过程中滑动窗口的可用大小是动态变化的。
但是还有这么一点,滑动窗口的设计仅仅是考虑到了处理方的处理能力,但是没有考虑到道路的通畅问题
就好像服务端可以处理100M数据,但是传输的数据99M都堵在路上了,这不就是导致道路阻塞了么?这就需要另外一个设计拥塞避免
流量控制的目的
- 如果发送者发送数据过快,接收者来不及接收,那么就会有分组丢失。
- 为了避免分组丢失,控制发送者的发送速度,使得接收者来得及接收,这就是流量控制。
- 流量控制根本目的是防止分组丢失,它是构成TCP可靠性的一方面。
如何实现流量控制
由滑动窗口协议(连续ARQ协议)实现。滑动窗口协议既保证了分组无差错、有序接收,也实现了流量控制。
主要的方式就是接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。
流量控制引发的死锁
当发送者收到了一个窗口为0的应答,发送者便停止发送,等待接收者的下一个应答。
但是如果这个窗口不为0的应答在传输过程丢失,发送者一直等待下去,而接收者以为发送者已经收到该应答,等待接收新数据,这样双方就相互等待,从而产生死锁。
为了避免流量控制引发的死锁,TCP使用了持续计时器。每当发送者收到一个零窗口的应答后就启动该计时器。时间一到便主动发送报文询问接收者的窗口大小。若接收者仍然返回零窗口,则重置该计时器继续等待;若窗口不为0,则表示应答报文丢失了,此时重置发送窗口后开始发送,这样就避免了死锁的产生。
拥塞控制
为什么要进行拥塞控制
假设网络已经出现拥塞,如果不处理拥塞,那么延时增加,出现更多丢包,触发发送方重传数据,加剧拥塞情况,继续恶性循环直至网络瘫痪。
拥塞发生前,可避免流量过快增长拖垮网络;拥塞发生时,唯一的选择就是降低流量。
主要使用4种算法完成拥塞控制:
- 慢启动
- 拥塞避免
- 快重传算法
- 快速恢复算法
算法1、2适用于拥塞发生前,算法3适用于拥塞发生时,算法4适用于拥塞解决后(相当于拥塞发生前)
rwnd与cwnd
rwnd
(Receiver Window,接收者窗口)与cwnd
(Congestion Window,拥塞窗口):
-
rwnd是用于流量控制的窗口大小,主要取决于接收方的处理速度,由接收方通知发送方被动调整。
-
cwnd是用于拥塞处理的窗口大小,取决于网络状况,由发送方探查网络主动调整。
同时考虑流量控制与拥塞处理,则发送方窗口的大小不超过min{rwnd, cwnd}
。
慢启动算法
慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。
一个传输轮次所经历的时间其实就是往返时间RTT,而且每经过一个传输轮次,拥塞窗口cwnd就加倍。
为了防止cwnd增长过大引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。
-
cwnd<ssthresh时,使用慢开始算法。
-
当cwnd>ssthresh时,改用拥塞避免算法。
-
当cwnd=ssthresh时,慢开始与拥塞避免算法任意
注意:这里的慢并不是指cwnd的增长速率慢,而是指在TCP开始发送报文段时先设置cwnd=1,然后逐渐增大,这当然比按照大的cwnd一下子把许多报文段突然注入到网络中要慢得多。
拥塞避免算法
让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍。
这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多
无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有按时收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理),就把慢开始门限ssthresh设置为出现拥塞时的发送窗口大小的一半(但不能小于2)。
然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。
这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够时间把队列中积压的分组处理完毕。
快重传算法
快重传要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方,可提高网络吞吐量约20%)而不要等到自己发送数据时捎带确认。
快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器时间到期
快恢复算法
快重传配合使用的还有快恢复算法,有以下两个要点:
-
当发送方连续收到三个重复确认时,就把ssthresh门限减半(为了预防网络发生拥塞)。
-
但是接下去并不执行慢开始算法
Socket
即套接字,是应用层 与 TCP/IP
协议族通信的中间软件抽象层,表现为一个封装了 TCP / IP协议族 的编程接口(API)
Socket
不是一种协议,而是一个编程调用接口(API
),属于传输层(主要解决数据如何在网络中传输)
对用户来说,只需调用Socket去组织数据,以符合指定的协议,即可通信。