计算机网络 From Mr.Liu

引言

本博客摘自Mr.Liu,原帖请点击这里

感谢Mr.Liu,这个文章很充分的描述了计算机网络的核心知识点。

我还在学习中,所以没有进行自己的转述。图片因为是copy代码而没有获得,想看更详尽的,请看原帖。


 

概述

计网到底是个啥

1.定义:计算机网络主要是由一些通用的,可编程的硬件互连而成,通过这些硬件,可以传送不同类型的数据,并且可以支持广泛和日益增大的应用(灵活可拓展性)。

2.分类:计算机网络在我们日常生活当中也是非常容易见到的,比如说我们可以连接WIFI,发个消息,看看手机网站都是依赖于计算机网络,其主要有以下两种分类:

根据使用范围根据使用者
广域网(跨省,跨国) 公用网络
城域网(跨城市)  
局域网(家庭或者公司) 专用网络

既然这里面由广域网,城域网,局域网这三种,那么这三种之间又是如何通信的呢,比如说局域网与局域网,局域网与城域网之间的信息交互,如何实现?先把问题留在这,后面会详细提到。

计网发展简史

欲知其谁,可以了解它的过去。

计算机网络的发展(四个阶段)

  • 第一阶段:面向终端的计算机网络(20世纪50年代到60年代)
    在这个阶段那,主要是以主机为中心,实现对各个终端的通信。终端围绕着主机分散在各处,各个终端通过通信线路共享主机的硬件和软件资源

面向终端的计算机网络

面向终端的计算机网络

这种模式很容易维护,把主机当成爸爸来养好,系统基本就没什么大问题了,但是呢,我们可以发现,以主机为中心,也就是说我干什么都是看主机的眼色(发数据,处理数据等),这样会使得传输的效率受到了限制,因为这全部依赖于主机的性能和可靠性,对主机的依赖性很大,主机一倒,全部都玩完,而且是各个终端连接到主机,这个过程的通信线路开销也比较大,利用率低。

  • 第二阶段:分组交换网,多台计算机互连(二十世纪60年代中期到70年代末)
    在这个阶段,若干个计算机利用通信电路相互连接形成一个系统,实现了计算机与计算机之间的通信。分组交换网又是什么?

分组交换网由通信子网和资源子网组成,以通信子网为中心,不仅共享通信子网的资源,还可共享资源子网的硬件和软件资源。网络的共享采用排队方式,即由结点的分组交换机负责分组的存储转发和路由选择,给两个进行通信的用户段续(或动态)分配传输带宽,这样就可以大大提高通信线路的利用率,非常适合突发式的计算机数据。

分组交换网比较侧重于数据的传送,而不是为了打电话啥的,其能够连接不同类型的计算机,传输数据也十分可靠(这个时期也提出了TCP/IP协议的雏形),但是缺点是没有形成统一的互连标准,使网络在规模与应用等方面受到了限制,因此也不利于普及。

分组交换网

分组交换网
  • 第三阶段:面向标准化的计算机网络(20世纪70年代末到20世纪80年代初)

为了使不同体系结构的计算机网络都能互联,国际标准化组织ISO提出了一个能使各种计算机在世界范围内互联成网的标准框架—开放系统互连基本参考模型OSI.。这样,只要遵循OSI标准,一个系统就可以和位于世界上任何地方的、也遵循同一标准的其他任何系统进行通信。
1984年公布了 ISO7498,即ISO/OSI-RM国际标准,该模型按层次结构划分为七个子层,已被国际社会普遍接受,是目前计算机网络系统结构的基础(后面会讲解)

但是在ARPANET的基础上,形成了以TCP/IP为核心的因特网。任何一台计算机只要遵循TCP/IP协议族标准,并有一个合法的IP地址,就可以接入到Internet。TCP和IP是Internet所采用的协议族中最核心的两个, 分别称为传输控制协议(Transmission Control Protocol, TCP)和互连网协议(Internet Protocol, IP)。

  • 第四阶段:面向全球互连的计算机网络(二十世纪九十年代初至今)

这一时期在计算机通信与网络技术方面以 高速率、高服务质量、高可靠性等为指标, 出现了高速以太网、VPN、无线网络、P2P网络、NGN等技术,计算机网络的发展与应用渗入了人们生活的各个方面,进入一个多层次的发展阶段。
各个国家都建立了自己的高速因特网,这些因特网的互连构成了全球互连的因特网,并且渗透到社会的各个层次。

讲了这么久的历史,博主感到枯燥无味,也讲不下去了,如果大家还想深入了解,可以查询一下互联网的发展简史,可以看看咱们现在使用的互联网是怎么来的。

计网的层次结构

1.层次结构设计的基本原则:

  • 各层之间相互独立:每一层都能够实现相对独立的功能,明确分工。

  • 每一层要有足够的灵活性,可以应对未来的变化:比如说未来我们需要实现什么业务,这个时候可以把业务对应的功能放入到某个层当中,可以改变这些层的内容

比如说,A和B通过一条数据通路相互连接,在这个连接的过程中,我们需要通过层次设计保证这条路要十分的顺畅,要能够识别目的计算机,即这条路到底是通向哪里,我们还可以通过这条路来了解目的计算机的状态是什么,以及这条路的数据会不会出现异常错误等等,通过层次设计我们还可以保证每一层都相对独立地执行本职工作,执行效率高
我们以一个分层设计的模式为例:

分层功能
网络应用数据 实现视频,文件,游戏
数据可靠通信 查询数据错误或者重复
物理网络接入 光电物理特性

等等

层次结构下的两个模型

1.OSI七层模型:

OSI七层模型

OSI七层模型

为了能够让大家看的更明白,博主从网上搬了一些讲的很形象的图片来做示例,加强理解,下面的表格是我的总结.

某一层天生我层必有用
应用层 为计算机用户提供接口和服务,这一层是我们所能够看见的,直接为我们服务
表示层 表示嘛就是表示数据的,这一层进行数据处理,比如说编码解码,加密解密等等
会话层 管理,建立,维护和重连通信会话,服务于通信,管理传输层以下的层
传输层 管理端到端的通信连接
网络层 通过数据路由决定路怎么走,决定它在网络中的路径
数据链路层 管理相邻节点之间的数据通信
物理层 数据通信的光电物理特性

咦,这个时候是不是会感到奇怪,为什么感觉有些层的功能重复了,比如说会话层和应用层明明都是直接为客户服务的呀,咦,既然有这个模型,为什么我们现在的一般听到并且使用的模型却是TCP/IP模型呢,而不是这个说明OSI模型呢?别急,博主细细谈来:
可以说OSI是一个比较理论化的模型,当然,看起来是比较好,但是呢它在实际的市场化过程中却困难重重,最后以至于被TCP/IP模型给替代了。总共有三点致命的原因:

  • OSI模型的设计专家缺乏实际经验,也就是说顾着搞科研而忽略了业务与科研的结合,忽略了科研的落地。

  • OSI标准模型制定的时间比较长,设备无法及时占领市场,其被TCP/IP模型抢先占领了市场。

  • OSI模型设计不是很合理,某些功能在多层中重复出现了。
    因此就成为了TCP/IP模型的世界,那它又是个说明东西呢?看下面!!!

2.TCP/IP四层模型:
这个模型比OSI少了三层,总共有应用层,传输层,网络层以及网络接口层(也可以说是数据链路层)四层,但是每一层的功能都和OSI当中的一层或者几层的功能相似。如下表是它们之间的映射和每一层涉及到的协议,关于这些协议后面博主也会重点讲:

TCP/IP四层模型与OSI

TCP/IP四层模型与OSI
TCP层每一层涉及的协议
应用层 HTTP,FTP协议
传输层 TCP,UDP协议
网络层 IP, ICMP协议,ARP,RARP协议
网络接口层 以太网协议

比如说,A和B之间进行数据交换和通信的时候,这一整个过程就会涉及到这四层,它们之间会有一个”中介”,即路由,这个路由只需要用到网络层和网络接口层的功能。

现代互联网的网络拓扑

1.边缘部分:
以家庭为例,如下图:

家庭网络拓扑

家庭网络拓扑

家庭当中的终端(比如说手机啊,电脑啊等等),它们呢就会通过家里的路由器(WIFI)与外面进行数据通信,家里的路由就会连接我们那个地方,比如说镇上或者县里的网关,然后通过这个网关又和地方的ISP(网络服务提供商,就是我们现在听到的中国移动电信等等,地方ISP就是湖南移动,湖南电信啥的)进行通信,这仅仅只是边缘部分,至于之后会怎么样就是核心部分了,稍后讲。
再来看看企业的例子,如下图:

企业网络拓扑

企业网络拓扑

企业里的网络拓扑就比我们家庭里的要复杂些许,多了一些网关,企业内部的多个终端会和不同的路由进行通信(几个或数十个终端和一个路由通信),然后每一个路由会连接到它们所对应的内部网关,然后这些网关都会汇合到一个统一网关上,再通过这个统一网关和地区ISP进行数据交互和通信。
至于后续如何,看看核心部分:

2.核心部分:这个部分主要是地区的ISP,主干ISP和国际ISP之间的通信了。

核心部分

核心部分

每个地区的ISP都会集中和主干ISP进行数据交互(比如说湖南移动和中国移动总部通信),当然如果说要FQ和外国某个地方进行数据交互的话,那么这个主干ISP就会和其它国家的主干ISP进行通信,前提是它们都会经过国际路由,这个路由有啥用,后面讲?

3.二者结合来看:

互联网

互联网

简单点看就是这样:

树状结构

树状结构

以上就是互联网的网络拓扑,相信大家也对计网有一定的了解了,或许有的读者还会疑惑,博主讲的好像也有点道理,可是我还不不太理解它们到底是什么通信的,地区ISP和地区ISP,终端和路由,它们是如何通信?凭什么就这样进行数据交互了?好,很好,带着问题阅读吧,答案在空中飘荡,飘荡着就在后面博主讲的地方去了。

计网的性能指标

1.速率指标(就是我们所说的网速嘛):速度指标的单位有Bps(1Bps = 1Byte/s),bps(1bps=1bit/s),Mbps(1Mbps=1Mbit/s)
为了能够有更好的理解,我们来看一个问题:比如说我们家如果拉了一个100M的光纤进行使用,可是当我们实际测速的时候,却只有12M/S,是不是这个商家在坑我们啊?不是的,算算就知道了,算完就长见识了。

1
2
3
我们这里所说的100M实际上就是100Mbps,而100Mbps=100Mbit/s=(100/8)MB/s=12.5MB/s
注意:1B=8bit,所以我们测速的时候才是只有12M/s。
我靠,既然是12M/s,为什么拉光纤的时候就不直接说速度12MB/s,而是说100M呢?那是因为100看着就比12要大那么多,很吸引顾客,就好比我们平时看到的房价标注一样首付4万(实际上那个4万比首付两个字大得多,让人们误以为原来这房子只有四万啊,赚了,赶紧买),博主开玩笑的,回归正途,继续讲。

2.时延:时延=发送时延+排队时延+传播时延+处理时延

  • 发送时延=数据长度(bit)/发送速率(bit/s),也就是和网卡性能有关。

  • 传播时延=传播路径距离/传播速率(bit/s),这个主要和传输的介质有关,比如说光纤,铜线等等

  • 排队时延=数据包在网络设备中等待被处理的时间,也就是说从它发送开始到被处理的那一瞬间所花的时间,不包括处理过程

  • 处理时延=数据包到达设备或者目的机器中被处理的时间

3.往返时间RTT:这个也是用来评估网络质量的,指的是数据报文在端到端通信当中来回一次的时间。咱们可以通过在CMD里面输入ping +某个目的设备的IP地址命令来查看这个RTT。
我就输入了: ping 127.0.0.1发一个数据报文给自己的机器看看这个RTT多少,发现为0ms

示例

示例

网络接口层

物理层

1.物理层是干嘛用的?

  • 物理层嘛,一看就是和实际线路相关的,用于连接不同的设备

  • 在物理层中传输比特流,也就是数字信号(0和1高低电平交替表示)

2.传输介质:有双绞线,同轴电缆,光纤,红外线,激光,WIFI等等(是不是觉得双绞线是啥鬼东西,不清楚,不清楚的话请打开百度查询双绞线,博主都把它给讲完了也太没意思了)

3.信道:往一个方向传送信息的载体(一个通信电路包括发送信道和接收信道)
信道总共有三个分类,分别为单工通信信道,半双工通信信道,全双工通信信道。

信道种类信道对应功能
单工通信信道 只能够往一个方向通信,没有反方向的反馈,一去不复返
半双工通信信道 双方都可以发送接收信息,但是双方不能同时发送和接收
全双工通信信道 双方可以同时发送接收信息

3.分用-复用技术:提升信道利用率
分用-复用技术

分用-复用技术


多个机器设备和复用器,分用器连接,分用器和复用器之间通过信道通信,这样就可以大大减少信道的使用量,提供利用效率(稍作了解即可)
一般会使用一下几种技术:

  • 频分复用

  • 时分复用

  • 波分复用

  • 码分复用

数据链路层

1.封装成帧:

  • “帧”是数据链路层数据的基本单位

  • 发送端在网络层的一段数据前后添加特定的标记形成“帧”

  • 接收段根据前后特定的标记识别出“帧”数据
    注意!!!帧数据的首部标记和尾部标记都是特定的控制字符(特定的比特流),比如说首部标记叫做SOH,其特定的比特流为:00000001,而尾部标记为EOT,其特定的比特流为:00000100.
    看到这里,或许有的人会很奇怪,如果说我中间的帧数据当中恰好就出现了SOH或者EOT咋办呢,这样会不会造成数据异常呢,好,对于这个问题,我们看到它的另外一个特点,透明传输:

2.透明传输:如果说控制字符出现在帧数据当中,就要把它当作不存在去处理。
如何去实现不存在的处理?
假如说帧数据当中出现了特定的尾部标记EOT,不进行特殊处理的话,系统会误以为这是尾部标记,其之前的数据有效,后面的就会不管,这个时候得作出处理,我们得在数据中的控制字符前作特殊处理,在这些特殊字符前面加上ESC转义字符,当我们的系统在EOT前面发现了ESC转义字符,就会把它进行透明处理,直接忽视。
当然,有的人还会疑惑,如果中间的数据出现了转义字符咋办,别慌,继续作特殊处理,在转义字符前面加上转义字符即可。

3.差错检测:物理层只管着传输比特流,却无法检查传输数据是否出错,这个时候的工作就交给数据链路层来做了,其负责差错检测。怎么检测,下回分解!!

数据链路层的差错检测

1.奇偶校验码:这个比较是一个比较简单的检测数据差错的方法,咱们来看看一个例子就懂了。
假设传输的数据为:00110010,那么我们就会在实际传输的时候,在后面加上奇偶校验码1,即数据为:001100101(因为前面有奇数个1),若数据为00111010,则加入奇偶校验码以后数据为:001110100(前面有偶数个1)

这种方法还比较简单,但是大家可以发现,这个只能检测一位数据出错的情况,若出错两位数据或者多位,就难以检测了

2.循环冗余校验码CRC:根据传输或者保存的数据而产生固定位数校验码,它会检测数据传输或者保存后可能出现的错误,把生成的数字计算出来并且附加到数据后面。
或许,看到这,还是不太清楚,来看例子就欧克了。
咱们得先了解一下模2除法(模2除法实际上是除最高位以外的位之间的异或运算):
比如说1001与1010进行模2除法,1001的最高位为1,因此商为1,而除最高位以外的其它为进行异或运算,得到的余数为011.

其运算步骤为:

  • 先选定一个用于校验的多项式G(x),并且在数据尾部添加r个0(r为G(x)中的最高阶)

  • 将添加的r个0后的新数据使用模2除法除以多项式的位串(比如说G(x)=x^2+x+1=1x^2+1x^1+1*x^0,那么G(X)的位串为111,最高阶为2,其中位串和每一项的系数和阶数有关,阶数大的位数大)

  • 得到的余数填充在原数据r个0的为止,若余数位数不够,则高位补0,最后得到可校验位位串

我们来看个简单的例子:
我们运用CRC来计算101001的可校验位串(设G(x)=x^3+x^2+1):

  • 得到G(x)的二进制位串为1101,最高阶为3,因此填充3个0到101001后的新数据为101001000

  • 新数据101001000对位串1101进行模2除法,以下图是具体过程:

模2除法过程

模2除法过程

在这个过程中,当我们进行进行每一步的模2除法时,如果最高位为1,则得到的商为1,比如说第一步的模2除法为1010对1101进行模2除法,最高位为1,因此商的最高(最左边)位为1,而其它位进行异或运算,因此第一步之后的结果为111,把后面的一位0拿过来变成1110,由于最高位为1,继续对1101进行模2除法,规则按照上述来,如果说进行模2除法的时候,最高位为0,那么商为0,并且此时不是对1101,而是对0000进行模2除法,以此类推得到最后的结果,我们可以看到余数为001,以此替换原数据后面的三个0,得到最后的可校验位串为101001001.

  • 进行完模2除法以后,再进行检验,将接收端得到的数据除以G(X)的位串,根据余数来判断对错,如果说余数为0,则说明这个数据传输或者保存的时候没有出错,反之则出错。

3.CRC的相关说明:

  • CRC的错误检测能力和位串的阶数r有关,阶数越高检测的精度准确率越高

  • 数据链路层只进行数据检测,不会进行纠正,如果说检测到的数据出现错误,则把它丢掉

  • G(X)有特定的选择,不是随机的,不同的用途和行业会使用不同的G(X)。

常用G(x)表达式

常用G(x)表达式

最大传输单元MTU

1.MTU:最大传输单元,用来描述最大的能够传输的数据帧,可以说数据帧的长度受MTU的限制。

2.路径MTU:由链路当中MTU的最小值决定。比如说A到B的MTU为1164,B到C的MTU为1000,那么这三个的路径MTU为1000.

以太网协议

来学习这个知识点之前,咱们先看一个例子,比如说A要发数据给B,但是这个过程的经过路由器,路由器是怎么知道A要发给B的,带着这个问题来阅读博客吧。
1.MAC地址:其也可以说是物理地址或者硬件地址

  • 每一个设备都有一个唯一的MAC地址

  • MAC地址总共有48位,一般使用16进制来表示,比如说30-B4-9E-ED-85-CA,每个占四个比特位,总共有12个,因此有48位。

  • 查看MAC地址一般是输入命令ipconfig /all,输入以后,会出现下面的一大堆看不懂的东西,看到那个物理地址就是某一个设备的MAC地址

MAC

MAC

2.以太网协议:完成相邻设备的数据帧传输,应用于数据链路层。

  • 首先咱们得了解以太网数据是什么,以下是以太网数据的格式
目的MAC地址源MAC地址类型帧数据CRC
6个字节 6个字节 2个字节 46-1500字节 4字节

其中目的和源MAC地址是用来寻找设备的,类型代表的是帧数据当中具体是什么协议的数据,帧数据就是具体内容了,CRC就是用来检测差错的,即循环冗余校验码。

  • 要了解数据链路层的工作原理,我们还得知道MAC地址表(直接映射到硬件接口),以下是它的格式:
MAC地址硬件接口
某个设备的MAC地址 对应某个设备的接口,一般都在路由中

3.网络接口层中数据链路层的具体流程:
我们看一个例子:
比如说A要发送数据给设备B或者C,但是它们之间得经过路由,在这个发送数据的过程中:

  • A通过网卡发送数据帧,数据帧到达路由器

  • 路由器取出数据帧的前6个字节(目的MAC地址),然后匹配路由当中存储的MAC地址表,找到对应的网络接口,比如说发现这个网络接口位设备B

  • 路由器会往该网络接口对应的设备B中发送数据帧
    这是一般的过程,当然如果说在匹配MAC地址表的时候,发现没有目的的MAC地址和网络接口映射,这个时候,路由器会广播A的数据帧到除A以外的所有端口,E将会收到B,C的回应,并且把B,C的MAC地址记录到MAC地址表当中,之后再进行匹配发送数据。

网络层

IP协议

1.虚拟互联网络:设备A与设备B相连,进行数据通信,需要经过虚拟互联网络

  • IP协议使得复杂的实际网络变为一个虚拟互连的网络
  • IP协议使得网络层能够屏蔽底层细节而专注于数据转发
  • IP协议解决了虚拟网络中数据报传输路径的问题

2.IP协议:

  • MAC地址与IP地址的对比:
MAC地址IP地址
占48位字,是每一个网络设备唯一的通行证,不可以改变,一般使用十六进制表示 占32位字,也就是32个比特网,是每一个网络设备的标识,但是其随着网络环境的变化而改变,一般使用点分十进制表示,比如说127.0.0.1
  • IP数据报:由IP首部和IP数据报数据组成(单位位比特位)

    IP数据报格式

    IP数据报格式

    其中除IP数据以外都是IP首部的内容,以下是我对这些内容的总结:

IP首部内容各个部分的功能
版本 占4位字,即四个比特位,指的是IP协议的版本(通信双方的版本必须一致),当前主流版本为IPV4,当然以后IPV6也会逐渐普及
首部长度 占4位字,最大数值为15,表示的是IP首部的长度,其单位为4个字节,也就是说一个首部长度相当于4个字节
服务类型 占8位,用来获得更好的服务。这个字段在旧标准中叫做服务类型,但实际上一直没有被使用过。只有在使用区分服务时,这个字段才起作用。
总长度 占16位,最大数值位65535,表示的是IP数据报总长度
标识 占16位,区分不同IP数据报
标志 占3位,判断IP报文是否需要分片
片偏移 如果说总长度大于最大传输单元MTU,那么就会将其拆分为多个数据帧,这个记录的是当前数据帧保存的是第几个偏移的IP数据
生存时间TTL 占8位,IP数据报文在网络当中的寿命,每经过一个设备,就会减一,当TTL为0的时候,数据报会被丢弃,这也是为了避免IP报文在网络当中找不到归宿的时候,避免其无限传输,而占用网络带宽,浪费资源
协议 占8位,表示IP数据报所携带的具体数据是什么协议,比如说这个字段值为1的时候表示ICMP协议等等
首部校验和 占16位,校验IP首部是否会出错
源IP地址 发送IP数据报设备的IP地址
目的IP地址 最终接收IP数据设备的IP地址
选项 IP头部其他内容,可以不用

IP协议的转发流程

1.路由表的简介:由目的IP地址和下一跳IP地址组成:

目的IP地址下一跳IP地址
IP1 IP2
。。。 。。。

比如说第一个表示的就是这个数据报最终的归宿在IP1的设备,当前要去的下一个地方就是IP2的设备,也就是下一跳。

2.IP协议的转发流程

IP协议转发流程

IP协议转发流程

比如说A发送数据报给C,那么我们来分析一下这个流程,以网络层为例:

  • A发出目的地为C的IP数据报,并且查询本地路由表,发现目的C对应的下一跳的IP地址为E
  • A把数据报发送给E
  • E查询本地路由表发现下一跳为F,把数据表发送给F
  • F查询本地路由表发现目的C就在前面,与它相连,因此发送数据给C,结束

接下来我们把网络层和网络接口层结合起来看看:

  • A发出目的地为C的IP数据报,查询本地路由表发现下一跳IP地址为E
  • A将IP数据报交给数据链路层,并且告知目的MAC地址为E
  • 数据链路层填充源MAC地址和目的MAC地址
  • 数据链路层通过物理层将数据发给E
  • E的数据链路层收到数据帧,把帧数据再交给网络层
  • E继续查询本地路由表,然后巴拉巴拉,以此类推,最终数据发给了C

我们可以发现,在这个过程中,数据帧每一跳的MAC地址都在改变,因为告知目的MAC地址一直在变,但是IP数据报每一跳的IP地址不变,因为目的IP地址没变,本地路由表也没变

ARP协议与RARP协议

1.ARP协议:又称为地址解析协议,即把网络层32为IP地址通过ARP协议转换成对应的数据链路层48位MAC地址,在这个过程会使用到ARP缓存表:

IP地址MAC地址
IP1 MAC1

这个其实就是IP与MAC之间的映射

如果说在进行映射查询的时候,发现没有对应需要的IP地址和MAC地址的映射,那么就会把设备该信息广播到其他设备,如果说对应设备发现自己是“所谓伊人”,那么它就会发出回应,然后设备就会在ARP缓存表中记录当前设备的MAC地址和IP地址的映射。

注意!!!ARP缓存表并不是永久有效的,而是有一定期限,因为IP地址会随着网络环境而变化,而MAC地址不变,因此映射关系很容易改变,本地ARP缓存表也会做出相关改变

如果说,我们要查询自己本机的ARP缓存表,可以打开你的CMD,然后输入arp -a 之后我们就可以看到我们需要的东西了。

2.ARP协议对应的位置:

ARP协议的数据封装在数据帧当中,当数据帧中的类型位0806的时候,就表示是携带了ARP协议的数据(一般为28位字节),ARP协议具体数据格式为:

硬件类型协议类型标记发送端以太网地址发送端IP地址目的端以太网地址目的地IP地址
占2位字节 2 4 6 4 6 4

2.RARP协议:也称为逆地址解析协议,也就是由MAC转IP的(类型为8035)

至于具体内容和ARP类似,可以类比,这两种协议在IP协议的转发流程中是一起使用的,把数据交给网络层(ARP),网络层数据交给数据链路层(RARP)

IP地址的子网划分

1.分类的IP地址:IP地址分为网络号和主机号,根据网络号位数的不同可以分为A,B,C三种类型:

分类内容
A类地址 网络号占8位(以0开头),主机号占24位字
B类地址 网络号占16位(以10开头),主机号占16位
C类地址 网络号占24位(以110开头),主机号占8位

特别注意!!!

  • 特殊的主机号:

    • 主机号全为0表示当前网络段,不可以分配到主机
    • 主机号全为1表示广播地址,向当前网络段的所有主机发送信息
  • 特殊的网络号:

    • A类地址网络段全为0表示特殊网络,不可用
    • A类地址网络段后七位全为1表示回环地址,比如说127.0.0.1表示的是本机回环地址,不属于任何类别,代表的是本机的虚拟接口(127的二进制表示位01111111,以0开头,并且后七位为1,因此为特殊网络号)
    • B类地址网络号后14位全为0不可使用
    • C类地址网络号后21位全为0不可使用

看完特殊的网络号和主机号以后,我们来做一个总结:

类别最小网络号最大网络号子网数量最小主机号最大主机号主机数量
A 1 127 2^7-2(把两个特殊的网络号去掉) 0.0.1 255.255.254(主机号不可全为1) 2^24-2
B 128.1 191.255 2^14-1 0.1 255.254 2^16-2
C 192.0.1 223.255.255 2^21-1 1 254 2^8-2

2.划分子网()避免IP地址的浪费:一般分为网络号+子网号+主机号

比如说对于C类地址网络段为193.10.10的可以划分两个子网(根据第一位主机号为0或1),第一个子网范围为:193.10.10.0193.10.10.127, 第二个子网范围为:193.10.10.128193.10.10.254

问题来了!那我们如何在子网号这么多的情况下来判断这个IP地址到底属于哪个网络段

比如说,对于IP地址为193.10.10.6,我们来找出它的子网:

  • 首先我们得了解子网掩码(这个过程需要用到),子网掩码就是由连续的0和1组成,比如说A类地址的某个子网掩码为:255.0.0.0,B类地址的子网掩码为:255.255.0.0,C类地址的子网掩码为255.255.255.0。

  • 我们先求出这个IP地址的二进制表达形式:11000001.00001010.00001010.00000110以及它的子网掩码(一看就是C类)为11111111.11111111.11111111.00000000

  • 然后让子网掩码和IP地址的二进制表达形式进行位与位之间的与运算,最后得到结果之后,再用点分十进制表示,因此求得其子网号为:193.10.10.0

3.无分类编址CIDR(目前使用最广泛的):

1.说明:

  • CIDR中没有什么A,B,C类这几个不同类别的网络号,以及子网

  • 其网络前缀相同的IP地址称为一个CIDR地址块:网络前缀(位数任意)+主机号

  • 对于CIDR,我们一般使用斜线记法,比如说193.10.10.129/25表示的是网络前缀有25位。

  • 虽然说CIDR没有A,B,C这几类,但是子网掩码,特殊网络号、主机号都是一样的。

例如下:

CIDR

CIDR

对于使用数量比较大的,例如说中型网络和大型网络,我们分配的网络前缀就少些,以配置更多的主机号,反之则多。

网络地址转NAT技术

1.背景:由于IPV4不够用了,按理来说是够的,不过早期对于IPV4规划不合理,就造成了大量的浪费,因此就产生了网络地址转NAT技术以弥补这个问题

2.IP地址分类:

  • 内网地址:内部机构使用,不过要避免与外网重复
  • 外网地址:全球范围使用,外网也可以是公网,是唯一的。

NAT技术就涉及内网与外网的通信。

3.说明:

  • 不同的公司使用同样的内网地址是不冲突的
  • NAT技术用于多个主机通过一个公有IP访问互联网
  • 端口号:指定某个设备具体哪个进程在使用网络,在通信

4.具体过程:

NAT

NAT

这个过程同样的也会用到一个表:NAT表

方向旧地址及端口号新地址及端口号
192.168.2.11:6666 173.21.59.10:1666
192.168.2.10:7777 173.21.59.10:1777
173.21.59.10:1666 192.168.2.11:6666
173.21.59.10:1777 192.168.2.10:7777

过程:IP地址为192.168.2.11的设备中端口为6666的进程使用网络,发出IP数据报文以后的对于源地址也是这个,当IP报文来到路由器的时候,其会根据NAT表把旧地址及端口号替换为新地址及端口号(173.21.59.10:1666),数据会以新地址及端口号发送到外部网络中进行通信,目的设备会识别其新的地址及端口号,处理完数据请求以后,发送应答数据给路由器,然后再把新地址及端口号通过查询NAT表来替换为旧地址及端口号(192.168.2.11),然后设备会收到应答数据报文。

在这个过程中,NAT技术会把不同IP地址及端口号转换到公有的IP及端口号与外网通信,减缓了IP地址的消耗,但是增加了复杂度。

ICMP协议

1.概述:其也称为网际控制报文协议,用来辅助IP协议的,可以报告错误信息或者异常情况

2.位置及组成:

  • 其数据封装在IP报文数据当中
  • 由ICMP报文首部和ICMP报文数据组成

3.ICMP首部:

类型代码校验和
8个比特位 8位 16位

ICMP协议封装在IP协议当中,IP首部的8位协议会会写入字段值为1,来表示封装的数据是ICMP协议

对于类型和代码或许大家会很疑惑,其实ICMP报文的类型总共有两种,分别为差错报告报文和询问报告报文:

  • 差错报告报文:
类型的值报文类型具体代码
3(终点不可达) 分为网络不可达和主机不可达(就是说这个地方是不能够去的) 网络不可达代码表示为0,主机不可达为1
5(重定向) 网络重定向和主机重定向(就是说这个目的地需要重新定向,比如说换一个网络,换一个主机) 前者代码为0,后者为1
11 传输超时 -
12(IP数据出现问题) 坏的IP头(IP头部出问题)和缺少其它必要参数 分别为0和1
  • 询问报文:
类型的值报文类型具体代码
0或8 回送请求或者应答(验证网络是否已经连通) —-
13或14 时间戳请求或者应答(通信请求时间同步)

ICMP协议的应用

1.ping应用(询问报文):这个一般是用来检查网络故障的

  • 检查自己的主机网络问题:ping+回环地址,比如说ping 127.0.0.1就是检查自己主机的问题
  • 检查网关问题,是否连接上:ping+网关地址
  • 检查和ISP或者目的设备连接问题:ping+远端地址

2.Traceroute应用:探测IP数据报在网络中走过的路径(利用差错不可达和TTL),如果说TTL=0,则记录为不可达,发出ICMP终点不可达报文。

举个例子:*

A——-。。。。。——->B

从设备A到设备B的过程中,我们若要获取这个过程的网络路径,我们则要先A中的Traceroute封装一个TTL=1的报文,经过第一个网络设备之后,TTL减为0,此时当前网络设备发送一个ICMP不可达报文给A,A就会记录当前的IP地址,之后A再会封装一个TTL为2的报文,然后经过第二个网络,TTL减为0,之后。。。以此类推,直到到达目的地为止,这样就可以记录路径了。

我们可以在我们自己的CMD中输入:tracert www.baidu.com 当然,也可以是其它网址或者IP地址,这个时候我们就可以看到访问的具体路径了。

ROAD

ROAD

网络层的路由概述

1.路由表需要解决什么问题?

  • 下一跳的地址是怎么来的?
  • 下一跳的地址是唯一的吗?
  • 下一跳的地址是最佳的吗?
  • 路由器这么多,它们又是如何协调好的?

互联网又这么大并且复杂,因此需要把互联网化成很多个部分来管理,就像一个国家划成很多个省,很多个市,县等等,也是为了更好的管理,更高的效率。

2.为了解决上面的问题,因此又出现了各种路由算法:必须满足:

  • 正确性,完整性
  • 简单性
  • 可变通性
  • 稳定性,公平性

3.自治系统(AS):处于一个管理机构下的网络设备群,其中AS内部的网络自行管理,其对外提供一个或者多个接口。

4.自治系统内部路由的协议:内部网关协议(RIP和OSPF)

5.自治系统外部路由的协议:外部网关协议(BGP)

内部网关路由协议

RIP协议

1.距离矢量算法(DV):

  • 每一个节点使用两个向量Di和Si表示,其中Di表示当前节点到别的节点的距离,Si表示当前节点到别的节点的下一个节点(下一跳)
  • 每一个节点到与相邻节点交换变量信息Di和Si(交换)
  • 每一个节点根据交换的信息更新自己的节点信息(更新)

驰哥来解释:

Di={di1,di2,….,din},Si={si1,si2,…,sin},其中din表示第i个节点到第n个节点的距离,sin表示第i个节点到第n个节点的下一个节点,n表示节点数量。

并且dij=min(dix+dxj),取的是路径的最小值。

我们来举个例子,看到一个图,把网络当成图来看,节点就为各个设备,线就代表路径:

DV

DV

以A为例,一步步来更新A的DV表,我们假设,初始化的时候,其DV是这样的:对于与路径相关的Di信息是这样的

 A
A 0
B 11
C 12
D 10
E 21
F 17

对于Si的信息暂时还不知道:

而根据通信,可以获得与A直接相连的节点的距离,即AB=6,AC=9,AD=8,AF=7。

并且可以收到相邻各个节点的D的信息,合并可以得到一个初始化的关于A与其各个相邻节点D信息的总表:

总表

总表

现在,我们来一步步交换更新信息:

  • A与B交换矢量信息: daa=0,dab=min{11(原来的),6(与B交换信息后得知的)}=6,dac={12,6+11(A->B->C)}=12,dad=min{10,6+7(A->B->D)}=10,dae={21,6+17}=21,daf={17,6+11}=17.

    更新完毕,得到新表,并且可以得到最新的关于A的S信息(基于上述的最短距离更新):

 A
A A
B B
C -
D -
E -
F -

(由于更新基本没变,就更新了A->B,因此A到B节点的下一节点就为B,其他的未知)

以此类推不断更新,经过与各个相邻节点交换信息之后,得到最终A的D向量和S向量的表:

D向量A
A 0
B 6
C 9
D 8
E 19
F 7
S向量A
A A
B B
C C
D D
E D
F F

2.RIP协议的过程(使用了DV算法):

  • RIP协议会把网络的跳数作为DV算法的路径
  • RIP协议每隔30秒就会交换一次路由信息
  • 如果跳数大于15的路由是不可达路由

过程:

  • 路由器初始化路由信息(即Di和Si)

  • 对相邻路由x发过来的信息,对信息内容进行修改(假设下一跳地址设置为X,更新表时,距离+1)

    • 检索本地路由,将信息中的新路由插入到路由表当中,信息可能是路由表当中没有的(没有的肯定拿进来)
    • 检索本地路由,对于下一跳位X的更新为修改后的信息(暂时更新,以便于和原表对比)
    • 检索本地路由,对比相同目的的距离,如果新信息的距离更小,则更新本地路由信息(用DV算法找最优的,新表和原表对比)
  • 如果三分钟内没有收到相邻的路由信息,则把相邻路由设置为不可达,以后就不管他了,不从它这过。

这个就是RIP协议的过程,我们来分析它的优缺点:

优点当然就是比较简单,开销小,管好隔壁就可以了

缺点就是故障信息传递很慢,因为太相信隔壁老刘了,隔壁老刘可能告诉你的不对,你也相信它,或者隔壁老刘告诉你的是个笨方法,根本到不了你要去的地方,你还是傻傻地会去相信。这个也会限制网络规模,因为都是和相邻节点通信并且交换信息,如果说规模是那种很大的,速度就会比较慢,就不适用了。

Dijkstra算法(求最短路径)

1.特点:

  • 是一种图的算法
  • 解决了有权图一个节点到其它节点的最小距离
  • 以起始点为中心,向外层层扩展

2.过程:

  • 初始化两个集合S和U,S只有初始顶点,U是其它顶点的集合。
  • 如果说U不为空,那么就对A到U集合顶点的距离进行排序,选取距离A最近的一个顶点D(中间也可以有其它顶点,比如说A->B-C),将该顶点放入到S中,重复该步骤,直到U为空.

OSPF协议

1.链路状态协议(LS协议):

  • 向所有的路由器发送消息,一传十,十传百
  • 消息描述了该路由器和相邻路由器的链路状态(比如说距离,时延等等),这个链路状态主要由网络管理人员决定
  • 只有当链路状态发送变化的时候,才能够发送信息

2.OSPF协议:开放最短路径优先,利用了Dijkstra算法

  • 向所有路由发送消息,每一个路由获取网络中的消息,得到网络完整的拓扑,这些信息也称为链路状态数据库。

后面就是和链路状态协议的步骤一样。

这个协议比RIP协议更加的客观先进,因为路径选择标准不再是单一的距离,而是链路状态(距离,时延等等)多种因素,而且这个不是固定的实践就会进行数据交换,而且等链路状态发送变化的时候才会发送消息,更加容易收敛,发现问题故障。

3.五种消息类型:这个协议涉及到五种类型的消息

  • 问候消息:与相邻路由器通信,看看是否可达
  • 链路状态数据库描述消息:向相邻路由器发送自己的链路状态消息
  • 链路状态请求消息:向相邻路由器发送链路状态获取请求
  • 链路状态更新信息:更新链路状态
  • 链路状态确认信息:确认更新

4.过程:

路由器接入网络->路由向邻居发送问候消息->与邻居交流链路状态数据库->广播和更新

5.对比:

RIPOSPF
从邻居看网络,距离是累加的,操作比较频繁,收敛比较慢,信息是通过路由间拷贝更新的 可以获取整个网络拓扑,使用Dijkstra算法计算最短路径,状态变化才更新,收敛更快,通过路由间传递链路状态信息,更加先进

外部网关路由协议之BGP协议

1.概述:BGP协议也称为边际网关协议

  • BGP协议是运行在各个自治(LS)系统,就是管理各个省的。

为什么要使用BGP协议?

  • 互联网规模比较大
  • AS内部使用不同的路由(有的使用RIP,有的使用OSPF)协议,比较复杂以此需要一个统一的协议来管理
  • AS之间还需考虑除网络特性以外的因素,比如说政治,安全等等

因此,BGP协议不能够找到一条到达目的最好的路由,但是可以找到比较好,比较合适的。

2.BGP发言人:一般是由AS中的边界路由器组成,比如说位于这个网络边界的就可以当这个网路的发言人,和其它网络通信。

  • BGP不会关心内部网络
  • AS间通过BGP发言人交流信息
  • BGP发言人可以人为的配置,方便更好的管理。

传输层

UDP协议

1.概述:UDP协议也称为用户数据报协议

  • 其只负责封装应用层发过来的数据报,并且不作如何处理,UDP协议中数据的大小取决于其接收应用层数据报的大小
  • UDP协议的数据一般是封装在IP数据报内容里面,其由UDP首部和UDP数据报数据构成

2.具体内容:

UDP首部:(单位为比特位)

16位源端口号(用户机器进程端口)16位目的端口号(目的机器通信进程端口)
16位UDP长度(UDP数据的长度) 16位UDP校验和

其它的都是UDP数据具体内容了

3.特点:

  • UDP是无连接的协议,A与B发送消息,不需要提前建立连接,想发就发
  • UDP不能够保证可靠的交付数据,中间可能会有数据丢失
  • UDP面向报文传输,不对数据进行处理,就是发送一整个封装好的报文
  • UDP没有拥塞控制,其不会感知网络是否会发生拥塞
  • UDP首部内容比较简单,开销比较小

TCP协议

1.概述:其也称为传输控制协议,位置和UDP数据一样,都是封装在IP数据当中

2.特点:

  • TCP是面向连接的协议,通信之前需要进行连接,就像打电话一样,你需要去接才能够通信
  • TCP是点对点的通信
  • TCP可以提供可靠的数据传输服务
  • TCP协议提供全双工通信,可以同时发送和接收
  • 其是面向字节流的协议,把一块块数据看成字节来传输,方便处理数据

3.TCP首部结构描述:(单位为比特位)

TCP

TCP
  • 序号:占32个比特位,范围:0~2^32-1,用来标记传输的字节,一个字节对应一个序号,这个序号代表的是TCP报文数据首字节的序号
  • 确认号:占32个比特位,范围:0~2^32-1,表示期待收到的数据的首字节号
  • 数据偏移:占4个比特位,单位位32位字(也就是4个字节),表示数据偏移首部的距离
  • TCP标记:占6个比特位,每位的意义都不同,总共六位标记:
标记含义
URG urgent:紧急位,URG=1,表示紧急数据
ACK 确认位,ACK=1时表示确认号生效
PSH push,推送位,PSH=1的时候,表示尽快把数据给应用层
RST reset:重置位,RST=1,表示重新建立连接
SYN 同步位,SYN=1,表示连接请求报文
FIN finish,FIN=1,表示释放连接
  • 窗口:占16个比特位,指明允许对方的发送的数据量,也就是最大值
  • 校验和:验证错误
  • 紧急指针:URG=1时有效,指定了紧急数据在报文中的位置
  • TCP选项:最多占40个字节,支持未来的拓展

可靠传输的基本原理

1.停止等待协议:来举个例子,或许说的搞清楚:

在无差错的情况下,发送方给接收方发送消息1,然后接收方收到消息以后,就向发送方发送确认收到消息,当发送方收到以后,继续发消息,然后以此类推。。。。

在这个过程中,发送方发出消息1的时候,会暂时停止发送消息2,此时等待

接收方发来的确认消息,当收到确认消息以后才会继续发送,这个就是停止等待协议。

当然也会出现差错的情况,如果说,消息在传输的途中丢失了:

  • 发送消息1的过程中,消息1丢失,接收方无法发送确认消息或者说,消息1收到了,但是发送确认消息的过程中消息丢失了,这就会让消息2等很久,如果说超过一定时间没有反应,则会超时重传,重新发送消息1.

  • 如果是消息没有丢失,但是迟到了,也会进行超时重传。

这个过程会有等待,停止的过程,会占用信道,导致信道效率不高。

  • 超时定时器:没发送一个消息,都会设置一个超时定时器,以便于超时重传的操作

2.连续ARQ协议:自动重传协议

  • 滑动窗口:滑动窗口的大小固定,比如说大小为6个字节,那么其就可以占据着六个字节的信息,但是其占据的东西可以不断更新,把后面的字节信息也囊括进来。

假设有连续的12个字节的报文需要发过来,而滑动窗口的大小为6,此时其就会占据前6个字节的数据,一个个连续发送,当第一个和第二个字节的数据被收到以后(发送确认消息过来),此时窗口会向后移动两个位置,此时占据的就是3~8的位置,一个个字节批量发送才会向后移动。其可以发送窗口里面的所有数据,但是当确认号数据(需要发送的数据)到达目的地以后,窗口才会后后移,但是确认的过程缺少逐个的确认,开销比较大。

  • 累计确认:如果发送了上述的16号数据,而确认号为5,则当收到了确认号消息以后,就会告诉5之前的数据都确认了(连续发送确认),便会移动窗口,变为611。等下我们看个例子就知道了。

TCP协议的可靠传输(基于ARQ协议)

 

假设窗口大小为7,若发送的消息如上,那么窗口以前的都是已经确认发送过的字节序号,其之后的都是还未确认,并且不允许发送的字节序号(因为不属于自己管),在滑动窗口内部则有已发送但是未确认的字节消息,还有未发送的可用窗口消息,如果23,24的消息确认以后,窗口就会右移两个位置变成:

 

以此类推把所有消息发送完,当然在上述过程,如果至少25,27发送并且收到了确认消息,而23,24未收到,则会等待,等待23,24的好消息,但是如果说超时了,就会从23开始重传,但是这样全部重传的效率很低,因此选择重传就诞生了:

  • 选择重传
    • 需要指定应该重传的字节
    • 每一个字节都有32位(四个字节)唯一的序号,而在TCP首部的TCP选项当中(最多存四十个字节),因此最多只能存十个序号,因此不可能会逐一记录需要重传的字节,地方毕竟就那么大,因此TCP选项里面记录的是重传数据的边界序号,比如说23,24,25,26这一段都需要重传,那么TCP选项里面记录的就是23和26的字节序号。

TCP的可靠传输大概就是这样完成的。

TCP协议的流量控制

1.目的:限制发送方的发送速率

2.实现方式:利用滑动窗口

3.过程:

TCP流量控制

TCP流量控制

以上是发送方和接收方互发消息的过程,从上往下按照时间顺序:

首先,发送方会发送(首字节)序列号(seq)为1的数据,大小为100字节,之后又发送首字节序列号为101的数据,大小为100字节,然后接收方收到消息以后,就会发送确认消息给发送方,ACK=1表示消息已经确认了,ack=201表示下一次希望发过来的首字节数据序列号为201,rwnd=300告诉对方发送数据的时候窗口大小为300,之后,发送方发送首字节序列号(seq)为301的数据,数据大小为100字节,由于窗口大小为300,因此后面只能够发送200字节的数据,后面又开始发送序列号为401的数据,大小为200字节,接收方收到消息以后,发送确认消息,ACK=1,告诉对方我已经收到你的消息了,ack=601表示我下次希望收到的数据围殴601,rwnd=0表示现在窗口为0了,以控制对方发送的速率,就让对方暂时不发送了。之后如果想要对方发送数据了,就会发送rwnd=1000的消息给发送方,告诉他现在窗口大小为1000了,你可以发送数据过来了。

注意!!!如果说最后发送rwnd=1000的消息在发送过程中出现了丢失,那么这个时候双方都不会发送消息,这是就会形成你等我,我等你的死锁状态,因此此时就出现了坚持定时器,来解决这个问题:

  • 坚持定时器:
    • 当收到窗口大小为0的消息之后,就会启动坚持定时器
    • 坚持定时器每隔一段时间都会发送一个窗口探测报文,看看窗口是否发生了变化,如果说探测的时候发现它明明发送了rwnd=1000的消息,而我却没有收到,这个时候会叫他重发。

TCP协议的拥塞控制

1.对比流量控制:

流量控制拥塞控制
考虑点对点的通信量的控制 考虑整个网络,具有全局性

2.控制拥塞的算法:

  • 慢启动算法:

    • 由小到大逐渐增加发送数据量
    • 每收到一个报文确认,就会逐渐把数据量加一(1->2->4->8…指数增长),让数据量发送变大
    • 当数据量增大到一个慢启动阈值的时候,就不增加了,此时就会使用下面的算法:
  • 拥塞避免算法:

    • 维护一个拥塞窗口的变量
    • 只要网络不拥塞,就试探把拥塞窗口调大,这个时候就是线性增长了

两个算法的过程如下:

前一个过程使用第一个算法,到达某种程度以后就启用第个算法

TCP的拥塞控制

TCP的拥塞控制

TCP连接的建立(三次握手)

三次握手

三次握手
  • 第一次握手发送方向接收方发送了:SYN=1,seq=x的数据报文,SYN是TCP标记,表示的是连接同步请求,就是请求建立连接的,并且还发送了序列号为x的报文
  • 第二次握手,接收方收到了来自发送方请求连接的请求,并且向它发送确认消息:ACK=1,而且也向发送方发送SYN=1连接同步请求,并且希望发送方下次发送的数据的序列号为x+1,而且接收方也向发送方发生了序列号为y的数据,此时发送方已经建立了连接
  • 第三次握手,发送方收到了接收方的数据请求,于是发送确认消息:ACK=1给接收方告诉它收到了连接的消息,并且同意连接,发送了序列号为x+1的数据报文(就是接收方想要的)的数据给接收方,接收方收到以后,也会建立起连接,TCP连接的建立完毕。之后就可以进行数据的传输。

第一次握手和第二次握手时建立连接的过程,2,3次握手过程时未来同步各自的序号,便于建立可靠的连接。对于发送方而言,第一次握手和第二次握手的过程是同步已发送的过程,第三次握手消息发送以后就建立了连接,对于接收方而言,第一次握手的过程就是监听过程,监听第一次握手的消息,第二次握手发送到第三次握手接收之前是同步已接收的过程,第三次握手的消息收到以后就是建立连接的过程,此时接收方已经建立好了连接,双方开始进行通信。

或许有的读者会问道,既然我第二次握手就可以建立连接了,那我为什么还闲着没事干发送第三次握手的消息?

其实这也是未来避免连接请求报文传送到对方引起报错,避免数据丢失或者超时而引起的错误。举个例子:

第三次握手的重要

第三次握手的重要
  • 假设第一次发送第一次握手的请求的时候,由于发送时间过长,发送的请求要经过很长时间才能够到达接收方,这个时候,自己也会用比较长的时间才能够收到第二次握手请求的确认消息,此时发送方会认为发送超时,就会第二次发送第一次握手的消息请求。(假设第一次发送的为1,为失效请求,第二次发送的为2,为有效请求)
  • 如果第二次握手就开始全部建立连接的话,那么1的存在就会影响连接,因为接收方还是会收到1,还是会照常发送第二次握手,只不过是时间问题,但是2发送的请求要到的早一点,但是1的迟到也会给第二次握手造成影响。
  • 因此就需要第三次握手来屏蔽这些无效请求,就会规定,发送方接收到第二次握手的请求后,发送了第三次握手请求之后,如果再遇到第二次握手的请求消息,就会不再发送第三次握手请求,避免失效消息1的影响和干扰,此时就可以建立可靠的连接。

TCP连接的释放(四次挥手)

四次挥手

四次挥手
  • 发送方发出第一次挥手的请求:FIN=1,即终止位标记为1,代表释放连接,并且发生了序列号为u的数据,此时发送方进入了连接结束第一次等待状态
  • 接收方收到第一次挥手的请求后发送确认收到的消息,即第二次挥手请求:ACK=1(表示确认,告诉发送方我知道了),seq=v(发送序列号为V的数据),ack=u+1(希望下次收到序列号为u+1的数据)。此时发送方进入连接结束第二次等待状态,接收方进入关闭等待状态
  • 接收方发送第二次挥手的请求是为了告诉发送方自己已经确认收到了,然后自己也需要去断开连接1,也要发送连接释放的请求,因此就发送了第三次握手的请求给发送方表示自己的连接释放请求:FIN=1(终止位标记为1,表示释放连接请求),ACK=1,seq=w,ack=u+1.此时接收方进入最后确认状态。
  • 发送方收到了来自接收方第三次挥手的请求以后,也要发送确认收到的消息,告诉接收方我已经收到了,即发出第四次挥手的消息请求:ACK=1,seq=u+1,ack=w+1,此时发送方设置一个等待计时器,等待时间过完,就进入关闭状态,接收方收到第四次挥手消息以后,也进入关闭状态,TCP连接的释放完毕。

对于那个等待计时器等待的时间,一般是等2MSL的时间,我们来解释一下MSL。

MSL:最长报文段寿命(一般设置为两分钟)。

在2MSL的等待时间段内,发送方不会释放连接,那为什么要等2MSL,而不是MSL,或者3MSL的时间呢?

  • 因为最后一个报文的发送还没有确认收到,需要等一下
  • 确保发送方的ACK可以到达接收方
  • 如果说2MSL的时间内接收方没有收到,那么接收方会重新发送第三次挥手的消息,等待计时器也是为了确保连接的正确释放。
  • 确保当前连接的所有报文都已经过期,便于释放。

套接字与套接字的编程

1.概述:套接字指的是IP和port的组合,也就是我们所说的socket。,socket表示的是TCP连接的一端,通过套接字可以进行数据的发送和接收,而TCP连接是点对点的通信,因此需要两个套接字。

2.过程:

服务器:创建套接字->绑定套接字->监听套接字->接收处理信息

客户端:创建->连接->发送

3.分类:

  • 网络套接字:一般是跨机器,跨网络的通信
  • 域套接字:通过域套接字文件进行通信,数据不需要经过协议栈,比较简单,并且速度快,消耗资源小,一般用于单机通信,就是自己内部的通信。

应用层

简单介绍

1.概述:应用层需要的就是往上对阶用户,并且提供丰富的服务,它会使用到以下各层为它服务

  • 比如说,它会利用UDP协议进行多媒体信息开发,比如说语言,视频,实时消息等等
  • 它也会利用TCP进行可靠消息的传输,比如说金融交易,可靠通讯等等

2.应用层定义的应用间通信的规则:

  • 定义应用进程的报文类型

  • 报文的语法格式

  • 应用进程发送数据的世纪和规则

等等

DNS

1.概述:DNS也被称为域名系统,一般是域+名,DNS会把IP与端口的组合转化为域名,让我们便于使用和记忆,就比如说www.baidu.com就是一个域名,而它其实就是IP地址和端口的组合由DNS服务转换而来的,也是为了我们方便使用。

2.组成:由点,字母和数字组成,点是用来分割不同的域,而域也分为顶级域,二级域,三级域等等,比如说www.baidu.com中www就是代表三级域,baidu就是代表二级域,com就是代表顶级域。

3.分类:

  • 顶级域:顶级域也会分为国家类和通用类,其中国家类由有cn(中国),us(美国),uk(英国),ca(加拿大)等等,通用类分为com(公司),net(网络服务机构),gov(政府),org(组织)等等
  • 二级域:一般都是具体名字,比如说baidu,aliyun,qq,taobao等等
  • 三级域:比如说www,tsinghua等等

3.说明:DNS是建立在DNS服务器上的,而DNS服务器也会有不同的分层,便于管理:

  • 根域名服务器
  • 根域名服务器下面就是顶级域名服务器
  • 顶级域名服务器再往下就是域名服务器

4.DNS过程:输入域名以后,设备会查询本地域名服务器,如果有对于的IP地址就还可,如果没有就会查找根域名服务器,找到对应的顶级域名服务器,从这里面找IP,再往下找域名服务器,最终返回对应的IP进行访问。

DHCP协议

1.概述:DHCP协议也称为动态主机设置协议

  • DHCP是一个局域网协议
  • DHCP是应用了UDP协议的应用层协议

2.功能:比如说,我们的电脑设备有时候会在宿舍,有时候会在学校,而不同的地方IP地址,网络环境也不同,那我们为什么不需要去配置IP地址来实现通信呢,它电脑本身又是怎么处理的呢?这个就是DHCP协议实现的即插即联网,DHCP会分配一个临时的内网IP,再结合NAT技术,去访问外网,因此实现通信,我们就不需要配置了。但是这个临时IP有一个租期,过期以后如果想继续使用就可以续租。

3.过程:假设某个网络环境下加入了一个新设备:

  • DHCP服务器监听默认端口67
  • 主机使用UDP协议广播DHCP协议发现报文请求,以找到DHCP服务器
  • DHCP服务器发送DHCP提供报文,告诉主机我在这里
  • 主机向DHCP服务器发出DHCP请求报文
  • DHCP服务器回应主机并且提供IP地址

HTTP协议

1.概述:HTTP协议也称为超文本传输协议

  • 其会使用一个超文本(带超链接的文本),其格式为http://<主机>:<端口>/<路径>,主机和端口对应的是设备或者某个网络资源的IP和端口的组合,路径一般指向特定的内容。
  • HTTP协议是可靠的数据传输协议,其基于TCP进行传输,可以传输各种web内容,比如说文本,文件,图片,音频,动图,视频等等

2.HTTP协议的具体过程:

web服务器接收客户端的连接->接收到请求报文->处理请求,看看客户端需要请求哪些内容->访问web资源,看看我web资源里面有没有这样内容->构造应答,我把这些内容整理一下->发送应答,把这些内容发送给客户端

3.HTTP请求方法:GET,POST,DELETE,UPDATE,PUT,OPTIONS,PATCH,HEAD,TRACE。

  • GET:获取指定的服务端资源
  • POST:提交数据到服务端
  • DELETE:删除指定的服务端资源
  • UPDATE:更新服务端资源等等

等等

那我们如何指定这个资源呢?

  • 在地址中指定,比如说http地址里面有一些参数可以指明我需要指定哪些资源,例如http://coding.imooc.com/class/355.html, 这个网址就在域名后面指定了/class,拉取课程内容,还指定了/355,表示的是第355个资源。
  • 在请求数据中指定,对于这个请求和应答过程涉及到两种http报文:

请求报文:

报文各个部分举例
请求方法,请求地址,HTTP版本 POST(请求方法),http://coding.imooc.com(请求地址),HTTP/1.1表示请求版本
请求头 Accept-Encoding:ip,Accept-Lauguage:zh-cn等等(这个不用管)
请求内容 {“sort”:0,”unlearn”:0,”page”:0}等等

应答报文:

分为这几个部分:

  • HTTP版本,状态码,状态解释
  • 应答头
  • 应答内容

状态码的说明:

状态码解释
200~299 成功状态码
300~399 重定向状态码
400~499 客户端错误状态码
500~599 服务端错误状态码

HTTP工作的结构

1.web缓存:web内容分为热门内容和冷门内容,一般满足二八原则,20%的热门内容和80%的冷门内容,对于热门内容需要优先缓存起来,提高访问数据量和速度,提高服务质量

2.web代理:client->proxy(代理)->server

客户端域服务端之间的访问连接有时候会利用代理,这也是为了屏蔽server部署的架构,保证server的安全,避免其暴露在危险的环境中

  • 正向代理:代理client访问server
  • 反向代理:代理server提供数据给client

3.CDN:也称为内容分发网络,可以把一个大型网络分为多个CDN到各个地方,便于访问,加快多媒体内容访问速度

HTTPS协议

1.对比HTTP协议

  • HTTP协议是明文传输,对于一些比较重要的内容来说是不安全的
  • HTTPs协议可以提供加密,更加安全

1.加密模型:

  • 对称加密:使用同一把密匙进行加密和解密
  • 非对称加密:其加密的密匙和解密的密匙不一样,分别为公钥和私钥,但是这一对密钥也有一定的数学关系,公钥是给大家使用的,对外公开,私钥是自己使用的,不对外公开。一般公钥加密,私钥解密。

2.数字证书:可信任组织颁发给特定对象的认证,证书格式如下:

证书格式,版本号 
证书序列号  
签名算法  
有效器  
对象名称  
对象公开的密匙  

3.SSL:安全套阶层,位于应用层和传输层之间

  • 保证了数据安全和完整性
  • 对传输层数据进行加密后再传输

4.HTTPS的过程:

  • 客户端和服务器通过443端口进行TCP连接
  • 进行SSL安全参数握手,之后便于进行数据加密
  • 客户端发送数据,由SSL加密
  • 服务端发送数据,涉及到SSL的加密和解密

SSL安全握手过程:

  • 客户端生成随机数1,并且告诉服务端协议版本和加密算法,把随机数1发送给服务端
  • 服务端确定加密算法和发送数字证书给客户端,生成随机数2,发送随机数2给客户端
  • 客户端确认证书是否有效,如果有效,就生成随机数3,使用服务器提供的公钥(数字证书提供)加密随机数3,发送给服务器
  • 服务端解密随机数3,得到随机数,此时双方有随机数1,2,3
  • 双方根据随机数1,2,3和相同的加密算法分别生成密匙(利用对称加密算法),此时密钥没有进行传输,保证了密钥的安全,而且随机数3也是安全的
  • 双方利用对称密钥进行加密通信

这个过程利用了对称加密和非对称加密,二者综合使用了。

实践环节

本次实践环节会实现TCP,UDP,IP报文解析器,让我们更加深入的了解计网。

基于之前的操作系统实践搭建服务器基本框架

网络工作模式:

混杂模式:接收所有经过网卡设备的数据

非混杂模式:只接收目的地址指向自己的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import socket
import json
from operate_system.pool import ThreadPool as tp
from operate_system.Task import AsyncTask
class ProcessTask(AsyncTask): #线程处理任务
def __init__(self,packet,*args,**kwargs):
self.packet =packet #接收报文信息
# AsyncTask(func=self.process,*args,**kwargs)
super(ProcessTask,self).__init__(func=self.process,*args,**kwargs)
def process(self): #线程实现的工作具体路基
class Server: #实现服务器,提供设置混杂模式捕捉到当前网络的各种报文信息,并且输出
def __init__(self):
self.socket = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_IP)
#第一个设置socket工作环境为IPV4,第二个设置套接字的类型为原始套接字,第三个设置工作协议为IP协议(可为其它)
self.ip = '192.168.0.101' #自己的主机IP,当前网络环节下的IP,可以CMD中输入ipconfig查查看
self.port=8888
self.socket.bind((self.ip,self.port)) #绑定
self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
#设置混杂模式
self.socket.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
self.pool = tp(10) #建立一个大小为10的线程池
self.pool.start()
def loop(self):
while True:
#接收
packet,addr = self.socket.recvfrom(65565) #接收报文信息
#生成task
task = ProcessTask(packet) #处理报文
#提交任务线程到线程池
self.pool.put(task)
#获取结果
result = task.get_result() #提供异步获得任务获得处理结果
result=json.dumps( #使用json设置打印格式
result,indent=4 #缩进为4
)
print(result)

if __name__=='__main__':
server = Server()
server.loop()

谈谈python如何操作字节序列

字节序是什么?就是字节的排列顺序,但是字节也分为高字节和低字节,比如说255(十进制)=0000 1111(二进制),前面四位0000就是高字节序,后面四个就是低字节序

字节序分类:

  • 大端字节序:高字节在前,低字节在后,这个也是我们人类的习惯使用
  • 小端字节序:低字节在前,高字节在后,这个就是主机经常使用的,因为计算机电路会优先处理低位字节。

格式字符的操作:

格式字符格式以后的类型占字节的大小
B 整数 1
H 整数 2
L 整数 4
s 字符串 -

在python当中一般是用struct包里面的unpack函数来处理:

1
2
3
4
5
6
7
8
9
10
11
mport struct
bin_str=b'ABCD1234' #八个字节
print(bin_str)
result=struct.unpack('>BBBBBBBB',bin_str) #B为格式化字符,由于bin_str有八个字节,格式字符要八个,>表示大端字节序
print(result)
result = struct.unpack('>HHHH',bin_str) #H格式化2个字节,而bin_str有8个字节,因此要4个H
print(result)
result = struct.unpack('>LL',bin_str)
print(result)
result = struct.unpack('>8s',bin_str)
print(result)

IP报文解析器

要把IP报文的各个细节解析出来,我们就得了解IP的具体组成了,以下的注释里面我都提到了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# ! _*_ encoding=utf-8 _*_

import struct
import socket
def data_str(data): #获取IP数据报里面的IP数据
length=len(data)
data=struct.unpack(length*'B',data)
string=''
for ch in data:
if ch>=127 or ch<32: #不可打印字符
string+='.'
else:
string+=chr(ch)
return string

class IPParser: #IP报文解析器
IP_HEADER_LENGTH=20; #IP头部长度为20字节
@classmethod
def parser_IP_header(cls,packet_header):
'''
IP头部重要内容:
第一行:四位IP_version 4为头部长度 服务类型 16为总长度
第二行:16位标识符 三位标记位 3位片偏移
第三行:8位TTL,8位协议 16位校验和
第四行:32位源IP地址
第五行:32位目的IP地址
:param packet_header:
:return:
'''
line1 = struct.unpack('>BBH',packet_header[:4]) #获取IP头部第一行信息
ip_version = line1[0]>>4 #第一个字节有八位分别存储IPversion和头部长度,比如说11110000,这里表示第一个字节右移4位,变成1111,就是ipversion
ipheader_length = line1[0]&15 #15的二进制表示为:00001111,二者相与只取后四位,即头部长度
ip_length = line1[2] #跳过服务类型,为总长度,第二行
line3 = struct.unpack('>BBH',packet_header[8:12])
TTL = line3[0] #第一个B
xieyi = line3[1] #第二个B
iph_checksum = line3[2] #第三个H
line4=struct.unpack('>4s',packet_header[12:16])
origin_ip = socket.inet_ntoa(line4[0]) #域名转成ip地址
line5=struct.unpack('>4s',packet_header[16:20])
destination_ip=socket.inet_ntoa(line5[0])
return { #返回IP头部信息
'ip_version':ip_version,
'ipheader_length':ipheader_length,
'ip_length':ip_length,
'TTL':TTL,
'xieyi':xieyi,
'ip_checksum':iph_checksum,
'origin_ip':origin_ip,
'destination_ip':destination_ip
}

pass
@classmethod
def parser(cls,packet):
ip_header = packet[:20] #前20字节为IP头部
return cls.parser_IP_header(ip_header)
pass

如果要打印出IP报文的具体内容就可以在我们之前构架的服务器上进行实现,把具体实现逻辑写入到线程处理类的process函数中。

UDP报文解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# ! _*_ encoding=utf-8 _*_

import struct
class TransParser:
IP_HEADER_OFFSET=20 #由于UDP报文的数据是封装在IP报文数据里面的,因此是IP头部之外的
UDP_HEADER_LENGTH=8 #UDP头部的字节长度
TCP_HEADER_LENGTH=20 #TCP头部的字节长度
def data_str(data): #获取具体数据
length=len(data)
data=struct.unpack(length*'B',data)
string=''
for ch in data:
if ch>=127 or ch<32: #不可打印字符
string+='.'
else:
string+=chr(ch)
return string

class UDPParser(TransParser):
'''
1.16为源端口 16为目的端口
2.16为UDP长度 16位校验和
'''
@classmethod
def UDP_header(cls,UDP_header):
line=struct.unpack('>HHHH',UDP_header)
return {
'origin_port':line[0],
'destination_port':line[1],
'UDP_length':line[2],
'check_sum':line[3]
}
pass
@classmethod
def UDP_parser(cls,packet):
UDP_header=packet[cls.IP_HEADER_OFFSET:cls.IP_HEADER_OFFSET+cls.UDP_HEADER_LENGTH]
data=packet[cls.IP_HEADER_OFFSET+cls.UDP_HEADER_LENGTH:]
data=data_str(data)
result=cls.UDP_header(UDP_header) #UDP头部内容
result['data']=data #UDP数据报文数据
return result #UDP报文

TCP报文解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# ! _*_ encoding=utf-8 _*_
from Computer_Network.processor.trans.UDPParser import TransParser
import struct
def data_str(data): #获取TCP报文中的TCP数据
length=len(data)
data=struct.unpack(length*'B',data)
string=''
for ch in data:
if ch>=127 or ch<32: #不可打印字符
string+='.'
else:
string+=chr(ch)
return string

class TCPParser(TransParser):
@classmethod
def TCP_header_parser(cls,tcp_header):
'''
TCP头部:
1.16位源端口 16位目的端口
2.32位序列号
3.32位确认号
4.4位数据偏移 6位保留字段 6位标记号 16位窗口长度
5.16位校验和 16位紧急指针
:param tcp_header:
:return:
'''
line1 = struct.unpack('>HH',tcp_header[:4]) #获取TCP头部第一行数据,前四个字节
origin_port=line1[0] #源端口
des_port=line1[1] #目的端口
line2=struct.unpack('>L',tcp_header[4:8]) #获取TCP头部第二行数据
xuliehao=line2[0] #序列号
line3 = struct.unpack('>L',tcp_header[8:12])
OK_num=line3[0] #确认号
line4=struct.unpack('>BBH',tcp_header[12:16])
data_offset=line4[0]>>4 #数据偏移,因为只占4个比特位,半个字节,因此需要作移位处理获得
flags=line4[1]&int('00111111',2) #进行与运算,获取6个比特位的标记号
FIN=flags&1 #通过与运算获得终止位标记
SYN=(flags >>1)&1 #通过移位和与运算获得同步位标记,下面都是以此类推,不多讲
RST=(flags >>2)&1
PSH=(flags >>3)&1
ACK=(flags >>4)&1
URG=(flags >>5)&1
win_length=line4[2]
line5=struct.unpack('>HH',tcp_header[16:20])
check_num=line5[0]
URG_pin=line5[1]
return { #返回TCP头部具体信息
'origin_port':origin_port,
'des_port':des_port,
'xuliehao':xuliehao,
'OK_num':OK_num,
'data_offset':data_offset,
flags:{
'FIN':FIN,
'SYN':SYN,
'RST':RST,
'PSH':PSH,
'ACK':ACK,
'URG':URG,

},
'win_length':win_length,
'check_num':check_num,
'URG_pin':URG_pin

}
@classmethod
def parser(cls,packet):
tcp_header=packet[cls.IP_HEADER_OFFSET:cls.IP_HEADER_OFFSET+cls.TCP_HEADER_LENGTH]
data = packet[cls.IP_HEADER_OFFSET + cls.TCP_HEADER_LENGTH:]
data = data_str(data)
result = cls.TCP_header_parser(tcp_header)
result['data'] = data
return result

调用TCP,UDP,IP报文解析器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import socket
import json
from operate_system.pool import ThreadPool as tp
from operate_system.Task import AsyncTask
from Computer_Network.processor.trans.UDPParser import UDPParser
from Computer_Network.processor.net.parser import IPParser
from Computer_Network.processor.trans.TCPParser import TCPParser
class ProcessTask(AsyncTask):
def __init__(self,packet,*args,**kwargs):
self.packet =packet
# AsyncTask(func=self.process,*args,**kwargs)
super(ProcessTask,self).__init__(func=self.process,*args,**kwargs)
def process(self):
# ip_header=IPParser.parser(self.packet)
# return ip_header
heder={
'IP_header':None,
'tans_header:':None
}
ip_header = IPParser.parser(self.packet)
heder['IP_header']=ip_header
if ip_header['xieyi']==17: #表示协议位UDP协议
udp_header=UDPParser.UDP_parser(self.packet)
heder['tans_header:']=udp_header
if ip_header['xieyi']==6:
tcp_header=TCPParser.parser(self.packet)
heder['tans_header:']=tcp_header
return heder




pass
class Server:
......

结果如下(部分数据):

运行的时候,如果你是使用pycharm的话,因为混杂模式需要权限,因此需要用管理员模式打开,才能够真正进入混杂模式,其它的编辑器也是如此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
{
"IP_header": {
"ip_version": 4,
"ipheader_length": 5,
"ip_length": 40,
"TTL": 64,
"xieyi": 6,
"ip_checksum": 40147,
"origin_ip": "192.168.0.102",
"destination_ip": "204.79.197.254"
},
"tans_header:": {
"origin_port": 49586,
"des_port": 443,
"xuliehao": 2379595048,
"OK_num": 3297254609,
"data_offset": 5,
"16": {
"FIN": 0,
"SYN": 0,
"RST": 0,
"PSH": 0,
"ACK": 1,
"URG": 0
},
"win_length": 1020,
"check_num": 26806,
"URG_pin": 0,
"data": ""
}
}
.....
{
"IP_header": {
"ip_version": 4,
"ipheader_length": 5,
"ip_length": 115,
"TTL": 53,
"xieyi": 17,
"ip_checksum": 55278,
"origin_ip": "183.232.127.242",
"destination_ip": "192.168.0.102"
},
"tans_header:": {
"origin_port": 8000,
"destination_port": 63675,
"UDP_length": 95,
"check_sum": 21212,
"data": ".8Y.....3A......i.....;l..k.a...zf.(...)...P..L....*R.....n.w ..0.q.:.@i.u..=J..#>..G<."
}
}
.....
{
"IP_header": {
"ip_version": 4,
"ipheader_length": 6,
"ip_length": 36,
"TTL": 1,
"xieyi": 2,
"ip_checksum": 33709,
"origin_ip": "192.168.0.1",
"destination_ip": "224.0.0.1"
},
"tans_header:": null
}

博主寄语

这篇博客博主花了比较多的心思,写博客的过程也是几次修改,并且加入了很多示意图,为了能够讲的更加清楚,我也特意招六很多图片,有一些是自己弄出来的,有的是网上找的。这已经不是一篇博客,而是充满博主心血的作品,希望读者细心阅读,如果有的地方出现了错误或者理解不清楚的,欢迎与博主华山论剑。希望读者们能够理解计算机网络的世界,探索世界的真理。

 

posted @ 2020-02-26 09:39  Comet_Fei  阅读(663)  评论(0编辑  收藏  举报