上次说到了客户端与服务器的连接操作,今天解决收发操作,如何使用滑动窗口解决等待时间问题,TCP模块交给IP模块后IP模块发送数据前做了什么。
发送缓冲区:
当浏览器调用完connect方法后,接下来就是调用write方法将发送的数据交给协议栈,协议栈接收到信息后并不是立马发送出去,而是将数据存放在内部的发送缓冲区中,并等待应用程序的下一段数据。不同的应用程序读取字节的方法有时是不同的。有的是一个一个字节传的,有的是一组一组传的。如果是一个字节一个字节传送的话,那每传一个字节就要发送一次数据,那得发送多少次。所以需要一个发送缓冲区,一般长度大小就是由MTU的大小决定。
MTU:
表示一个网络包的最大长度,在以太网中一般是1500字节(包括ip头部,tcp头部和数据部分,不包括,mac头部)
MSS:
除去头部之后,一个网络包所能容纳的TCP数据的最大长度(不包含ip头部,tcp头部)。
发送最大等待时间:
但是发送缓冲区在未存满的情况下不可能无休止的等待下去,所以会有一个最大等待时间,如果过了这段时间数据包还没有发送,那么就直接发送。
拆分较大数据:
当一个用户的发送的http请求消息比较短的话一般不需要拆分包,但是如果提交大表单等一次发送很多数据的情况下(比如发送一篇大文章),超过了一个网络包所能容纳的最大数量,那么就会被被拆分成好几块发送。具体一块一块发送到服务器那如何拼回完整的以后说。类似于一份寒假作业赶作业时一个人的能力是有限的,所以可以多找几个人写,别人不来帮你不能一直等,写完了还要按顺序拼在一块(玩笑玩笑)。
使用ACK确认网络包已经收到:
如果接收方接收到了网络包会回复ACK信号表示“我已经接收到了”。在TCP拆分数据的时候就会计算,当前网络包的长度大小,首个字节位于全包下的哪一号索引。知道了首位和长度,就可以计算出下一个网络包的首位。TCP头部中的序号就是这段时间派上用场的。假设第一个包的序号是1,那么下一个包的序号就应该是1 + 第一个包的长度。假设一个包的长度是1460个字节,那么第二个包的序号就应该是1461,第三个就应该是2921。如此,当服务器接收到网络包后就会设置ACK值,表示期望下一个包的序号是多少多少。然后发送发接收到后发送第二块包。如此就完成了网络的发送操作。如果有遗漏,那么发送方一定会少接收到某个或者某些ACK值,后面检查的时候补上即可。
随机序号初始值:
实际上通信中发送序号一般都不是从1开始的,而是从一个随机数子开始的。因为如果从1开始,那么对于别人就可以轻而易举的计算出后续网络包的序号,知道需要后就可以利用这一点进行攻击。但是如果初始值是随机的那么就没有这一点。
实际上的通信在连接时就已经相互告知双方的初始化序号值。比如连接时发送方告知接收方我是从1122开始的,接收方告知发送方我是从3344开始的,那么在发送数据时,发送方就会发送第一个数据序号为1123号,设置ACK值为3345表示期望对方回复的包的编号。假设数据包长度为1460,那么接收方接收到后,将序号为1123的数据包填写在第一个位置,然后设置ACK值为1123 + 1460,表示下次期望你发送的型号是由这个开始的,然后将长度为54字节的回复信息发送出去,序号为3345。发送方接收到3345号数据包后将数据包解析TCP头部,放在接收信息的第一块位置,解析后发现需要将1123 + 1460序号的包发送给接收方,所以就发送给接收方发送1123 + 1460号数据包,并且期望收到3345号数据包的回复信息。如此直到发送完毕。注意这里只分发送方和接收方,不分服务器和客户端。
通过这一机制我们就可以清楚到底哪个包发送异常,哪个包没有送到,没送到就重新发送,也因此网卡,集线器,路由器没有了错误补偿机制。
根据网络包的平均往返时间调整ACK号等待时间:
等待的时间不能是固定的,如果网络比较通畅的情况下等待数据包的时间多了就会导致数据发送较慢。而网络比价堵塞的情况下短时间重发几个数据包会导致增加堵塞情况。因此需要动态调整等待ACK号时间。
滑动窗口解决等待时长问题:
当一个数据包发送后等待一个ACK包的时间是可以利用的。
当发送一个数据包到接收方处理该数据包给出回应再发送数据包,这种情况是含有大量等待时间的。我小学的时候老师批改课堂作业都是“写好了就拿上来,排队等着批改”。这种方法同样适用于客户端和服务器。当发送方等待接收ACK号的时候就可以发送下一个ACK号了。如果老师叫一个名字,那个孩子就拿作业上去批改,和老师不叫名字,孩子自动上去排队等着批改,中间的空闲时间就会减少。相反的,不可能全班的孩子同时上去排队批改作业。一般老师会说“一次上来五个人就够了”。这就是滑动窗口的接收缓冲区空间问题。当发送方发送的数据包个数超过了服务器能够一次接收的上限,那么多出来的数据包就会丢失。假设当数据缓冲区容量为5,发送方在一开始发送的时候就需要被告知缓冲区的最大容量了!而这个值在TCP头部中,他就是窗口值。发送方在建立连接时就知道了接收方最多能接受容量为‘5’的窗口值,因此在服务器还没回应新的窗口值可容纳量时,发送方不会发送新的数据,当接收方发送TCP头部告知发送方窗口缓冲区的包我read了4个,你可以再发4个给我,那么发送方就会立刻发送4个过去。以此解决等待时长问题。
回复窗口值与ACK值冲突问题:
刚刚虽然解决了发送问题,但是每次接收方接收到数据包后都需要向发送方反馈,希望下一个是多少。比如老师上课让同学回答1-5选择题的答案,有两种情况,第一种情况是:第一题多少,A,第二题多少,B.....第二种情况:1-5题答案多少,ABCDA。我们发现每当处理返回了一个ACK值其实就意味着缓冲区空出了一个包。所以真实情况下并不需要处理一个包发送一个ACK,应用包发送是连续的。所以可以一次处理好几个连续的包,如果没有缺漏的话直接跳到最后一个包的ACK值,同时更新窗口信息。这样就解决了ACK值返回与窗口值返回冲突问题。