TCP/IP笔记——TCP特点、首部格式、滑动窗口
这次总结一下TCP相关的知识。
TCP主要特点
- 面向连接:在通信前必须建立连接(只是逻辑上存在,而不是物理连接)
- 只能有两个端点:即只能一对一通信(所以通常p2p是用UDP实现的)
- 提供可靠交付服务:保证发送的数据无差错、不丢失、不重复、顺序对(可靠,但是代价较UDP大)
- 全双工通信:允许双方在任何时候发送数据。TCP连接两端都有发送和接收缓存,在发送前先将数据写入缓存,TCP会自己选择合适的时候发送(接受同理,先写入缓存然后再读取)
- 面向字节流:流指的是流入到进程或者从进程流出的字节序列,TCP类似搬运工,只管发送接收,不理解含义
实际传输中TCP报文段要先传送到IP层,加上IP首部后再传到数字链路层(TCP/IP位于运输层),加上数据链路层首部和尾部后才会被发送到物理链路。
TCP发送的时候不关心应用一次发多长的报文到TCP的缓存中(UDP的话是上面应用层给多少他就直接包装下发多少),会自动根据对方给出窗口值和当前网络拥塞的程度来决定一个报文段包含多少字节。太长TCP会自己进行划分,太短,TCP会存下来等到一定程度后再发送。
TCP连接
TCP连接的端点叫做socket、套接字或插口。每个TCP连接有两个端点,具体定义如下:
P.S. 同一个IP可以有多个不同的TCP连接,同一个端口号可以出现在多个不同TCP连接中。
TCP报文段首部格式
各字段意义:
- 源端口和目的端口:各占2个字节,建立socket用
- 序号:4个字节,范围是\([0, 2^32 - 1]\),用来给TCP传输中的字节流编号,传输的每个字节都要按顺序编号,循环使用(用到\(2^32 - 1\)后,循环回到0)。报文段的序号由要传送的字节路中的第一个字节的序号决定,例如序号字段值为301,携带100字节,那么下一个报文段数据序号从401开始,序号字段值为401
- 确认号:4字节长度,期望收到对方下一个报文段的第一个数据字节的序号;例如确认号是701,那么说明已经接收到了序号到700为止的数据
确认号有32位,可以编号4GB数据(没必要记,可以这样想,32位的xp系统最高只能上4G内存,所以32位最多之恶能编号4GB数据) - 数据偏移:TCP报文段中有一部分是长度不确定的选项字段,所以需要数据偏移来指出TCP报文段首部的长度,不然没办法知道数据段从哪里开始(这也是为什么这一段叫做“数据偏移”,虽然其实他就是记录报文段首部的长度)
- 保留:6位,未来可能会用,目前置为0
- 紧急URG(URGent):URG=1时TCP会优先传送这个报文段。例如Control + C的中断指令,如果已经缓存了一堆要发送的数据,突然用户反悔了(相信我,用户就是渣男/女,天天反悔,嗯),不想传了,就会按下中断指令Control + C。如果不立个URG的FLAG,那么只能等前面的所有缓存好了的数据发送完成之后才能够发送中断指令,就会给服务端一种“我裤子都脱了你给我看这个?”的白费功夫的感觉。所以需要优先发送中断指令,这样就不会浪费双方的资源和时间。URG置为1的时候TCP会把紧急数据插入到要发送的数据的最前面,普通数据会跟在紧急数据后面(配合首部中紧急指针字段实现)
- 确认ACK(ACKnowledge):ACK=1的时候确认号字段有效,否则无效,连接建立后所有传送的报文段必须把ACK置1
- 推送PSH(PuSH):接收方接收到PSH置1的报文的时候,会尽快交付接收应用进程,而不是放在缓存中。通常用在交互式的通信,这样键入一个命令后才可以立即收到对方的响应
- 复位RST:此位置1表示连接出现严重错误,必须释放连接然后重新建立(reset)。此外还用来拒绝非法报文段或者打开一个连接。
这个就比较常见了 - 同步SYN(SYNchronization):在连接建立时用来同步序号,当SYN=1,ACK=0,表示这是一个连接请求报文段。如果对方同意建立连接,那么响应中应该设置SYN=1,ACK=1。总之SYN置1表示这是一个连接请求或者连接接受报文
- 终止FIN(FINish):置1用来释放连接
- 窗口:占2字节,值是\([0, 2=^{16}-1]\)之间的整数。这是接收方让发送方设置其发送窗口的依据,可以理解为缓存大小,即一次性能接收多少数据量
- 检验和:占2字节,检验范围包括首部和数据2部分。计算式要在报文段前加上12字节的伪首部,格式与UDP的伪首部一样,除了第四个字段中的17要改为6(TCP协议号为6),第5字段中UDP长度改为TCP长度。接收方接收到报文要检验的时候也要加上伪首部。注意IPv6和IPv4的伪首部不同
- 紧急指针:占2字节,URG=1时才有用。用来指出本报文段中的紧急数据的字节数(因为紧急数据后面紧跟着的就是普通数据),相当于给出了紧急数据末尾的位置。另外,即使窗口值为0也可以发送紧急数据
- 选项:长度可变,最长40字节。没有这个的时候TCP首部长度为20字节
滑动窗口
例子:
这只是其中一方的发送窗口,其实因为TCP是双工的,接受和发送都需要窗口,所以总共四个窗口。注意窗口的单位为字节。右边是前,左边是后,发送过程中窗口整体不断向右移动。
如图所示,每次发送方都会不断地发送窗口内的数据,然后发送过的数据会被暂时保留,超时重传的时候再发一遍。发送并且受到确认信号之后,窗口后沿(左边)会前移(往右走),如果窗口大小没变,那么前沿(右边)也会跟着右走。有时候窗口前沿会往后收缩,这时候通常是对方通知窗口大小变小了,不过TCP强烈不赞同这么做(因为发送方已经发送了窗口中的大部分数据,突然收缩窗口可能会让一些已经发送过了的数据处在“不允许发送”的位置,如果确认信号突然过来了恰好要求的就是这些不允许发送的数据,那么就会出错)。
通常需要3个指针来描述发送窗口的状态(P1、P2、P3),对应的意义:
注意B支队按序收到的数据中的最高序号给出确认,所以这里B的确认报还是31。没有按序收到的报文会暂存在接收窗口,等缺失部分到了之后再按序交付上层。
A收到确认号之后发送窗口会向前移动。
如果A发送完了窗口内的数据,但是还没有收到确认,那么窗口会一直停在这里。超时后会重新发送数据。
总结下:
此外:
- 缓存空间和序号空间有限,并且是循环使用的;
- 实际上缓存和窗口中的字节数非常大,这里只是例子所以取了比较小的数
- 发送方和接收方的窗口大小并不总是一致,因为网络具有滞后性
发送方缓存存放的内容:
- 准备发送的数据
- 发了但是还没得到确认的数据
接收方:
- 按序到达但是还没有被读取的数据
- 未按序列到达的数据
关于超时重传时间
这里基本上就是计算公式,所以:
RTT就是报文段的往返时间,即发送时间减去收到确认的时间。\(0 \leq \alpha < 1\),建议值为\(\frac{1}{8}\)。\(\mathrm{RTT}_{\mathrm{s}}\)为平滑的往返时间。其实这个公式做的就是加权平均。
RTO就是超时重传时间。
\(\mathrm{RTT}_{\mathrm{D}}\)是RTT的偏差的加权平均值,\(\beta < 1\),通常选0.25,\(\frac{1}{4}\)。
其实最关键的其实是中间出现的RTO,用来确定超时重传的时间的,这样可以保证不会一直等确认信号而不发送信息太久。
此外:
但是如果报文段实验突然增大,导致在原来的重传时间内不会收到确认报文段,所以发出的报文段一定会超时,所以就会发出新的报文段。但是根据上面的算法,超时之后的确认报文段虽然到了,但是因为不采用其往返时间的样本,就会导致RTTS不会被更新(因为确认报文段肯定无法在超时前到大,每次发送都会忽略掉上次发送的报文对应的确认报文)。