lel

V1

2022/12/27阅读:30主题:全栈蓝

网络是怎样连接的?(二)

前言

笔记补充上篇 正常的查询DNS流程是没问题的,但是实际上客户端、服务端的每一层的动作都会有记忆来加快客户端查询DNS这一过程

DNS缓存

浏览器缓存

当通过浏览器访问某域名时,浏览器首先会在自己的缓存中查找是否有该域名对应的IP地址(若曾经访问过该域名且没有清空缓存,便存在)

系统缓存

当浏览器缓存中无域名对应IP则会自动检查用户计算机系统Hosts文件DNS缓存是否有该域名对应IP

路由器缓存

当浏览器及系统缓存中均无域名对应IP则进入路由器缓存中检查,以上三步均为客户端的DNS缓存

ISP(互联网服务提供商)DNS缓存

当在客户端查找不到域名对应IP地址,则将进入ISP DNS缓存中进行查询

根域名服务器

当以上均未成功查询域名对应IP,则进入根服务器进行查询。根域名收到请求后会查看其区域文件记录,若查询结果为无,则将其管辖范围内顶级域名,服务器IP告诉本地DNS服务器

顶级域名服务器

顶级域名服务器收到请求后查看其区域文件记录,若无则将其管辖范围内主域名服务器的IP地址告诉本地DNS服务器

主域名服务器

主域名服务器接受到请求后查询自己的缓存,如果没有则进入下一级域名服务器进行查找,并重复该步骤直至找到正确纪录

保存结果至缓存

本地域名服务器把返回的结果保存到缓存,以备下一次使用,同时将该结果反馈给客户端 (并倒序进行缓存的记录)

正文开始

浏览器、客户端通过DNS拿到实际IP后开始挠头,我光知道月球的实际位置,但是咋走啊? 咋交互啊?

本篇重点放到操作系统中的协议栈是如何处理数据发送请求的

对于上一篇提到的Socket库协议栈的关系我理解就是 Socket库是应用程序与协议栈沟通的桥梁。

重点项

  1. 协议栈内部
  2. 创建套接字
  3. 连接服务器
  4. 收发数据
  5. 从服务器断开连接并删除套接字

协议栈内部

协议栈 (英语:Protocol stack),又称协议堆叠,是计算机网络协议套件的一个具体的软件实现。

协议栈是指网络中各层协议的总和,其形象的反映了一个网络中文件传输的过程,由上层协议底层协议,再由底层协议上层协议

这张图中的上下关系是有一定规则的,上面的部分会向下面的部分委派工作,下面的部分接受委派的工作并实际执行(简单参考即可 其中也有一部分上下关系不明确,或者上下关系相反的情况)

像浏览器或者一般的应用程序都是使用TCP收发数据的,而像DNS查询等收发较短的控制数据的时候则使用UDP

图的下半部分是用IP协议控制网络包收发操作的部分。在互联网上传送数据时,数据会被切分成一个一个的网络包,而将网络包发送给通信对象的操作就是由IP来负责的。IP中还包括ICMP协议ARP协议。ICMP用于告知网络包传送过程中产生的错误以及各种控制消息,ARP用于根据IP地址查询相应的以太网MAC地址

IP下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收的操作。

创建套接字

啥是套接字?为啥要创建ta?

协议栈内部有一块用于存放控制信息内存空间,这里记录了用于控制通信操作的控制信息,

控制信息什么?

例如通信对象的IP地址、端口号、通信操作的进行状态,还有一些TCP协议的固定字段

这些信息会被添加在客户端与服务器之间传递的网络包的开头,因此被称为头部。也可以称为包头(就是内蒙古下的包头

以太网和IP协议也有自己的控制信息,这些信息也叫头部,为了避免各种不同的头部发生混淆,我们一般会记作TCP头部、以太网头部、IP头部

控制信息先到这里继续聊套接字

本来套接字就只是一个概念而已,并不存在实体,如果一定要赋予它一个实体,我们可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体。

OK知道了啥是套接字,为啥要搞这个?做啥用的?

有啥用?

协议栈在执行操作时需要参考这些控制信息

比如在发送数据时,需要看一看套接字中的通信对象IP地址和端口号,以便向指定的IP地址和端口发送数据。在发送数据之后,协议栈需要等待对方返回收到数据的响应信息,但数据也可能在中途丢失或者永远也等不到对方的响应。在这样的情况下,我们不能一直等下去,需要在等待一定时间之后重新发送丢失的数据,这就需要协议栈能够知道执行发送数据操作后过了多长时间。套接字中必须记录是否已经收到响应,以及发送数据后经过了多长时间,才能根据这些信息按照需要执行重发操作。

上面说的只是其中一个例子。套接字中记录了用于控制通信操作的各种控制信息,协议栈则需要根据这些信息判断下一步的行动(该重发、还是该断开、还是该继续发?还是该断开连接等等),这就是套接字的作用。

说人话就是 套接字就是一个工作手册、日程表、备忘录一样的东西,告诉协议栈这个老实孩子下一步你该干啥。

通信操作中使用的控制信息分为两类

  1. 头部中记录的信息
  2. 套接字(协议栈中的内存空间)中记录的信息

怎么看套接字?

Windows中可以用netstat命令显示套接字内容

图中每一行相当于一个套接字,当创建套接字时,就会在这里增加一行新的控制信息,赋予即将开始通信的状态并进行通信的准备工作,如分配用于临时存放收发数据的缓冲区空间。 就等于我去你家住宿你不得提前 收拾收拾准备一个猪圈来给我??

假如说本地IP地址和外部IP地址都是0.0.0.0,这表示通信还没开始,IP地址不确定

其中状态这一列的含义为(挺重要的) 具体用的场景我经历过的就是 服务器碰到超多连接的时候或者CUP卡顿、或者没内存了。可以看这个图分析状态来判断到底卡那里了?是真的客户端太多 ?还是保持没必要的连接时间太长了,导致没有释放导致占用内存空间?

题外话

一个计算机到底能创建多少连接?

都知道一台计算机的端口号总共有65535个所以大部分人都以为只能创建6W+

其实不是的

一个完整的TCP连接由四个部分组成:源IP 源端口 目的IP 目的端口 这就是经典的socket四元组

建立一个TCP连接,需要将两端的套接字进行绑定:源IP地址:源端口号 <---->目标IP地址:目标端口号,只要确保绑定的套接字不重复,即可完成一个tcp的连接,由此可见如果端口号不够用了,就不断变换目标IP地址和目标端口号,保证四元组不重复,就能创建很多个TCP的连接

其实到底能创建多少个连接取决于上图的限制条件 有可能6W+都创建不了、也有可能大于6W+

开始创建套接字

创建套接字时,首先分配一个套接字所需的内存空间,然后向其中写入初始状态,然后将套接字的描述符告知应用程序

应用程序在向协议栈进行收发数据委托时就需要提供这个描述符。由于套接字中记录了通信双方的信息以及通信处于怎样的状态,所以只要通过描述符确定了相应的套接字,协议栈就能够获取所有的相关信息,这样一来,应用程序就不需要每次都告诉协议栈应该和谁进行通信了。

说人话就是 我(协议栈)创建了一个类(套接字)给字段ID赋值了一个随机数(描述符)之后给到应用程序,告诉他,你以后跟我交流咱俩就就传这个ID我就知道你想跟谁聊天了

我(协议栈)初始化好了,给了你一个ID(描述符)OK 你现在想传数据了?放屁能?我(协议栈)啥也不知道跟谁聊天啊??我就知道一个ID,这时候应用程序一想也对啊我得跟你说这个人在哪里 这一步就是连接connect,将服务器的IP地址和端口号等信息告知协议栈,这是连接操作的目的之一

假如服务器是个女生 客户端是男生

服务器(女生)一般会在系统启动时就创建套接字(初始化部分)并等待客户端(男生)连接,关于服务器的工作原理 先挖个坑吧~

服务器(女生)的协议栈和客户端(男生)是一样的,只创建套接字是不知道应该和谁进行通信的。而且,和客户端(男生)不同的是,服务器(女生)并不知道在生命(生命周期)当中会遇到哪个客户端(男生)

这个时候懂的都懂 常规都是男生要主动~

我想和你开始通信,我的IP地址是xxx.xxx. xxx.xxx,端口号是yyyy

咳咳~~ 这时候女生的常见操作是什么能?

但是客户端是霸道总裁 服务端必须得听

连接操作

  1. 在TCP模块处创建表示连接控制信息的头部,通过TCP头部中的发送方和接收方端口号可以找到要连接的套接字。
  2. TCP模块会将信息传递给IP模块委托它进行发送
  3. IP模块执行网络包发送操作后,网络包就会通过网络到达服务器,然后服务器上的IP模块会将接收到的数据传递给TCP模块,服务器的TCP模块根据TCP头部中的信息找到端口号对应的套接字,套接字中会写入相应的信息,并将状态改为正在连接
  4. 上述操作完成后,服务器的TCP模块会返回响应这个过程和客户端一样

这就是三次握手的过程了~ 后续有可能会单独写一篇这个

只要数据传输过程在持续,也就是在调用close断开之前,连接是一直存在的。建立连接之后,协议栈的连接操作就结束了,也就是说connect已经执行完毕,控制流程被交回到应用程序。

收发操作

发送操作

收发说白了就是Send("在吗?") 信息

因为连接完成后控制流程被交回到应用程序,应用程序Send 一下,发给协议栈小弟一个跑腿任务,协议栈小弟并不是一收到数据就马上发送出去,而是会将数据存放在内部的发送缓冲区中。小弟还是聪明的不想多跑

比如:

男生:在吗?

女生:在

男生:帮我买个饭

女生:什么饭?

.....

协议栈心里想,我等男生说完要干啥我再去 ,先放到缓冲区

具体协议栈会根据两个东西来判断是否要跑一趟

  1. MTU的参数 MTU表示一个网络包的最大长度,在以太网中一般是1500字节并且减去头部的长度叫做MSS 后判断 要超过了,赶紧跑一趟。

  2. 计时器 每隔一段时间跑一趟通常是毫秒为单位的

如果男生一口气5000字检讨发送过去,协议栈小弟懵逼了,太大了就会将缓冲区中的数据会被以MSS长度为单位进行拆分,每块数据会被放进单独的网络包中,就在每一块数据前面加上TCP头部,并根据套接字中记录的控制信息标记发送方和接收方的端口号,然后交给IP模块来执行发送数据的操作

双向通信也是类似刚才聊的单项通信只不过反过来了

结论是 通过“序号”和“ACK号”可以确认接收方是否收到了网络包。

TCP采用这样的方式确认对方是否收到了数据,在得到对方确认之前,发送过的包都会保存在发送缓冲区中。如果对方没有返回某些包对应的ACK号,那么就重新发送这些包。这一机制非常强大。通过这一机制,我们可以确认接收方有没有收到某个包,如果没有收到则重新发送,这样一来,无论网络中发生任何错误,我们都可以发现并采取补救措施(重传网络包)。

反过来说,有了这一机制,我们就不需要在其他地方对错误进行补救了。因此,网卡、集线器、路由器都没有错误补偿机制,一旦检测到错误就直接丢弃相应的包。应用程序也是一样,因为采用TCP传输,即便发生一些错误对方最终也能够收到正确的数据,所以应用程序只管自顾自地发送这些数据就好了。不过,如果发生网络中断、服务器宕机等问题,那么无论TCP怎样重传都不管用。这种情况下,无论如何尝试都是徒劳,因此TCP会在尝试几次重传无效之后强制结束通信,并向应用程序报错。

网络的错误检测和补偿机制

实际上网络的错误检测和补偿机制非常复杂。说几个点,首先是返回ACK号的等待时间(这个等待时间叫超时时间)

  1. 当网络传输繁忙时就会发生拥塞,半天ACK不来 我是等不等能? 等多长时间能?

TCP采用了动态调整等待时间的方法,这个等待时间是根据ACK号返回所需的时间来判断的。具体来说,TCP会在发送数据的过程中持续测量ACK号的返回时间,如果ACK号返回变慢,则相应延长等待时间;相对地,如果ACK号马上就能返回,则相应缩短等待时间 基本上0.5s-1s 之间

  1. 每发送一个包就等待一个ACK号 我干等太浪费了吧?我可以做点别的吧?我异步啊,我继续发给你,后续你慢慢回我,哪个没回我我就重发一下。

这就是大名鼎鼎的滑动窗口

这时候服务端不乐意了 我空间是无限的啊??你就这么传?这样吧折个中,我告诉你我还剩多少空间,我空间不够了你别传了

终于终于完事了发送就讲到这里哈,我上个厕所去~。

接收

Receive()

浏览器或者应用程序在委托协议栈发送请求消息之后,马上就问协议栈小弟说,服务器回复我了吗?(✧◡✧) , 然后协议栈会执行接下来的操作。和发送数据一样,接收数据也需要将数据暂存到接收缓冲区中。如果啥也木有协议栈就尬住了(挂起),也就是从接收缓冲区中取出数据并传递给应用程序的工作暂时挂起,等服务器返回的响应消息到达之后再继续执行接收操作。

协议栈接收数据的具体操作过程已经在发送数据的部分聊过了,可以简单的回顾一下

  1. 协议栈会检查收到的数据块和TCP头部的内容,判断是否有数据丢失,如果没有问题则返回ACK号
  2. 协议栈将数据块暂存到接收缓冲区中,并将数据块按顺序连接起来还原出原始的数据,最后将数据交给应用程序。具体来说,协议栈会将接收到的数据复制到应用程序指定的内存地址中,然后将控制流程交回应用程序。将数据交给应用程序之后,协议栈还需要找到合适的时机向发送方发送窗口更新(我收到啦~)

从服务器断开连接并删除套接字

收发数据结束的时间点应该是应用程序判断所有数据都已经发送完毕的时候,数据发送完毕的一方会发起断开过程,但不同的应用程序会选择不同的断开时机。

可能也有一些程序是客户端发送完数据就结束了,不用等服务器响应,这时客户端会先发起断开过程。这一判断是应用程序作出的,协议栈在设计上允许任何一方先发起断开过程。(男女生都有权利说分手~~~)

基于国际化的 Ladies first.

假设服务器要断开连接

还是咱们熟悉的库 Socket库 中有一个close程序,一调用。啪的一下子~ 很快啊~

协议栈会生成包含断开信息的TCP头部,具体来说就是将控制位中的FIN比特设为1,接下来,协议栈会委托IP模块向客户端发送数据。同时,服务器的套接字中也会记录下断开操作的相关信息。

客户端当收到服务器发来的FIN为1的TCP头部时

擦干眼泪后,客户端的协议栈会将自己的套接字标记为进入断开操作状态。然后,为了告知服务器已收到FIN为1的包,客户端会向服务器返回一个ACK号

介个过程嘛 就是四次挥手啦~~ (港台语气.mp4)

服务器女生:分手吧!这是通知不是商量
客户端刘波儿:啊~ 这~ 你~ 我~ 唉~  好吧
客户端刘波儿:最后祝你幸福吧!
服务器女生:别说那些废话,赶紧滚,打车滚。

如果最后服务端返回的ACK号丢失了,结果会如何呢?这时,客户端没有接收到ACK号,可能会重发一次FIN。

客户端刘波儿:最后祝你幸福吧! 等了半天没回复, 难道说被我感动了? 我再说一次试试
客户端刘波儿:最后祝你幸福吧!
服务器女生:别说那些废话,赶紧滚。

如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。

一般来说会等待几分钟之后再删除套接字(毕竟还有感情嘛~),那倒不是,主要是怕 新朋友拿到旧朋友的ID ,同时还有 重传、延迟等等机制在这里,万一给新朋友传过去那多不好啊是吧。

这是一个结尾

大象放进冰箱需要几步? (此处宋丹丹配音.mp4)

数据收发总共分为几步?

分类:

后端

标签:

后端

作者介绍

lel
V1