前言:网络知识很是的重要,若是你不是作程序的,那么一些网络常识仍是得知道的;而作程序的,就更不用说了,不只须要了解一些网络知识,仍是知道其原理,若是不了解原理,不敢说他不是程序员,可是总缺了点意思,就像去北京没去过长城同样。html
网络原理系列文章:程序员
1、五分钟了解网络链接(已完成)服务器
2、收发数据的原理(上)(已完成)网络
3、收发数据的原理(下)(已完成)并发
4、收发数据的番外篇(未完成)app
由于网络原理不是三言两语能够讲完,若是读者很忙,能够直接拉到最底下,看总结,知道个大概,再回头细读此文章。感谢关注。废话很少说,直接进入主题。在上篇咱们已经讲了TCP收发数据的前两步,接下来是最后两步。socket
上篇讲到控制流程从 connect 回到应用程序以后,就到了数据收发阶段。 数据收发数据是从应用程序调用write将要发送的数据交给协议栈开始的,协议栈收到数据后执行发送操做,这一操做包含以下要点。post
首先,协议栈并不关心应用程序传来的数据是什么内容。应用程序调用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号中发送给发送方(TCP的seq和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返回这段时间,若是什么都不作,就很是浪费。为了减小浪费,TCP采用滑动窗口管理数据发送和ACK号的操做。所谓滑动窗口,就是在发送一个包,不等待ACK号返回,直接发送后续的一系列包。
可是这样有可能出现如下问题,在不返回ACK号的时候,就连续发送包,可能致使发送包的频率超过接收方处理能力的状况。具体来讲,接收方TCP接收到包,会先将数据存放到接收缓冲区中。而后,接收方须要计算ACK号,将数据块组装起来还原成本来的数据并传递给应用程序,若是该操做未完成,又有下一个包到来,一样是存入接收缓冲区中,若是包到来速率比将数据块组装数据并传给应用程序速率快,缓冲区数据就会越积越多,最后溢出,接收方就收不到后面的包了。因此,接收方须要告诉发送方本身最多能接收多少数据,而后发送方根据这个值对数据发送进行控制,这个最大值称为窗口大小。这就是滑动窗口方式的基本思路。
可以接收的最大数据量称为窗口大小,它属于TCP调优的一个重要参数
前面说过窗口大小就是最大接收量,当接收的数据存入缓冲区中,不必立刻向发送方更新窗口大小,更新窗口大小时机应该是接收方从缓冲区中取出数据传递给应用程序的时候,由于这时,缓冲区中数据减小,剩余的空间变大,理应告诉发送方。
接收方收到数据,确认内容没有问题,就应该向发送方返回ACK号。假设ACK包是一个包,而更新窗口大小又是另一个包,这样可能会收到一个包的状况下,接收方须要向发送方返回两个包。这样一来,接收方发给发送方的包就太多了,致使网络效率降低。
因此,若是在等待发送ACK的时候,恰好也要更新窗口大小,就能够把这两个包合并成一个包发送,从而减小的包的数量。当须要连续发送多个ACK号,也能够减小包的数量,这是由于ACK号表示的是已经收到的数据量,也就是说,它是告诉发送方目前已接收的数据最后位置在哪里,由于当须要连续发送ACK号时,只要发送最后一个ACK号就能够了。同理,当须要连续发送多个窗口更新也能够减小包的数量。
客户端委托协议栈发送请求后,等待服务端返回的消息,调用read程序来获取响应消息。和发送数据同样,接收数据也须要将数据暂存到接收缓冲区中。具体操做以下,协议栈尝试从接收缓冲区取出数据并传递给应用程序,但这个时候可能响应消息还没返回,因此接收操做就无法继续。那么,协议栈会将应用程序的委托,也就是从缓冲区取数据的工做暂时挂起,等响应消息到达再继续接收操做。注意,这里只是挂起这项工做,协议栈并无中止工做,还会处理好多其余的工做。
应用程序在发送数据和接收数据都依赖协议栈。
协议栈接收数据会先将数据放入缓冲区,而后将数据块按顺序链接,还原成原始数据,最后将数据交给应用程序。具体来讲,协议栈会将接收方的数据复制到应用程序指定的内存地址中,而后将控制流程交给应用程序,同时,协议栈还要找到合适时机告诉发送方更新窗口大小。
应用程序接收数据,其判断数据被所有接收完成,则这个时间就是收发数据结束的时间。协议栈在设计上容许通讯双方的任意一方先发起断开过程。大部分程序向服务器发送请求消息,服务器再返回响应消息,这时收发数据的过程就所有结束了,服务器一方会先发起断开过程。也有一些程序是发完数据就先发起断开过程。
协议栈在设计上容许通讯双方的任意一方先发起断开过程,具体哪方先断开,由那方的程序决定。
咱们以常见的服务器断开讲解。首先,服务器一方的程序会调用Socket库的 close 程序。而后,服务器的协议栈会生成包含断开信息的 TCP 头部,具体来讲就是将控制位的 FIN 比特设为1。接下来,协议栈会委托IP模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操做的相关信息。
客户端收到服务器发来的 FIN 为 1 的TCP头部时(①),客户端协议栈会将本身的套接字标记进入断开操做状态。而后,为了告知服务器已经收到 FIN 的包,客户端会向服务器返回一个 ACK 号(②)。这些操做完成后,就等待应用程序来取数据了。
过了一会,应用程序就回来调用 read 来读取数据。这时,协议栈不会向应用程序传递数据,而是会告知应用程序来自服务器的数据已经所有收到,客户端收到所有数据,也会调用 close 结束数据收发操做,这时客户端的协议栈也会和服务器同样,生成一个FIN比特为1的TCP包,而后委托IP模块发送给服务器(③)。隔一段时间,服务器就会返回ACK号(④)。到此,客户端和服务器的通讯所有结束。
有没有记到前面说过,通讯双方在链接阶段中间相似有一条管道,准备链接时,咱们创建,如今收发数据结束,咱们理应要删除它,其实也就是删除这条虚拟管道的两方套接字。
通讯结束以后,咱们要删除套接字,不过,套接字不会当即被删除,而是会等待一段时间以后再被删除。等待一段时间是为了防止误操做,引发误操做的缘由不少,好比说: 一、客户端发送FIN 二、服务器返回ACK号 三、服务器发送FIN 四、客户端发送ACK号
若是最后客户端返回的ACK号丢失了,服务器没有接受到ACK号,它可能会从新发送一次FIN。若是这个时候,客户端的套接字已经删除,那么套接字中保存的开工至信息也跟着消失,套接字对应的端口号就会被释放出来。这时,若是别的应用程序建立套接字,新套接字恰好被分配了同一个端口号,而服务器重发的FIN正好到达,这个时候,FIN就会错误的跑到新套接字里面,新套接字就开始执行断开操做了。因此不立刻删除套接字,就是因为这样。
客户端的端口号是从空闲的端口号中随意选择的。
等待多长时间才删除套接字,这得看包重传的操做方式。网络包丢失以后会进行重传,这操做通常要持续几分钟。若是重传了几分钟以后依然无效,则中止重传。因此通常等待几分钟以后再删除套接字。
TCP收发数据的总体流程分为如下三个部分。
收发数据三个步骤开始前的操做是建立套接字,应用程序调用Socket库的一个程序组件socket程序申请建立套接字,以后协议栈去执行操做。
1、链接操做。建立完套接字,就准备链接通讯对象。首先,客户端会生成一个SYN为1的TCP包并发给服务器。这个TCP包的头部包含了客户端向服务器发送数据时使用的seq(初始序号),以及服务器发送数据给客户端须要用到的窗口大小。这个包到达服务器后,服务器会返回一个SYN为1的TCP包,这个TCP包一样包含着序号和窗口大小,此外还包含表示已经收到客户端发来的TCP包的ACK号。过段时间,客户端会返回ACK号,表示已经收到服务器发送的TCP包。
2、收发操做。不一样应用程序可能会有些异同。通常。客户端会向服务器发送请求消息。TCP会将数据拆分红不少个网络包分别发送出去。每一个包的TCP头部都包含这序号,表示当前发送的是第几个字节数据。服务器收到包后,会返回ACK号,必定时间后也会返回更新窗口大小的包。固然通讯是双向的,服务器也会向客户端发送数据,也是相似的流程。
3、断开操做。通常,服务器会先发起断开过程。服务器先发一个 FIN 为1的TCP包给客户端,客户端返回 ACK号做为确认收到。客户端收到所有数据,也会生成一个 FIN 比特为1的TCP包,发送给服务器,服务器也返回ACK号,等待一段时间后,套接字会被删除。到此,客户端和服务器的通讯所有结束。
参考文献:
网络是怎样链接的
欢迎关注技术公众号「程序员大咖秀」