# 一、传输控制协议TCP
## 1、传输控制协议TCP概述
### 1.1 TCP的主要特点
1. TCP是**面向连接的**运输层协议。应用程序在使用TCP协议传输数据之前,必须先建立连接;数据传输完成后,必须释放已经建立的连接。
2. TCP只能提供**一对一**服务。每一条TCP的连接只能是2个端点,一对一的的建立。
3. TCP提供**可靠的交付**。通过TCP连接传送的数据,无差错、不丢失、不重复,并且按序到达。
4. TCP提供**全双工通信**。TCP连接允许通信双方的应用进程在任何时候都能互相发送数据,TCP连接的两端都设有`发送缓存`和`接收缓存`,用来临时存放双方通信的数据。
5. TCP是**面向字节流**的。
> 面向字节流:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但是TCP把应用程序交下来的数据仅仅看做成`无结构的字节流`。TCP有一个缓冲,并且TCP不关心应用程序一次会发送多长的数据到TCP缓冲中,TCP会根据对方给出的窗口值和网络拥塞的程度来决定一个报文段应包含多少个字节。如果应用进程一次发送到TCP缓冲中的数据块太长,TCP就会把数据块划分短一点再传送;同样,如果应用进程一次发送到TCP缓冲的数据块很短,TCP则会等待足够多的字节后再构成报文段发送出去。
> UDP面向报文,是应用层交给UDP一个报文,UDP既不拆分也不合并,就会在报文上加上UDP首部后发送整个报文出去。
:-: ![](https://img.kancloud.cn/55/53/55539add0f139e25c76357fe5df72189_860x401.png)
### 1.2 TCP的连接
TCP把`连接`作为基本的抽象。其大部分特性都与TCP是面向连接的这个基本特性有关。
TCP连接的端点叫做socket;IP地址拼接上端口号就构成了一个socket。`socket = IP地址:端口号`
每一条TCP连接唯一的被通信两端的两个端点(即两个socket)所确定。`TCP连接 = {socket1, socket2} = {(IP1:Port1), (IP2:Port2)}`
## 2、可靠传输的工作原理
### 2.1 理想的传输条件
理想的传输条件一般需具备以下两个条件:
* 传输信道不产生差错
* 不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据
但是,实际的网络不能具备上述两个理想的条件。需要使用一些可靠的传输协议来实现。当出现差错时让发送方重传出现差错的数据,同时在接收方来不及处理收到的数据时,及时告诉发送方适当减缓发送数据的速度。这样,不可靠的传输信道就能够实现可靠传输了。
### 2.2 停止等待协议
全双工通信的双方既是发送方又是接收方。我们把传送的数据称之为分组。`停止等待`就是每发完一个分组就停止发送,等待对方的确认。在收到确认后再发送下一个分组。
#### 2.2.1 无差错传输情况
:-: ![](https://img.kancloud.cn/70/b6/70b636485728a4c3ec7aa0b4d6ae84e0_423x460.png)
#### 2.2.2 有差错传输情况
* 超时重传:只要超过一定的时间没有收到确认,就认为刚才发送的分组丢失了,因而就会重传前面发送过的分组。要实现超时重传,就要在每发完一个分组时,设置一个`超时计时器`。
1. 发送完一个分组后,必须暂时保留已发送的分组(在发生超时重传时使用);只有在收到相应的确认后才能清除暂时保留的分组副本
2. 分组和确认分组都必须进行编号。这样才能明确是哪一个发送出去的分组收到了确认,而哪一个分组还没有收到确认
3. 超时计时器的重传时间,应当比数据在分组传输的平均往返时间更长一些
:-: ![](https://img.kancloud.cn/a7/63/a763cb0dc737173c4a85beb9fc97cc05_610x460.png)
* 确认丢失:当一定时间没有收到确认后,就会重传分组;接收方收到后,就会丢弃重复的分组,然后重传相应的确认分组
:-: ![](https://img.kancloud.cn/b1/08/b1080e0e54036687f093433fccc381b4_617x460.png)
* 确认迟到:当一定时间没有收到确认,重传后收到重传分组确认,然后又收到了开始发送分组的确认,这种情况下就直接丢弃此时收到的确认分组
:-: ![](https://img.kancloud.cn/d4/b8/d4b8d0b87c5f7de09b66780de60eaaa2_642x460.png)
通过上述的确认和重传机制,我们就可以在不可靠的传输网络上实现可靠的通信。
#### 2.2.3 信道利用率
停止等待协议的优点是简单易实现,但是缺点也很明显,就是信道利用率太低。
为了提高传输效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输。流水线传输就是发送方可连续发送多个分组,不必每发完一个分组就停顿下来等待对方的确认。这样可以使信道上一直有数据不间断的在传送。这样就可以获得更高的信道利用率。
### 连续ARQ协议
连续ARQ协议是指发送方维持着一个一定大小的发送窗口,位于发送窗口内的分组都可以连续发送出去,而不需要中途等待对方的确认。而当发送方每收到一个确认后,就会把发送窗口向前滑动一个分组的位置。
接收方一般都是采用`积累确认`的方式。这样接收方就不需要对收到的分组逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认,这就表示到这个分组为止的所有分组都收到了。
* 优点:简单,容易实现,即使确认丢失也不必重传
* 缺点:不能想发送方正确的发映出接收方已经正确的收到所有分组的信息
> 如果发送方发送了前5个分组,而中间的第3个分组丢失了。这时接收方只是对前两个分组发出确认。发送方无法知道后面三个分组的下落,而只好把后面的三个分组都再重传一次。
:-: ![](https://img.kancloud.cn/28/57/285700c38baa50a86a7cd6b98e50b555_586x319.png)
## 2、TCP报文段的首部格式
TCP虽然是面向字节流的,但是TCP传送的数据单元是报文段。一个TCP报文段分`首部`和`数据`两部分。TCP报文段的前20个字节是固定的,后面有4n个字节是根据需要而增加的选项(n是正整数)。因此TCP报文段的首部最小长度是20字节。
:-: ![](https://img.kancloud.cn/d9/c9/d9c9a6fac89034490134b90808b55712_741x488.png)
### 2.1 首部字段含义解释
* `源端口`和`目的端口`:各占2各字节,分别写入端口号和目标端口号
* `序号`:占4个字节。序号的范围是[0, 2<sup>32</sup>-1] 共2<sup>32</sup>(即4294967296)个序号。序号增加到2<sup>32</sup>-1后,下一个序号就又回到0。在TCP连接的传输的字节流中,每一个字节都是按顺序编号。
* `确认号`:占4个字节,是期望收到发送方下一个报文段的第一个数据字节的序号。
> 例如,接收方收到了发送方的一个报文段,其序号字段值为**201**,且数据长度为**200**;那么接收方期望收到的下一个数据序号就是**401**。因此接收方发送给发送方的确认报文段中就会把确认号设置为**401**。`也就说明了:若确认号为N,那么表明到序号N-1为止的所有数据都已正确收到`
* `数据偏移`:占4位,指出TCP报文段的数据部分起始处距离TCP报文段的起始处有多远。`实际上指出了TCP报文段的首部长度`。
* `保留`:占6位,保留位今后使用。
* 后面有一个控制位,用来说明报文段的性质
1. `紧急URG`:当URG=1时,说明紧急指针字段有效。告诉系统此报文段中有紧急数据,应尽快传送,而不是按原先的排队顺序传送
2. `确认ACK`:仅当ACK=1时确认号字段才有效;当ACK=0时,确认号字段无效。`TCP 连接规定,在连接建立后所有传送的报文段都必须把ACK置为1`
3. `推送PSH`:当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置为1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地(即“推送”向前)交付接收应用进程。而不用再等到整个缓存都填满了后再向上交付。
4. `复位RST`:当RST=1时,表名TCP连接中出现了严重错误(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立传输连接。RST置为1还用来拒绝一个非法的报文段或拒绝打开一个连接。
5. `同步SYN`:在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使SYN=1和ACK=1,因此SYN置为1就表示这是一个连接请求或连接接受报文。
6. `终止FIN`:用来释放一个连接。当FIN=1时,表明此报文段的发送发的数据已发送完毕,并要求释放运输连接。
* `窗口`:占2字节。窗口值是[0,2<sup>16</sup>-1]之间的整数。窗口指的是发送本报文段的一方的接受窗口(而不是自己的发送窗口)。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。总之,窗口值作为接收方让发送方设置其发送窗口的依据。
* `校验和`:占2字节。检验和字段检验的范围包括首部和数据这两部分。和UDP用户数据报一样,在计算检验和时,要在TCP报文段的前面加上12字节的伪首部。伪首部的格式和UDP用户数据报的伪首部一样。但应把伪首部第4个字段中的17改为6(TCP的协议号是6); 把第5字段中的UDP中的长度改为TCP长度。接收方收到此报文段后,仍要加上这个伪首部来计算检验和。
* `紧急指针`:占2字节。紧急指针仅在URG=1时才有意义,他指出报文段找那个紧急数据的字节数。
## 2、TCP可靠传输的实现
### 2.1 以字节为单位的滑动窗口
* 发送窗口构造
TCP的发送窗口是以字节为单位的。假设A收到了B发来的确认报文段,其中窗口是**10**,确认号是**31**(这表明B期望收到的下一个序号是31,且序号到30为止的数据都已经收到了)。根据这两个值(窗口和确认号),A就可以构造出自己的发送窗口。在没有收到B的确认的情况下,A可以连续把窗口内的数据都发送出去。`凡是已经发送出去的数据,在未收到确认之前都必须暂时保留,以便超时重传时使用`。
:-: ![](https://img.kancloud.cn/07/be/07be3f34c52c0fcdfaebb897d73b0055_675x315.png)
* 发送窗口变化
发送窗口的位置由窗口前沿和后沿的位置共同确定。
a. 发送窗口后沿的位置变化有2种,`即不动`(没有收到新的确认)`和前移`(收到了新的确认)。发送窗口后沿不可能向后移动,因为不能撤销已收到的确认。
b. 发送窗口前沿的位置通常情况下都是`向前移动`;但是在以下2种特殊情况下也有可能`保持不动`:
> 1. 没有收到新的确认,对应通知的窗口大小也不变
> 2. 收到了新的确认但是对方通知的窗口缩小了,使得发送窗口的前沿正好不动
c. 发送窗口前沿也有可能`向后收缩`。这种情况发生在对方通知的窗口缩小了。但是TCP的标准`强烈不赞成这样做`。因为很可能发送方在收到这个通知以前已经发送了窗口中的许多数据,现在又要收缩窗口,不让发送这些数据,这样就会产生一些错误。
* 缓存和窗口
发送缓存用来暂时存放:
> 1. 发送方准备发送给对方TCP的数据
> 2. TCP已发送出但尚未收到确认的数据
已收到确认的数据应当从缓存中删除,因此发送缓存和发送窗口的后沿是重合的。发送应用程序必须控制写入缓存的速率,不能太快,不然发送缓存很快就会没有存放数据的空间。
接收缓存用来暂时存放:
> 1. 按序到达的,但尚未被应用程序读取的数据
> 2. 未按序到达的数据
当接收应用程序来不及读取收到的数据,接收缓存最终就会被填满,使接收窗口减小到零。当接收应用程序能够及时的从接收缓存中读取收到的数据时,接收窗口就可以增大,最大为接收缓存的大小。
要点小结:
> 1. 虽然A的发送窗口是根据B的接收窗口设置的,但在同一时刻,A的发送窗口并不总是和B的接收窗口一样大。通过网络传送的窗口值需要经历一定的时间滞后,改时间并不确定。
> 2. 对于不按序到达的数据,TCP通常是先临时存放在接收窗口,等字节流中所缺少的字节收到后,再按序上交给应用程序。
> 3. TCP 要求接收方必须要有累计确认的功能,这样可以减少传输开销
## 3、 TCP流量控制
### 3.1 利用滑动窗口实现流量控制
流量控制:让发送方的发送速率不要太快,要让接收方来得及接收。利用滑动窗口机制可以很方便的在TCP连接上实现对发送方的流量控制。
TCP流量控制举例:假设A主机和B主机,每次发送100字节,B通知A窗口大小为300
:-: ![](https://img.kancloud.cn/d7/2a/d72aecd8e8b64b0ed13dccf2031b9696_742x578.png)
避免死锁:TCP为每一个连接设有一个持续计数器。只要TCP连接的一方收到对方零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(只携带1字节的数据),而对方就在确认这个探测报文段时给出了当前的窗口值。若果窗口扔为零,那么收到这个报文段的一方就重新设置持续计数器。如果新的窗口不是0,那么就打破了死锁的僵局。
## 4 TCP的拥塞控制
### 4.1 拥塞控制的一般原理
在计算机网络中的链路容量(宽带)、交换节点中的缓存和处理机等,都是网络资源。在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就会变坏。这种情况就叫做拥塞。
拥塞控制就是防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不至于过载。
:-: ![](https://img.kancloud.cn/dd/ab/ddab61dcad6f8fe31e0c7d8f683d40cc_611x421.png)
## 4.2 TCP的拥塞控制方法
TCP进行拥塞控制的方法有四种,分别是:`慢开始`,`拥塞避免`,`快重传`,`快恢复`。
* 慢开始
当主机开始发送数据时,由于并不清楚网络的负荷情况,如果立即把大量数据字节注入到网络,就有可能引起网络发生拥塞。经验证明,比较好的方法是先探测一下,即`由小到大逐渐增大发送窗口`,也就是说,`由小到大逐渐增大到拥塞窗口值`。 cwnd: 发送方的拥塞窗口,开始发送时设置为:cwnd=1
> **慢开始算法:**
在刚刚开始发送报文段时,先把拥塞窗口 cwnd 设置为1个最大报文段MSS的数值,而后每收到一个对新的报文段的确认,就把拥塞窗口增加1个MSS的数值,这样拥塞窗口cwnd的值就随着传输轮次(一个轮次即发送完一个cwnd的MSS)呈指数级增长,事实上,慢启动的速度一点也不慢,只是它的起点比较低一点而已。用这样的方法逐步增大发送方的拥塞窗口 cwnd ,可以使分组注入到网络的速率更加合理。
> **慢开始门限ssthresh:**
为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量(如何设置ssthresh)。慢开始门限ssthresh的用法如下:
当 cwnd < ssthresh 时,使用上述的慢开始算法。
当 cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当 cwnd = ssthresh 时,既可使用慢开始算法,也可使用拥塞控制避免算法。
* 拥塞避免
让拥塞窗口cwnd缓慢的增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是像慢开始那样加倍增加。因此在拥塞避免阶段就产生了`加法增大`的特点。这表明在拥塞避免阶段,拥塞窗口cwnd`按线性规律增长缓慢`,比慢开始算法的拥塞窗口增城速率缓慢的多。拥塞避免并非完全能够避免拥塞,而是把拥塞窗口控制位按线性规律增长,使网络比较不容易出现拥塞。
> **拥塞避免算法:**
当cwnd >= ssthresh时,就会进入“拥塞避免算法”,让拥塞窗口cwnd缓慢地增大,每收到1个ACK拥塞窗口cwnd = cwnd + 1/cwnd,即每经过一个传输轮次就把发送方的拥塞窗口cwnd加1。这样拥塞窗口cwnd按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。
* 快重传
采用快重传算法可以让发送方`尽早知道发生了个别报文段的丢失。`快重传算法首先要求接收方不要等待自己发送数据时才进行捎带确认,而是要立即发送确认,即使收到了失序的报文段,也要立即发出对方已收到报文段的重复确认。
* 快恢复
发送方知道当前只是丢失了个别的报文段。于是不启动慢开始,而是执行快恢复算法。
## 5、TCP的运输连接管理
TCP 是面向连接的协议。运输连接是用来传送TCP报文的。TCP运输连接的建立和释放是每一次面向连接的通信中必不可少的过程。运输连接有三个阶段:`连接建立`、`数据传送`和`连接释放`。运输连接管理就是使运输连接的建立和释放都能够正常的进行。
在TCP连接建立过程中要解决以下三个问题:
1. 要使每一方能够确知对方的存在
2. 要允许双方协商一些参数,`如:最大窗口值,是否使用窗口扩大选项和时间戳选项等`
3. 能够对运输实体资源(缓存大小、连接表中的项目等)进行分配
### 5.1、TCP的连接建立
TCP建立连接的过程叫握手,握手需要在客户和服务器之间交换三个TCP报文段
:-: ![](https://img.kancloud.cn/ba/f0/baf00e706eb51b9efdce8108f97ffb45_646x445.png)
* 1)第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN\_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
* 2)第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN\_RECV状态。
* 3)第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
> 四报文握手:B 发送给 A 的报文段,可拆成两个报文段。先发送一个确认报文段(ACK = 1,ack = x + 1),然后再发送一个同步报文段(SYN = 1,seq = y)。这样的过程就变成了`四报文握手`,与三报文握手效果一致
### 5.2、 TCP连接释放
TCP建立连接的过程叫挥手,握手需要在客户和服务器之间交换四个TCP报文段
:-: ![](https://img.kancloud.cn/eb/4e/eb4e002450a0d02650098ad657744a77_1350x790.png)
* 1)A的应用进程先向其TCP发出连接释放报文段(**FIN=1,序号seq=u**),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认。
* 2)B收到连接释放报文段后即发出确认报文段,(**ACK=1,确认号ack=u+1,序号seq=v**),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放。
* 3)A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段。
* 4)B没有要向A发出的数据,B发出连接释放报文段(**FIN=1,ACK=1,序号seq=w,确认号ack=u+1),**B进入LAST-ACK**(最后确认)状态,等待A的确认。
* 5)A收到B的连接释放报文段后,对此发出确认报文段(**ACK=1,seq=u+1,ack=w+1**),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态。