TCP协议(三)

  • 流量控制
    接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等一系列连锁反应。因此,TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制。
    下面通过图示例子说明入户利用滑动窗口机制进行流量控制。
    设A向B发送数据。在连接建立时,B告诉了A:“我的接收窗口rwnd = 400”(这里rwnd表示receiver window)。因此,发送方的发送窗口不能超过接收方给出的接收窗口的数值。请注意,TCP的窗口单位是字节,不是报文段。TCP连接建立时的窗口协商在图中没有显式出来。再设每一个报文段为100字节长,而数据报文段序号的初始值设为1.请注意,途中箭头上边大写ACK表示首部中的确认位ACK,小写ack表示确认字段的值。
    在这里插入图片描述
    我们应注意到,接收方的主机B进行了三次流量控制。第一次把窗口减小到rwnd = 300,第二次又减到rwnd=100,最后减到rwnd=0,即不允许发送方再发送数据了。这种使发送方暂停发送的状态将持续到主机B重新发送一个新的窗口值为止。我们还注意到,B向A发送的三个报文段都设置了ACK=1,只有在ACK=1时确认号字段才有意义。
    现在我们考虑一种情况:B向A发送了零窗口的报文段后不久,B的接收缓存又有了一些存储空间。于是B向A发送了rwnd=400的报文段。然而这个报文段在传送过程中丢失了。A一直等待收到B发送的非零窗口的通知,而B也一直等待A发送的数据。如果没有其他措施,这种互相等待的死锁局面将一直持续下去。
    为了解决这个问题,TCP为每一个连接设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器。若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。如果窗口仍是零,那么收到这个报文段的一方就重新设置持续计时器。如果窗口不是零,那么死锁的僵局就可以打破。
    接收端如何把窗口大小告诉发送端呢?
    回忆我们的TCP首部中,有一个16位的窗口字段,就是存放了窗口大小信息;那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小时窗口字段的值左移M位。(扩大2的M次)
    拥塞控制
    虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能雪上加霜的。
    发送方维持一个叫做拥塞窗口cwnd的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口(如果再考虑到接收方的接受能力,那么发送窗口还可能小于拥塞窗口)。发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数。
    TCP引入慢启动机制,先发少量的数据探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。
    慢开始的思路是这样的:当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。经验证明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口cwnd设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。 用这样的方法逐步增大发送方的拥塞窗口cwnd,可以使分组注入到网络的速率更加合理。
    在这里插入图片描述

  • 发送开始的时候,,定义拥塞窗口大小为1;

  • 每次收到一个ACK应答,拥塞窗口加1

  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口

像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动”只是指初始时慢,但是增长速度非常快。

为了不增长的那么快,因此不能使拥塞窗口单纯的加倍

此处引入一个叫做慢启动的阈值

当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长

当TCP开始启动时 ,,慢启动阈值等于窗口最大值

在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置为一

那么发送方又是如何知道网络发生了拥塞呢?我们知道,当网络发生拥塞时,路由器会丢弃分组。少量的丢/包,我们仅仅是出发超时重传;大量的丢包,我们就认为网络拥塞。

延迟应答

如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小

	假设接收端缓冲区为1M,一次收到了500K的数据;如果立即应答,返回的窗口就是500K,但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了,在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些也能处理过来,如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口就是1M

一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率

那么所有的 包都可以延迟应答吗?当然不是的
  • 数量限制:每隔N个包就应答一次
  • 时间限制:超过最大延迟时间就应答一次

具体的数量和超时时间,依操作系统不同也有差异:一般N取2,超时时间取200ms

捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是“一发一收”的,意味着客户端给服务器说了"How are you",服务器也会给客户端回一个“Fine”,那么这个时候ACK就可以搭顺风车,和服务器回应的“Fine”一起回给客户端

面向字节流

创建一个TCP的socket,同时在内核中创建一个“发送缓冲区”和“接收缓冲区”

  • 调用send/write时,数据会先写入发送缓冲区中
  • 如果发送的字节数太长,会被拆分成多个TCP的数据包发出
  • 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去
  • 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区
  • 然后应用程序可以调用read从接收缓冲区拿数据
  • 另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区。那么对于这一个连接,既可以读数据,也可以写数据。(全双工)

由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:

  • 写100个字节数据时,可以调用一次write写100个字节,也可以调用100次write每次写一个字节
  • 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read100个字节,也可以重复read100次,一次read一个字节

粘包问题

首先明确一点,粘包问题中的“包”,是指应用层的数据包。在TCP的协议头中,没有如同UDP一样的“报文长度”这样的字段,但是有一个序号这样的字段。站在传输层的角度,TCP是一个一个报文过来的,按照序号排好序放在缓冲区中。站在应用层的角度,看到的只是一串连续的字节数据,那么应用程序看到了这么一串字节数据就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。

那么如何避免粘包问题呢?明确两个包之间的边界!!!
  • 对于定长的包,保证每次都按固定大小读取即可
  • 对于变长的包,可以在包头位置,约定一个包总长度的字段,从而就知道了包的结束位置
  • 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序员自己定的)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值