动手学习TCP中

1 数据传输与MSS分段

TCP是面向字节流的协议的,不限制应用层传输内容长度,但是实际上在网络层和数据链路层由于受到机器内存限制,因此必须限制内容长度,这就需要把应用层任意长度的字节流进行拆分。那么拆分成报文段依据是什么?又是怎么拆分的?

1.1TCP 应用层使用示例

首先看下socket 应用层编程是怎么使用的,如下面代码所示,在应用层是直接发送的是一个比较大的字节流,应用层并没有考虑分段。


import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8080);
        String tmp = "hello world";
        for (int i = 0; i < 1000; i++) {
            tmp += "hello world";
        }
        while (true) {
            Socket socket = serverSocket.accept();
            System.out.println("有客户端连接来了" + socket + "--发送长度" + tmp.length());
            //发送内容,传输的是一个比较大的,发送长度11011
            socket.getOutputStream().write(tmp.getBytes(StandardCharsets.UTF_8));
        }
    }
}

 import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket();
        SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
        socket.connect(socketAddress, 1000);
        byte[] buf = new byte[1024];
        while (true) {
            InputStream inputStream = socket.getInputStream();
            //在已经分配好的内存中读取任意长度(小于buf大小)的字节流
            int len = inputStream.read(buf);
            if (len == -1) {
                System.out.println("close");
                break;
            }
            System.out.println("------len--------"+len);
            System.out.println(new String(buf, 0, len));
        }
    }
}

在这里插入图片描述

1.2TCP流与报文分段

TCP流分段依据:MSS,防止IP层分段;流控,接收端的能力。
MSS 相关说明:

  • 定义:仅指TCP承载的body数据大小,不包含数据头部。参加RFC879
  • MSS目的:使得每个报文段携带更多的数据,减少头部空间的占比;防止每个报文段背某个设备的IP层基于IP层基于MTU拆分(1个分片丢包后,所有的分片都得重传)
  • 默认MSS:536字节,536=默认MTU576-20(IP头部)-TCP(头部)
  • 握手阶段协商MSS,通过TCP option 类型为2,长度为4
  • MSS分类:SMSS(发送方最大报文段),RMSS(接收方最大报文段)
    在这里插入图片描述

2 重传与确认

TCP会将应用层字节流拆分多个报文段,通过重传与确认保证每个报文段一定会到达对方。
如下图所示,设备A向设备B发送消息,如果第二条消息丢了该怎么办呢?
在这里插入图片描述
解决之道PAR:Positive Acknowledgment with Retransmission。这种做法比较简单,发送每一条消息并启动一个定时器等待接受方应答,如果在规定时间接收到应答继续发下一条消息;如果在规定的时间内没有接收到应答则重发这条消息;由于是串行化(必须一个一个发)的操作,效率会比较低。

在这里插入图片描述
PAR改进版本(并发发送),并发发送时候会给每个消息加一个唯一编号。这里要考虑到接受方机器内存空间的限制,需要加了一个limit限制发送方,具体如下图所示。
在这里插入图片描述
由于TCP要解决应用层字节流的可靠发送,引入了Sequence 序列号和ACK序列号,序列号的值是针对的字节流而不是报文。序列号可以有效跟踪应用层的发送端是否送达,确定接收端有序接收的字节流。如下图所示,可以看到TCP报文长度是552,Sequence 序列号长度609,因此Next Sequence是552+609=1161
在这里插入图片描述
对应的ack报文
在这里插入图片描述
TCP序列号只有32位,最大能传输4G,如果一直复用一个连接序列号肯定会超过4G。PAWS(Protect Against Wrapped Sequence numbers)防止序列号回绕,通过时间TCP option timestamp,发送TCP报文时候带上相应的时间戳。
Kind

3 RTO重传定时器计算

在指定时间内没有收到相应的确认时候,需要重传这个报文段,这个指定时间设置多大是合适呢?

3.1 RTT

定义:RTT(Round trip time),往返延时。如下图所示,从客户端来看发送一个SYN报文到收到这个报文ACK所需要的时间。
在这里插入图片描述
如何测量RTT? 在不考重传的情况下会比较简单,发送方记录对应序列号的,等到ack响应报文回来时候直接减发送的时间即可,在有重传情况下,按照这种计算方式就会有问题,如下图所示。第二种测量方法可以通过TCP option 中Timestamp 选项回显时间。

在这里插入图片描述

3.2 RTO(Retransmission Timeout)重传超时时间

RTO 应当略大于RTT,实际上RTT是经常变化的,比如网络波动,网络路径重新选择。
在这里插入图片描述
由于RTT经常变化,RTO应当考虑平滑性,在RFC793文档中关于平滑的RTO:

  • SRTT(smoothed round-trip time)=a*SRTT+(1-a)*RTT。其中a是从0-1(RFC推荐是0.9)
  • RTO=min(UBOUND,max(LBOUND,b*SRTT))。UBOUND一般1分钟,LBOND为1秒钟,b从1.3到2 之前

平滑的RTO,不适用于RTT波动大(方差大)的场景,目前大部分操作系统都没有采用,linux采用追踪RTT方差。

4 滑动窗口:发送窗口与接收窗口

4.1发送窗口

如下图所示,是一个发送窗口的快照可以划分为四个部分。值得说明是这里52字节以后是应用层已经调用wrtie等方法告诉TCP需要发送字节。其中46-51字节称为可用窗口(usable window),32-51称为发送窗口,
在这里插入图片描述
当把46-51字节发送出去后可用窗口耗尽,但是发送窗口大小没有变化。在这里插入图片描述
滑动窗口移动,当32-36收到ack应答,如下图所示如果发送窗口就会右移动,52-56是usable window。

在这里插入图片描述
发送窗口,SND.UNA(send Unacknowledged Pointer),SND.NXT(send next pointer)。可用窗口大小=SND.WND-(SND.NXT-SND.UNA)=20-(46-32)=6
在这里插入图片描述

4.2接收窗口

如下图所示,其中1-31已接收字节,32-51是接收窗口(这与内存或者缓冲区设置有关)接收端还可以再接收20字节。RCV.NXT 表示下一个要接收字节指针。通常接收窗口大小约等于对端发送窗口,窗口大小会与缓冲区大小以及进程读取缓冲区的速度有关。
在这里插入图片描述

5 滑动窗口与流量控制

一个实际传输的案例,为了简化问题,前提条件:MSS不变(实际上MSS会随着网络路径的变化而变化),窗口不变(窗口大小会和操作系统缓冲区大小&进程读取缓冲区的快慢都有关系),具体如下面所示。

5.1客户端和服务端初始化

如下图所示:客户端发送窗口大小位360,对应服务器接收窗口也是360;服务器发送窗口为200,客户端接收窗口大小位200;发送窗口已经发送未确认指针(SND.UNA)从1开始,服务器为了区分SND.UNA设置为241。
在这里插入图片描述

5.2客户端发送140字节请求

客户端发送140字节请求(如HTTP get请求),此时SND.NXT值变为141=1+140,服务器在监听等待,客户端发送可用窗口为220。
在这里插入图片描述

5.3 服务器接收到140字节并发送80字节的响应以及ACK

服务器接收140字节,并发送80字节的响应以及ACK;所以此时RCV.NXT值变为141=1+140,
SND.NXT=241+80=321.
在这里插入图片描述

5.3 客户端接收服务器响应80字节以及ACK并发送ACK

客户端接收到服务器140字节的ACK后,SND.UNA=1+140=141,由于接收了服务器的80字节
RCV.NXT=241+80=321。客户端发送80字节ACK(服务器还没有收到)。此时服务器读取了要响应文件280字节,此时发现服务器发送可用窗口=SND.WND-(SND.NXT-SND.UNA)=200-(441-321)=120字节,所以在第四步只能发送120字节,发送后服务器SND.NXT=441,此时可用窗口为0,也称为窗口关闭。
在这里插入图片描述

5.4 客户端接收响应文件第一部分120字节,服务器接收到80字节ACK

客户端接收到服务器120字节的,RCV.NXT=120+321=441。在第六步服务器接收到客户端80字节ACK,SND.UNA=241+80=321,此时服务器端可用窗口为80字节。值得说明是:这里服务器收到80字节后,服务器是否步不等待窗口足够立即发送,如果立即发送客户端延迟会比较低,但是服务器会多做一次上下文切换,而且由于固定40字节的TCP、IP头部,有效信息比会更低;因此这个需要服务器要在低时延和高吞吐量上做权衡,比如当服务器使用了吞吐量优先的设置时,这个就会延迟发送,反之立即发送。
在这里插入图片描述

5.5 服务器接收第四步ACK

服务器接收到了第四步发出120字节ACK(这里是假定服务还没有发送剩下响应报文),服务器的SND.UNA=321+120=441,此时服务期可用窗口为200字节。

在这里插入图片描述

5.5 服务器发送剩余内容

服务发送160字节后,其SND.NXT=601。
在这里插入图片描述

5.6 客户端接收剩余部分160字节并发送响应ACK

在这里插入图片描述

5.7 服务器接收第8步的响应ACK

服务器接收到步骤8的ACK,SND.UNA=601,发送可用窗口为恢复到200.
在这里插入图片描述

6 操作系统缓冲区和滑动窗口的关系

上一节中,假定发送窗口和接收窗口都是不变的,但是实际上无论是发送窗口还是接收窗口其所存放字节数都是放在OS缓冲区中的;操作系统缓冲区会被操作系统调整,应用进程不能及时读取缓冲区内容时候也会对缓冲区造成影响。

6.1 应用进程没有及时读取缓存

如下图所示,服务器应用进程没有及时读取缓冲区大小,导致接收窗口会不断地减少,最终收缩为0,此时也叫窗口关闭。
在这里插入图片描述

6.2 收缩窗口导致丢包

从下图所示,在步骤2由于内存紧张导致缓存区减少,收缩窗口导致客户端在步骤3发送的包丢失。当然实际操作系统不会让这种情况发生的,通常是先收缩窗口,再减少缓存,避免了这种丢包情况。对于这种窗口关闭后,客户端要定时发送探测窗口给服务器。

在这里插入图片描述

6.3 接收窗口和发送窗口应该配置多少合适

最大接收窗口= BPS(带宽)*RTT (时延),相同带宽下,如果RTT越大,那么应当配置更大的缓冲区,以允许TCP使用更大的滑动窗口。

7 如何减少报文提升网络效率

当TCP报文段只有几个字节时候,整个网络效率是很低的;因为每个TCP报文段都有固定20字节TCP头部和20字节IP头部,所以在整个报文中有效地信息占比更少。

7.1糊涂窗口综合证(Silly Window Syndrome)

在这里插入图片描述

参考文献
[1]极客时间,陶辉,Web协议详解与抓包实战

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值