网络是怎么连接笔记(二)用电信号传输TCP/IP数据

介绍

在了解了HTTP请求是如何产生的,以及网址是如何跳转到我们想要看到的页面,我们需要了解协议栈内部的细节
在这里插入图片描述

创建套接字

  • 我们将探索操作系统中的网络控制软件(协议栈)和网络硬件(网卡)是如何将浏览器的消息发送给服务器的?
  • 首先跟浏览器不一样的是,协议栈是操作系统内部的东西,所以我们用户是完全体验不到的,所以难以想象他是怎么工作的,所以下图介绍
    在这里插入图片描述
    • 整体的流程大概是这样的
      • 上面的部分是网络应用程序,也就是浏览器、电子邮件客户端、Web服务器、电子邮件服务器等程序,它们会将收发数据等工作委派给下层的部分来完成。
      • 应用程序的下面是 Socket 库,其中包括解析器,解析器用来向 DNS 服务器发出查询
      • 再下面就是操作系统内部了,其中包括协议栈。协议栈的上半部分有两块,分别是负责用 TCP 协议收发数据的部分和负责用 UDP 协议收发数据的部分,它们会接受应用程序的委托执行收发数据的操作。
      • 下面一半是用 IP 协议控制网络包收发操作的部分。在互联网上传送数据时,数据会被切分成一个一个的网络包 ,而将网络包发送给通信对象的操作就是由 IP 来负责的。此外,IP 中还包括 ICMP 协议和 ARP 协议。 ICMP 用于告知网络包传送过程中产生的错误以及各种控制消息,ARP 用于根据 IP 地址查询相应的以太网 MAC 地址 。
      • IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收的操作。
  • 我们已经具体了解了协议栈是怎么个工作方式,下面了解一下套接字的详细信息
    • 协议栈内部存放一块用户控制信息的内存空间,这里用于记录通信操作的一些控制信息,主要为下面几个部分:
      • 协议类型
      • IP地址和端口号
      • 通信对象的IP地址和端口号
      • 通信状态
      • 套接字的标识符
        在这里插入图片描述
  • 了解了套接字的具体样子以后,我们需要看看客户端调用Socket组件,协议栈是如何工作的?
    在这里插入图片描述

连接服务器

  • 创建完套接字之后,就会调用connet,然后协议栈就会通过本地的套接字跟远端的套接字进行连接,连接实际上是通信双方交换控制信息,在套接字中记录这些必要信息并准备数据收发的一连串操作
  • 连接操作的目的:套接字刚刚创建完成的时候,里面并没有存放任何数据,也不知道通信的对象是谁。在这个状态下,即便应用程序要求发送数据,协议栈也不知道数据应该发送给谁。浏览器可以根据网址来查询服务器的 IP 地址,而且根据规则也知道应该使用 80 号端口,但只有浏览器知道这些必要的信息是不够的,因为在调用 socket创建套接字时,这些信息并没有传递给协议栈。因此,我们需要把服务器的 IP 地址和端口号等信息告知协议栈
  • 当执行数据收发操作时,我们还需要一块用来临时存放要收发的数据的内存空间,这块内存空间称为缓冲区
  • 控制信息分为两类:
    • 一类 头部中记录的信息 :是客户端和服务器相互联络时交换的控制信息,这些信息不仅连接时需要,包括数据收发和断开连接操作在内,整个通信过程中都需要,这些内容在 TCP 协议的规格中进行了定义
      在这里插入图片描述
      在这里插入图片描述
    • 另一类 套接字(协议栈中的内存空间)中记录的信息 :是保存在套接字中,用来控制协议栈操作的信息,应用程序传递来的信息以及从通信对象接收到的信息都会保存在这里,还有收发数据操作的执行状态等信息也会保存在这里,协议栈会根据这些信息来执行每一步的操作
  • 连接的实际过程:
    • 将服务器IP和端口号,传给 客户端协议栈 的TCP模块
    • 客户端TCP模块 创建头部信息,然后传给 客户端IP模块
    • 客户端IP模块 执行网络包(控制位SYN设置为1)进行发送
    • 服务器IP模块 接收到网络包,并将数据传输给 服务器TCP模块
    • 服务器TCP模块 从头部信息找到对应的端口号,并根据端口号找到对应的套接字,修改套接字的状态
    • 服务端TC模块 返回响应(控制位SYN设置为1和ACK设置为1)给 服务端IP模块,并委托进行发送
    • 客户端IP模块 收到响应,并把数据传输到 客户端TCP模块
    • 客户端TCP模块确认信息,是否连接成功
    • 如果连接成功(SYN设置为1),客户端IP模块 会向套接字输入对应服务器的IP地址等信息,还将状态改为连接状态
    • 客户端IP模块 返回响应(ACK设置为1)给服务端IP模块
    • 服务端IP模块收到响应
    • 数据传输

收发数据

  • 控制流程从connect 回到应用程序之后,接下来就进入数据收发阶段。
  • 协议栈收到数据,发送数据
    • 协议栈不关系应用程序传来的数据是什么内容,应用程序在调用write时会指定发送数据的长度
    • 协议栈并不是已收到数据就马上发送出去,会将数据存放到内部的发送缓冲区中,并等待应用程序的下一段数据,达到一定的数据量才会发送数据
      • 如何界定需要多少数据量才会发送呢?
        • 其中一个要素:每个网络包能容纳的数据长度,协议栈会根据一个叫做MTU的参数来进行判断,MTU表示一个网络包的最大长度在以太网中一般是 1500 字节 。MTU 是包含头部的总长度,因此需要从 MTU 减去头部的长度,然后得到的长度就是一个网络包中所能容纳的最大数据长度,这一长度叫作MSS 。当从应用程序收到的数据长度超过或者接近 MSS 时再发送出去,就可以避免发送大量小包的问题了。
          在这里插入图片描述
        • 另一个要素:时间,当应用程序发送数据的频率不高的时候,如果每次都等到长度接近 MSS 时再发送,可能会因为等待时间太长而造成发送延迟,这种情况下,即便缓冲区中的数据长度没有达到 MSS,也应该果断发送出去。为此,协议栈的内部有一个计时器,当经过一定时间之后,就会把网络包发送出去 32。
      • HTTP 请求消息一般不会很长,一个网络包就能装得下,但如果其中要提交表单数据,长度就可能超过一个网络包所能容纳的数据量,比如在博客或者论坛上发表一篇长文就属于这种情况。如何解决?
        • 这个时候就需要对数据进行拆分,拆分出来的每块数据会被放进单独的网络包中
          在这里插入图片描述
  • 现在协议栈已经装好数据准备发往服务器了,但是发送数据,还需要确认对方有没有收到数据,并且收到数据有没有正确,不然的话就没有意义了,所以收发数据还有一个很重要的操作,就是 确认操作。
    • TCP模块 再拆分数据时,会算好每一块数据在第几个字节,并且会在发送的时候将第几个字节卸载TCP头部发送出去,这就是序号这个字段的作用,并且这个数据的长度也会告知对方,所以接收方可以通过这个来判断数据是否异常和组装数据。
      • 假设上次接收到第 1460 字节,那么接下来如果收到序号为 1461 的包,说明中间没有遗漏;但如果收到的包序号为 2921,那就说明中间有包遗漏了。
    • 接收方收到数据后,还需要计算收到了多少字节的数据,并且将这个数据放进TCP头部的ACK发送给发送方。
      • 发送方说的是“现在发送的是从第 ×× 字节开始的部分,一共有 ×× 字节哦!”而接收方则回复说,“到第 ×× 字节之前的数据我已经都收到了哦!”这个返回 ACK 号的操作被称为确认响应,通过这样的方式,发送方就能够确认对方到底收到了多少数据。
    • ps:在实际的通信中,序号并不是从 1 开始的,而是需要用随机数计算出一个初始值,这是因为如果序号都从 1 开始,通信过程就会非常容易预测,有人会利用这一点来发动攻击。但是如果初始值是随机的,那么对方就搞不清楚序号到底是从多少开始计算的,因此需要在开始收发数据之前将初始值告知通信对象。
      在这里插入图片描述
    • 之前都只是单向传输,但实际上服务端跟客户端应该是双向传输,不仅是服务端会发送数据,客户端也会发送数据过来。双方都需要计算ACK号和序号
      • 例子:首先客户端先计算出一个序号,然后将序号和数据一起发送给服务器,服务器收到之后会计算 ACK 号并返回给客户端;相反地,服务器也需要先计算出另一个序号,然后将序号和数据一起发送给客户端,客户端收到之后计算 ACK 号并返回给服务器。此外,如图所示,客户端和服务器双方都需要各自计算序号,因此双方需要在连接过程中互相告知自己计算的序号初始值。
        在这里插入图片描述
      • 具体流程:
        • 客户端在连接时需要计算出与从客户端到服务器方向通信相关的序号初始值,并将这个值发送给服务器
        • 服务器会通过这个初始值计算出 ACK 号并返回给客户端。初始值有可能在通信过程中丢失,因此当服务器收到初始值后需要返回 ACK 号作为确认。
        • 服务器也需要计算出与从服务器到客户端方向通信相关的序号初始值,并将这个值发送给客户端。
        • 接下来像刚才一样,客户端也需要根据服务器发来的初始值计算出 ACK 号并返回给服务器。
        • 到这里,序号和 ACK 号都已经准备完成了,接下来就可以进入数据收发阶段了。数据收发操作本身是可以双向同时进行的,但 Web 中是先由客户端向服务器发送请求,序号也会跟随数据一起发送。
        • 然后,服务器收到数据后再返回 ACK 号。从服务器向客户端发送数据的过程则正好相反。
          在这里插入图片描述
  • 实际情况下,还会出现很多问题,所以网络的错误检测和补偿机制还有几点:
    • 首先是返回 ACK 号的等待时间(这个等待时间叫超时时间)当网络传输繁忙时就会发生拥塞,ACK 号的返回会变慢,这时我们就必须将等待时间设置得稍微长一点,否则可能会发生已经重传了包之后,前面的 ACK 号才姗姗来迟的情况。
      • 所以将等待时间设置为一个固定值并不是一个好办法。因此,TCP 采用了动态调整等待时间的方法,这个等待时间是根据 ACK 号返回所需的时间来判断的。具体来说,TCP 会在发送数据的过程中持续测量 ACK 号的返回时间,如果 ACK 号返回变慢,则相应延长等待时间;相对地,如果 ACK 号马上就能返回,则相应缩短等待时间
      • 每发送一个包就等待一个 ACK 号的方式是最简单也最容易理解的,但在等待 ACK 号的这段时间中,如果什么都不做那实在太浪费了。所以产生了滑动窗口,就是发送一个包之后,不等待ACK号返回,而是直接发送后续的一系列包
        在这里插入图片描述
        • 先说这个操作的问题,当接收方的 TCP 收到包后,会先将数据存放到接收缓冲区中。然后,接收方需要计算 ACK 号,将数据块组装起来还原成原本的数据并传递给应用程序,如果这些操作还没完成下一个包就到了也不用担心,因为下一个包也会被暂存在接收缓冲区中。如果数据到达的速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中的数据就会越堆越多,最后就会溢出。缓冲区溢出之后,后面的数据就进不来了,因此接收方就收不到后面的包了,这就和中途出错的结果是一样的,也就意味着超出了接收方处理能力。
        • 知道问题所在之后,我们就需要措施去避免,首先,接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制,这就是滑动窗口方式的基本思路。
          在这里插入图片描述
    • 知道了滑动窗口之后,还有一个问题,那就是返回 ACK 号和更新窗口的时机。如果假定这两个参数是相互独立的,分别用两个单独的包来发送,结果会如何呢?
      • 其实没必要每次都向发送方更新窗口大小,因为只要发送方在每次发送数据时减掉已发送的数据长度就可以自行计算出当前窗口的剩余长度。因此,更新窗口大小的时机应该是接收方从缓冲区中取出数据传递给应用程序的时候。这个操作是接收方应用程序发出请求时才会进行的,而发送方不知道什么时候会进行这样的操作,因此当接收方将数据传递给应用程序,导致接收缓冲区剩余容量增加时,就需要告知发送方,这就是更新窗口大小的时机。
      • 那么 ACK 号又是什么情况呢?当接收方收到数据时,如果确认内容没有问题,就应该向发送方返回 ACK号,因此我们可以认为收到数据之后马上就应该进行这一操作。
  • 发送 HTTP 请求消息后,接下来还需要等待 Web 服务器返回响应消息。对于响应消息,浏览器需要进行接收操作,这一操作也需要协议栈的参与。

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

  • 当数据的收发接近尾声,协议栈也需要一些收尾操作。,收发数据结束的时间点应该是应用程序判断所有数据都已经发送完毕的时候。这时,数据发送完毕的一方会发起断开过程,但不同的应用程序会选择不同的断开时机。
    • 例子:服务端一方发起断开过程为例子
      • 服务器会调用Socket库中的close程序,服务器协议栈会生成包含断开信息TCP头部信息,然后将FIN置为1
      • 服务器协议栈会委托IP模块给客户端发送数据
      • 客户端收到FIN为1 的 TCP头部后,客户端会将自己的套接字置为断开的状态,然后为了告诉服务器也发送了一个FIN为1的TCP头部
      • 服务器接收到客户端的FIN为1 的 TCP头部,也返回一个ACK号
      • 客户端收到后,就开始通知应用程序来读数据了,调用 close 来结束数据收发操作
        在这里插入图片描述
  • 和服务器的通信结束之后,用来通信的套接字也就不会再使用了,这时我们就可以删除这个套接字了。不过,套接字并不会立即被删除,而是会等待一段时间之后再被删除。
    • 原因是因为如果最后客户端返回的 ACK 号丢失了,结果会如何呢?这时,服务器没有接收到 ACK 号,可能会重发一次 FIN。如果这时客户端的套接字已经删除了,会发生什么事呢?套接字被删除,那么套接字中保存的控制信息也就跟着消失了,套接字对应的端口号就会被释放出来。这时,如果别的应用程序要创建套接字,新套接字碰巧又被分配了同一个端口号 ,而服务器重发的 FIN 正好到达,会怎么样呢?本来这个 FIN 是要发给刚刚删除的那个套接字的,但新套接字具有相同的端口号,于是这个 FIN 就会错误地跑到新套接字里面,新套接字就开始执行断开操作了。之所以不马上删除套接字,就是为了防止这样的误操作。

IP与以太网的包收发操作(待更新)

UDP协议的收发操作(待更新)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值