目录
TCP:概述 RFCs: 793, 1122, 1323, 2018, 2581
- 点对点
- 一个发送方,一个接收方【提供一个应用进程到另一个应用进程的服务,一个端节点上的应用进程向另外一个端节点上的应用进程提供一个点到点的、双向的数据通信,所以TCP不提供一点到多点、更不提供多点到多点的应用进程到应用进程的通信】
- 可靠的、按顺序的字节流
- 没有报文边界【不出错、不重复、不丢失,但他不提供报文的界限,应用进程和应用进程之间报文的界限是要靠应用进程自己去维护的】
- 管道化(流水线):
- TCP拥塞控制和流量控制设置窗口大小【发送方在未经确认的情况下可以向对方发送很多的连续的TCP的段】
【应用进程往下交下来的报文到了TCP之后,TCP要按照MSS(最大报文段)的大小,把它分成一个个的报文段,每个段之前要加上TCP的头部。把TCP的段称为报文段。】
- 发送和接收缓存
【发送方有发送缓冲区,因为发完之后要检错重发,超时重传;接收方也有接收缓冲区,因为读取的速率和接受的速率是不一样的,要安排一个接收缓冲区来匹配这两个速度的不一致性。】
- 全双工数据:
- 在同一连接中数据流双向流动
- MSS:最大报文段大小【任何一个物理网络都有一个MTU,最大传输单元;例如以太网最大传输单元是1500字节,也就是说,对于以太网来说,他有自己的头部,也有它的载荷部分,他的数据部分最长的长度就是1500字节。所以应用进程交下来如果非常长的话,必须要打成一个个MSS的大小,之后每一个MSS的大小加上TCP的头部(20字节)】
- 面向连接:
- 在数据交换之前,通过握手(交换控制报文)初始化发送方、接收方的状态变量【两个应用进程在建立通信之前要建立一个连接】
- 有流量控制:
- 发送方不会淹没接收方
TCP报文段结构
【首部的长度以四个字节为单位;校验和主要是用于校验数据在传输的过程中有没有出错,16比特为单位,求和、然后进位回滚、反码;紧急数据指针基本上不用】
TCP序号,确认号
序号:
- 报文段首字节的在字节流的编号【以字节为编号,载荷部分的第一个字节在整个报文当中的偏移量;两个应用进程在建立连接的时候不是从0或者1这种固定序号开始的,因为要防止老的连接上的分组、滞留在网络当中的分组,它所包括的TCP的段,对新的连接可能会造成一些影响】
确认号:
- 期望从另一方收到的下一个字节的序号【例如,ACK = 4,表明我知道你收到了3及以前的所有字节,而且你是希望我是从4及之后的字节开始传送。】
- 累积确认
【确认号是以字节为单位进行的一个确认,实际上告诉对方对这个确认号字节号及以后字节的一个期待】
Q:接收方如何处理乱序的报文段-没有规定【可以缓存,也可以抛弃掉】
【序号42,确认号79,表示希望对方从79号及以后开始传,到了主机B,主机B处理完了之后再回显,因此你给我42,那么42之前我已经收到了,我希望你从43开始传,因此我给的确认哈是43,主机B知道主机A收到78及以前的了,它希望我是从79号及以后的开始传,那么主机B给的反过来的段的序号是79。】
TCP往返延时(RTT)和超时
【TCP两个应用进程在通信的时候,超时定时器的设置不是一个固定的值,而是一个适应式的测量和适应式的计算】
【TCP所面临的通信环境,随着一段时间变化非常大,因此无法固定的设置超时时间。他的通信环境和在局域网当中两个链路的网卡通信不一样,所以在计算机的局域网当中,他的往返延迟可以设置成一个固定的值,但是在两个应用进程通过TCP为他们提供通信的情况下,他的往返延迟的分布非常大,所以他的超时时间的设置应该是动态的、自适应的。要定期去测量往返延迟,要计算往返延迟的平均值,之后再计算往返延迟的方差。】
Q:怎样设置TCP超时?
- 比RTT要长
- 但RTT是变化的
- 太短:太早超时
- 不必要的重传
- 太长:对报文段丢失反应太慢,消极
Q:怎样估计RTT?
- SampleRTT:测量从报文段发出到收到确认的时间
- 如果有重传,忽略此次测量
- SampleRTT会变化,因此估计的RTT应该比较平滑【因为每个分组当时的网络状况不一样】
- 对几个最近的测量值求平均,而不是仅用当前的SampleRTT
【当前采样值:SampleRTT;前一个周期的采样时间:EstimatedRTT;移动平均值:EstimatedRTT】
- 指数加权移动平均
- 过去样本的影响呈指数衰减
- 推荐值:α = 0.125
设置超时
- EstimatedRTT + 安全边界时间
- EstimatedRTT变化大(方差大) → 较大的安全边界时间
- SampleRTT会偏离EstimatedRTT多远:【当前的采样值离估计RTT(平均值)的偏差的程度的平均值,相当于是标准差】
超时时间间隔设置:
TCP:可靠数据传输
【TCP是GBN和SR的混合体】
- TCP在IP不可靠服务的基础上建立了rdt
- 管道化的报文段【一次在未确认的情况下,可以向对方发送很多段】
- GBN or SR
- 累积确认(像GBN)【接收方如果像发送方发送ack=555,那么说明接收方已经收到了554及以前的字节,接收方希望发送方发送555号及以后的字节】
- 单个重传定时器(像GBN)【发送方只设置1个定时器,不是为每个段都设一个定时器,这一点像GBN】
- 是否可以接受乱序的,没有规范【TCP实体在接收窗口当中收到一个乱序到来的报文段,TCP协议没有对这个报文段的处理做规范,可以抛掉,也可以缓冲】
- 管道化的报文段【一次在未确认的情况下,可以向对方发送很多段】
- 通过以下事件触发重传
- 超时(只重发那个最早的未确认段:SR)【定时器只是和最老的那个段相关联,一旦超时定时器到时,他不是把发出去的所有段都重新发一遍,而是把最老的那一个段重发一遍,这一点像SR】
- 重复的确认
- 例子:收到了ACK50,之后又收到3个ACK50【1个正常确认,3个冗余确认,而超时定时器还没到时就把段放出去,这时候不是超时定时器触发的重发,而是收到三个冗余的ACK触发的重发。我们把这种超时定时器还没到时就重传的叫做快速重传。】
- 首先考虑简化的TCP发送方:
- 忽略重复的确认
- 忽略流量控制和拥塞控制
TCP发送方(简化版)
【一开始初始化进入等待事件的状态。初始化要做的动作:NextSeqNum = 初始化序号,SendBase = 初始化序号,也就是一开始从初始化序号开始发,不是从固定序号开始发,因为要防止老的连接上的段对新的连接的数据传输造成干扰。从上层来了一个数据,用NextSeqNum作为字节的序号形成一个段,通过下层的接口把他交给IP将它放走,同时NextSeqNum要加上数据的长度,相当于发送窗口的前沿向前滑动。如果SendBase等于NextSeqNum,那么就说明没有已发但是未确认的数据,超时定时器就需要关掉。】
TCP发送方事件:
从应用层接收数据:
- 用nextseq创建报文段
- 序号nextseq为报文段首字节的字节流编号
- 如果还没有运行,启动定时器
- 定时器与最早为确认的报文段关联
- 过期间隔:TimeOutInterval
超时:
- 重传后沿最老的报文段
- 重新启动定时器
收到确认:
- 如果是对尚未确认的报文段确认
- 更新已被确认的报文序号
- 如果当前还有未被确认的报文段,重新启动定时器【如果已经到了NextSeqNum,那么就说明已经没有已发送但未确认的报文段,这时候超时定时器就需要关掉。】
简化的TCP发送方
TCP:重传
ACK丢失:【发送方发送92序号,8字节数据,这时候说明92及之后的8字节,也就是99及以前的字节接收方都已经收到了,那这时候接收方希望发送方从第100号字节开始传,所以ACK = 100,如果出错或者丢失,超时重发机制会让发送方重新放送一下92序号,8字节报文段。因为接收方已经收到了99及以前的,所以接收方要再发一次ACK = 100。对顺序到来的字节+1,把期待发送给发送方。】
过早超时:【如果这时发送方发送92序号和8字节,那么接收方应该发ACK=100,发送方再发100序号和20字节,那么接收方应该发ACK=120,如果超时定时器设置的超时时间过早,那么发送方会重发92序号和8字节,只会发最老的那个段,接收方会发ACK=120的确认,因为接收方给到的是顺序到来的最后一个字节+1的一个期待。】
累积确认:【接收方收到ACK=120之后,他的base应该移动到120。接收方发送的确认是累积确认,而且是对这个字节及以后的期待。】
产生TCP ACK的建议 [RFC 1122, RFC 2581]
接收方的事件 | TCP接收方动作 |
所期望序号的报文段按序到达。所有在期望序号之前的数据都已经被确认 | 延迟的ACK。对另一个按序报文段的到达最多等待500ms。如果下一个报文段在这个时间间隔内没有到达,则发送一个ACK。 |
有期望序号的报文段到达。另一个按序报文段等待发送ACK | 立即发送单个累计ACK,以确认两个按序报文段。 |
比期望序号大的报文段乱序到达。检测出数据流中的间隔 | 立即发送重复的ACK,指明下一个期待字节的序号 |
能部分或完全填充接收数据间隔的报文段到达。 | 若该报文段起始于间隔(gap)的低端,则立即发送ACK。 |
快速重传
- 超市周期往往太长:
- 在重传丢失报文段之前的延时太长
- 通过重复的ACK来检测报文段丢失
- 发送方通常连续发生大量报文段
- 如果报文段丢失,通常会引起多个重复的ACK
【发送方发送40-49的段,接收方发送ACK=50确认,接收方没有收到50-59的报文段,那么对于后面收到的60-69、70-79、80-89的段都发ACK=50的确认,这时候,在超时定时器没有到时的情况下就把50-59这个段重发,这样就比超时定时器启动来的时机更早一些,我们把这种重传叫做快速重传】
- 如果发送方收到同一个数据的3个冗余ACK,重传最小序号的段:
- 快速重传:在定时器过时之前重发报文段
- 它假设跟在被确认的数据后面的数据丢失了
- 第一个ACK是正常的
- 收到第二个该段的ACK,表示接收方收到一个该段后的乱序段
- 收到第3,4个该段的ack,表示接收方收到该段之后的2个,3个乱序段,可能性非常大段丢失了
TCP快速重传
快速重传算法
TCP流量控制
【流量控制,就是防止发送方发送的太快,超过了接收方的处理能力】
- 接收方在其向发送方的TCP段头部的rwnd字段“通告”其空闲buffer大小
- RcvBuffer大小通过socket选项设置(典型默认大小为4096字节)
- 很多操作系统自动调整RcvBuffer
【捎带技术:一方把自己空闲缓冲区的大小告诉对方,空闲缓冲区=整个缓冲区-已用缓冲区】
- 发送方限制未确认(“inflight”)字节的个数≤接收方发送过来的rwnd值【防止缓冲区溢出】
- 保证接收方不会被淹没
【LastByteRcvd:上一次收到的字节;LastByteRead:上一次读的地址,两个相减就是已经用了的缓冲区大小】
TCP连接管理
【连接建立的本质是知道要和对方通信,准备好资源,一些控制变量要做置位,特别是一些控制变量双方要发给对方,而且要给一个确认,特别是连接的初始序号,还有初始化的RcvBuffer要告诉对方】
在正式交换数据之前,发送方和接收方握手建立通信关系:
-
- 同意建立连接(每一方都知道对方愿意建立连接)
- 同意连接参数
同意建立连接
Q:在网络中,2次握手建立连接总是可行的吗?
- 变化的延迟(连接请求的段没有丢,但可能超时)
- 由于丢失造成的重传(e.g. req_conn(x))
- 报文乱序
- 相互看不到对方
服务器维持虚假连接
服务器把旧的连接当作新连接来用——【客户方连接请求的段发送出去,服务器发送确认的报文段,但是客户端那边超时重发连接请求的段,重发之后收到了上一个连接请求的确认,然后客户端开始发送数据,服务器没给确认,客户端又重新发送数据;在虚线之前是双方建立起的正常连接的数据传递,之后是重发的,对服务器来说是收到一个连接请求,给一个连接确认,同时又收到一个数据,就把旧的数据当成新的数据来收了,实际上是一个伪造的情况,所以两次握手是不行的。】
TCP 3次握手
【标志位SYN = 1的话就是连接请求,Seq告诉对方我要从x这个字节之后开始传(实际上是从x+1之后开始传);服务器要发送一个连接确认,他的SYN = 1,ACK = 1,确认的ACKnum = x + 1,希望你从x+1开始传给我,同时我把我的序号给你;客户端收到了第二次握手之后就发出第三次握手,ACKnum = y+1,要确认你给我的初始序号,至此三次握手连接确认建立起来。】
【服务器端知道客户端传送给我的都是x+1及以后的数据,客户端知道服务器传送的都是y+1及以后的数据】
【客户端向服务器发出连接建立的请求最少需要三次握手,因为双方必须选择自己的初始序号,还要告诉对方,对方给确认,最少需要三次交互才能完成,第三次交互通常跟第一次数据传递弄在一起】
3次握手解决:半连接和接收老数据问题
【连接请求,连接确认,超时重发,然后客户端关掉了连接建立。超时的连接建立到了服务器,服务器发送确认,但是客户端知道这个是超时的连接,我没有发,发的是已经关闭掉的,所以就拒绝连接建立。】
【因为三次握手不会出现半连接的情况,所以对于老数据,有两种情况,一种就是服务器没有和你建立连接,所以把数据丢掉;还有一种就是滞留的老数据,两边的端口号是一致的,但是因为连接的序号不在当前连接的范围内,所以被丢掉】
TCP 3次握手:FSM
TCP:关闭连接
- 客户端,服务端分别关闭它自己这一侧的连接
- 发送FIN bit = 1的TCP段
- 一旦接收到FIN段,用ACK回应
- 接到FIN段,ACK可以和它自己发出的FIN段一起发送
- 可以处理同时的FIN交换
【最后一次连接释放,释放的确认段或者连接释放的段是不可靠的,只要依赖最后一段,不管采用几次的确认都是不可靠的,都存在一方维持着连接,另外一方把连接拆除的情况】
【一方释放连接,对方确认;对方也没有数据发了,也释放连接,一方也确认,确认发完之后启动一个定时器,定时器到来之前如果没有数据再被接收到,这个连接就被真正的关掉了。】
【TCP连接释放,对称释放,并不完美。】