收发数据的原理(下)
来源:Dwyane_Coding     阅读:769
动风网络
发布于 2018-12-25 22:37
查看主页

前言:网络知识非常的重要,假如你不是做程序的,那么少量网络常识还是得知道的;而做程序的,就更不用说了,不仅需要理解少量网络知识,还是知道其原理,假如不理解原理,不敢说他不是程序员,但是总缺了点意思,就像去北京没去过长城一样。

网络原理系列文章:
一、五分钟理解网络连接(已完成)
二、收发数据的原理(上)(已完成)
三、收发数据的原理(下)(已完成)
四、收发数据的番外篇(未完成)

由于网络原理不是三言两语可以讲完,假如读者很忙,可以直接拉到最底下,看总结,知道个大概,再回头细读此文章。感谢关注。废话不多说,直接进入主题。在上篇我们已经讲了TCP收发数据的前两步,接下来是最后两步。

将HTTP消息传给协议栈

上篇讲到控制流程从 connect 回到应用程序之后,就到了数据收发阶段。
数据收发数据是从应用程序调用write将要发送的数据交给协议栈开始的,协议栈收到数据后执行发送操作,这一操作包含如下要点。

首先,协议栈并不关心应用程序传来的数据是什么内容。应用程序调用write时会指定发送数据的长度,在协议栈看来,要发送的数据数据就是肯定长度的二进制字节序列而已。

其次并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并且继续等下一段数据。不过应用程序交给协议栈发送的数据长度是由应用程序本身决定,有些应用程序会一次性传递所有的数据,有些程序则会逐字节或者者逐行传递数据。

总之,一次将多少数据交给协议栈是由应用程序决定的,协议栈没有这个控制行为。

协议栈之所以不一收到数据就发出去,是由于那样可能会发送大量的小包,导致网络效率下降。至于积累多少数据才发送,有以下两个要素判断。

第一,每个网络包能容纳的数据长度。协议栈会根据一个叫做MTU的参数来进行判断。MTU表示一个网络包的最大长度,在以太网中一般是1500字节。MTU包含了头部的总长度,所以MTU减去头部长度才是一个网络包所能容纳的最大数据长度,这一长度叫做MSS。当协议栈收到的长度大于或者者接近MSS时发送出去,就很好的处理大量小包的问题。

MTU表示一个网络包的最大长度,在以太网中一般是1500字节。MTU包含了头部的总长度,MTU = MSS + 头部,所以MSS是一个网络包所能容纳的最大数据长度。

第二,等待时间。当应用程序发送数据频率不高的时候,协议栈收到的数据要接近MSS,可能要等非常久,而造成发送推迟,所以在这种情况下,即时缓冲区的数据没接到MSS,都发送出去。协议栈内部里面有计时器,经过肯定时间,就会把网络包发送出去。

协议栈内部里面有计时器,经过肯定时间,就会把网络包发送出去。

读者可以发现,其实这两个判断要素是相互矛盾的。假如长度优先,网络效率会提高,但可能由于等待而产生发送推迟;相反,时间优先,则会降低网络效率,但推迟时间减少。所以这两个要素要综合考虑,以达到平衡。这个平衡由协议栈的开发者来决定,所以不同种类和版本的操作系统在相关操作上也就存在差异。当然应用程序在发送数据时,可以指定发送选项,比方说让网络包直接发送,不用存在缓冲区了。

对较大数据进行拆分

HTTP请求消息一般不会很长,一个网络即可装下,但假如要发送一张图片或者者发送一篇长文呢,发送缓冲区的数据一定超过MSS的长度。这时,我们除了不等到后面的数据,还要对现有数据进行拆分,拆分的每块数据会放进每个单独的网络包。

上一篇也讲过,发送数据前,要在每一块数据增加TCP头部,并根据套接字中包含的通信对象的信息(发送方和接收方的端口号),而后交给IP板块解决发送操作,IP板块会在每个网络包前面增加IP头部和以太网头部,具体操作,后面再讲。

网络错误检测和补偿机制

网络以及其余环境很复杂,收发数据时,难免会在发送中出现错误,所以需要检测和补偿机制。

网络包发往服务器,需要确认对方能否收到网络包,对方没收到时及时重发。那么确认原理是什么?

TCP板块在拆分数据时,会算好每一块数据相当于从头开始的第几个字节,接下来在发送此块数据,会将算好的字节数写在TCP头部中,上一篇中说到的seq作用就在这里。而后告知接收方数据长度,但是数据长度不是通过TCP头部传输,由于接收方可以通过整个网络包的长度减去头部长度得出。所以,我们可以知道发送的数据是从第几个字节开始,长度是多少。

通过上面两个数值,接收方还可以检查收到的网络包有没遗漏。比方:上次接收到第1120字节,假如接下来收到序号是第1121的包,则表示没有遗漏。收到第2200字节,则有包遗漏了。假如确认没有遗漏,接收方会将到目前为止接收到的数据长度加起来,计算出一共已经收到了多少个字节,而后将这个数值写入TCP头部的ACK号中发送给发送方,同时将控制位中的ACK比特设为1,这代表ACK号字段有效。返回ACK号这一操作称作确认响应。

有个需要注意的是,seq序号不是从1开始,由于从1开始,很容易被猜到,被攻击者发动攻击。所以seq序号初始值是用随机数算出来,开始收发数据前需要告知通信对象序号初始值。上文讲到连接过程中,有一个将SYN控制位设为1并发送给服务器的操作,就是在这一步将序号的初始值告知对方的。实际上,在将SYN设为1的同时,还需要同时设置序号字段的值,而这里的值就是初始值。

发送方和接收方在开始收发数据前需要告知对象序号初始值

通过seq序号和ACK号可以确认数据,我们前面只考虑了单向传输,但TCP数据收发是双向的,所以用户端向服务器发送数据,服务器也会向用户端发送。所以收发双方都需要计算序号,并且在连接过程中相互告诉对方自己计算的序号初始值。

将HTTP消息传给协议栈

上篇讲到控制流程从 connect 回到应用程序之后,就到了数据收发阶段。
数据收发数据是从应用程序调用write将要发送的数据交给协议栈开始的,协议栈收到数据后执行发送操作,这一操作包含如下要点。

首先,协议栈并不关心应用程序传来的数据是什么内容。应用程序调用write时会指定发送数据的长度,在协议栈看来,要发送的数据数据就是肯定长度的二进制字节序列而已。

其次并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中,并且继续等下一段数据。不过应用程序交给协议栈发送的数据长度是由应用程序本身决定,有些应用程序会一次性传递所有的数据,有些程序则会逐字节或者者逐行传递数据。

总之,一次将多少数据交给协议栈是由应用程序决定的,协议栈没有这个控制行为。

协议栈之所以不一收到数据就发出去,是由于那样可能会发送大量的小包,导致网络效率下降。至于积累多少数据才发送,有以下两个要素判断。

第一,每个网络包能容纳的数据长度。协议栈会根据一个叫做MTU的参数来进行判断。MTU表示一个网络包的最大长度,在以太网中一般是1500字节。MTU包含了头部的总长度,所以MTU减去头部长度才是一个网络包所能容纳的最大数据长度,这一长度叫做MSS。当协议栈收到的长度大于或者者接近MSS时发送出去,就很好的处理大量小包的问题。

MTU表示一个网络包的最大长度,在以太网中一般是1500字节。MTU包含了头部的总长度,MTU = MSS + 头部,所以MSS是一个网络包所能容纳的最大数据长度。

第二,等待时间。当应用程序发送数据频率不高的时候,协议栈收到的数据要接近MSS,可能要等非常久,而造成发送推迟,所以在这种情况下,即时缓冲区的数据没接到MSS,都发送出去。协议栈内部里面有计时器,经过肯定时间,就会把网络包发送出去。

协议栈内部里面有计时器,经过肯定时间,就会把网络包发送出去。

读者可以发现,其实这两个判断要素是相互矛盾的。假如长度优先,网络效率会提高,但可能由于等待而产生发送推迟;相反,时间优先,则会降低网络效率,但推迟时间减少。所以这两个要素要综合考虑,以达到平衡。这个平衡由协议栈的开发者来决定,所以不同种类和版本的操作系统在相关操作上也就存在差异。当然应用程序在发送数据时,可以指定发送选项,比方说让网络包直接发送,不用存在缓冲区了。

对较大数据进行拆分

HTTP请求消息一般不会很长,一个网络即可装下,但假如要发送一张图片或者者发送一篇长文呢,发送缓冲区的数据一定超过MSS的长度。这时,我们除了不等到后面的数据,还要对现有数据进行拆分,拆分的每块数据会放进每个单独的网络包。

上一篇也讲过,发送数据前,要在每一块数据增加TCP头部,并根据套接字中包含的通信对象的信息(发送方和接收方的端口号),而后交给IP板块解决发送操作,IP板块会在每个网络包前面增加IP头部和以太网头部,具体操作,后面再讲。

网络错误检测和补偿机制

网络以及其余环境很复杂,收发数据时,难免会在发送中出现错误,所以需要检测和补偿机制。

网络包发往服务器,需要确认对方能否收到网络包,对方没收到时及时重发。那么确认原理是什么?

TCP板块在拆分数据时,会算好每一块数据相当于从头开始的第几个字节,接下来在发送此块数据,会将算好的字节数写在TCP头部中,上一篇中说到的seq作用就在这里。而后告知接收方数据长度,但是数据长度不是通过TCP头部传输,由于接收方可以通过整个网络包的长度减去头部长度得出。所以,我们可以知道发送的数据是从第几个字节开始,长度是多少。

通过上面两个数值,接收方还可以检查收到的网络包有没遗漏。比方:上次接收到第1120字节,假如接下来收到序号是第1121的包,则表示没有遗漏。收到第2200字节,则有包遗漏了。假如确认没有遗漏,接收方会将到目前为止接收到的数据长度加起来,计算出一共已经收到了多少个字节,而后将这个数值写入TCP头部的ACK号中发送给发送方,同时将控制位中的ACK比特设为1,这代表ACK号字段有效。返回ACK号这一操作称作确认响应。

有个需要注意的是,seq序号不是从1开始,由于从1开始,很容易被猜到,被攻击者发动攻击。所以seq序号初始值是用随机数算出来,开始收发数据前需要告知通信对象序号初始值。上文讲到连接过程中,有一个将SYN控制位设为1并发送给服务器的操作,就是在这一步将序号的初始值告知对方的。实际上,在将SYN设为1的同时,还需要同时设置序号字段的值,而这里的值就是初始值。

通过seq序号和ACK号可以确认数据,我们前面只考虑了单向传输,但TCP数据收发是双向的,所以用户端向服务器发送数据,服务器也会向用户端发送。所以收发双方都需要计算序号,并且在连接过程中相互告诉对方自己计算的序号初始值。

工作过程

上图表示了实际的工作过程。首先,用户端在连接时需要计算出序号初始值并告知服务器(①)。接下来,服务器会通过初始值计算出ACK号并返回给用户端(②)。初始值有可能在通信中丢失,所以服务器需要返回ACK号给用户端作为确认。由于数据传输是双向,服务器也需要告知用户端它计算出来的序号初始值,并将其发给用户端(②)。接下来,用户端也会计算出ACK号告知服务器,已经收到了其发来的初始值(③)。到此,连接操作工作完成。接下来到收发操作工作,数据收发工作可以双向同时进行。用户端向服务器发送请求,序号也会跟随数据一起发送(④),服务器收到数据返回ACK号(⑤)。同理,服务器向用户端发送数据(⑥⑦)。

在得到对方确认之前,发送过的网络包都会保存在缓冲区中,假如出现丢包现象,也就是通信对象没有返回ACK,协议栈中的TCP板块重新发送这些包。

通过“seq”和“ACK”可以确认对方能否收到网络包。

返回ACK号的等待时间(也叫超时时间),当网络繁忙时会发生拥塞,这时需要把等待时间设置长点,否则重发包了,上次需要返回的ACK号才来,这样会导致原本就拥塞的网络更加要命。假如设置等待时间过长,也不行,重传包会有很大推迟。这又要找一个时间平衡,真难!所以TCP采用了动态调整等待时间的方法。这个等待时间根据ACK号返回所需的时间来判断的。具体来说,TCP会在发送数据的过程中,不断的测量ACK号的返回时间,假如ACK号返回很慢,则延长等待时间,相反,假如返回很快,则缩短等待时间。

采用滑动窗口来管理数据发送和ACK操作

每发送一个网络包,就等到一个ACK号返回,这个很容易了解,但是在等待ACK返回这段时间,假如什么都不做,就非常白费。为了减少白费,TCP采用滑动窗口管理数据发送和ACK号的操作。所谓滑动窗口,就是在发送一个包,不等待ACK号返回,直接发送后续的一系列包。

但是这样有可能出现以下问题,在不返回ACK号的时候,就连续发送包,可能导致发送包的频率超过接收方解决能力的情况。具体来说,接收方TCP接收到包,会先将数据存放到接收缓冲区中。而后,接收方需要计算ACK号,将数据块组装起来复原成本来的数据并传递给应用程序,假如该操作未完成,又有下一个包到来,同样是存入接收缓冲区中,假如包到来速率比将数据块组装数据并传给应用程序速率快,缓冲区数据就会越积越多,最后溢出,接收方就收不到后面的包了。所以,接收方需要告诉发送方自己最多能接收多少数据,而后发送方根据这个值对数据发送进行控制,这个最大值称为窗口大小。这就是滑动窗口方式的基本思路。

能够接收的最大数据量称为窗口大小,它属于TCP调优的一个重要参数

ACK与窗口包的合并

前面说过窗口大小就是最大接收量,当接收的数据存入缓冲区中,没必要马上向发送方升级窗口大小,升级窗口大小时机应该是接收方从缓冲区中取出数据传递给应用程序的时候,由于这时,缓冲区中数据减少,剩余的空间变大,理应告诉发送方。

接收方收到数据,确认内容没有问题,就应该向发送方返回ACK号。假设ACK包是一个包,而升级窗口大小又是另外一个包,这样可能会收到一个包的情况下,接收方需要向发送方返回两个包。这样一来,接收方发给发送方的包就太多了,导致网络效率下降。

所以,假如在等待发送ACK的时候,恰好也要升级窗口大小,即可以把这两个包合并成一个包发送,从而减少的包的数量。当需要连续发送多个ACK号,也可以减少包的数量,这是由于ACK号表示的是已经收到的数据量,也就是说,它是告诉发送方目前已接收的数据最后位置在哪里,由于当需要连续发送ACK号时,只需发送最后一个ACK号即可以了。同理,当需要连续发送多个窗口升级也可以减少包的数量。

接收HTTP响应消息

用户端委托协议栈发送请求后,等待服务端返回的消息,调用read程序来获取响应消息。和发送数据一样,接收数据也需要将数据暂存到接收缓冲区中。具体操作如下,协议栈尝试从接收缓冲区取出数据并传递给应用程序,但这个时候可能响应消息还没返回,所以接收操作就没法继续。那么,协议栈会将应用程序的委托,也就是从缓冲区取数据的工作暂时挂起,等响应消息到达再继续接收操作。注意,这里只是挂起这项工作,协议栈并没有中止工作,还会解决好多其余的工作。

应用程序在发送数据和接收数据都依赖协议栈。

协议栈接收数据会先将数据放入缓冲区,而后将数据块按顺序连接,复原成原始数据,最后将数据交给应用程序。具体来说,协议栈会将接收方的数据复制到应用程序指定的内存地址中,而后将控制流程交给应用程序,同时,协议栈还要找到合适时机告诉发送方升级窗口大小。

接收完成与服务器断开

应用程序接收数据,其判断数据被一律接收完成,则这个时间就是收发数据结束的时间。协议栈在设计上允许通信双方的任意一方先发起断开过程。大部分程序向服务器发送请求消息,服务器再返回响应消息,这时收发数据的过程就一律结束了,服务器一方会先发起断开过程。也有少量程序是发完数据就先发起断开过程。

协议栈在设计上允许通信双方的任意一方先发起断开过程,具体哪方先断开,由那方的程序决定。

我们以常见的服务器断开讲解。首先,服务器一方的程序会调用Socket库的 close 程序。而后,服务器的协议栈会生成包含断开信息的 TCP 头部,具体来说就是将控制位的 FIN 比特设为1。接下来,协议栈会委托IP板块向用户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息。

用户端收到服务器发来的 FIN 为 1 的TCP头部时(①),用户端协议栈会将自己的套接字标记进入断开操作状态。而后,为了告知服务器已经收到 FIN 的包,用户端会向服务器返回一个 ACK 号(②)。这些操作完成后,就等待应用程序来取数据了。

过了一会,应用程序就回来调用 read 来读取数据。这时,协议栈不会向应用程序传递数据,而是会告知应用程序来自服务器的数据已经一律收到,用户端收到一律数据,也会调用 close 结束数据收发操作,这时用户端的协议栈也会和服务器一样,生成一个FIN比特为1的TCP包,而后委托IP板块发送给服务器(③)。隔一段时间,服务器就会返回ACK号(④)。到此,用户端和服务器的通信一律结束。

删除连接管道

有没有记到前面说过,通信双方在连接阶段中间相似有一条管道,准备连接时,我们建立,现在收发数据结束,我们理应要删除它,其实也就是删除这条虚拟管道的两方套接字。

通信结束之后,我们要删除套接字,不过,套接字不会立即被删除,而是会等待一段时间之后再被删除。等待一段时间是为了防止误操作,引起误操作的起因很多,比方说:
1、用户端发送FIN
2、服务器返回ACK号
3、服务器发送FIN
4、用户端发送ACK号

假如最后用户端返回的ACK号丢失了,服务器没有接受到ACK号,它可能会重新发送一次FIN。假如这个时候,用户端的套接字已经删除,那么套接字中保存的开工至信息也跟着消失,套接字对应的端口号就会被释放出来。这时,假如别的应用程序创立套接字,新套接字恰好被分配了同一个端口号,而服务器重发的FIN正好到达,这个时候,FIN就会错误的跑到新套接字里面,新套接字就开始执行断开操作了。所以不马上删除套接字,就是因为这样。

用户端的端口号是从空闲的端口号中随便选择的。

等待多长时间才删除套接字,这得看包重传的操作方式。网络包丢失之后会进行重传,这操作一般要持续几分钟。假如重传了几分钟之后仍然无效,则中止重传。所以一般等待几分钟之后再删除套接字。

总结

TCP收发数据的整体流程分为以下三个部分。

收发数据三个步骤开始前的操作是创立套接字,应用程序调用Socket库的一个程序组件socket程序申请创立套接字,之后协议栈去执行操作。

一、连接操作。创立完套接字,就准备连接通信对象。首先,用户端会生成一个SYN为1的TCP包并发给服务器。这个TCP包的头部包含了用户端向服务器发送数据时使用的seq(初始序号),以及服务器发送数据给用户端需要用到的窗口大小。这个包到达服务器后,服务器会返回一个SYN为1的TCP包,这个TCP包同样包含着序号和窗口大小,此外还包含表示已经收到用户端发来的TCP包的ACK号。过段时间,用户端会返回ACK号,表示已经收到服务器发送的TCP包。

二、收发操作。不同应用程序可能会有些异同。一般。用户端会向服务器发送请求消息。TCP会将数据拆分成很多个网络包分别发送出去。每个包的TCP头部都包含这序号,表示当前发送的是第几个字节数据。服务器收到包后,会返回ACK号,肯定时间后也会返回升级窗口大小的包。当然通信是双向的,服务器也会向用户端发送数据,也是相似的流程。

三、断开操作。一般,服务器会先发起断开过程。服务器先发一个 FIN 为1的TCP包给用户端,用户端返回 ACK号作为确认收到。用户端收到一律数据,也会生成一个 FIN 比特为1的TCP包,发送给服务器,服务器也返回ACK号,等待一段时间后,套接字会被删除。到此,用户端和服务器的通信一律结束。




参考文献:
TCP/IP协议族
网络是怎么连接的

欢迎关注技术公众号「程序员大咖秀」

免责声明:本文为用户发表,不代表网站立场,仅供参考,不构成引导等用途。 系统环境 服务器应用
相关推荐
超详细的8 种 NoSQL 数据库系统比照
斗战胜佛22乾坤袋收宝塔
Java程序员从0开始学架构
linux安装flume和集成kafka测试
【重学数据结构与算法(JS)】字符串匹配算法(四)——Sunday算法
首页
搜索
订单
购物车
我的