计算机网络主体框架
为降低设计的复杂性,增强通用性和兼容性,将计算机网络设计为层次化的结构。分层的核心在于上层功能需要建立在下层的基础实现之上,并在不同层之间实现不同的功能。目前OSI参考模型为当前网络层次的主流标准,通过一个抽象化的概念结构,将整体的计算机网络划分为7个不同的层次,对于这些层次在网络中扮演着不同的角色,实现不一样的功能。<br />OSI共有七层,如下图所示:<br /><br />对于不同层次中的协议数据单元(PDU),其中的叫法不相同:<br />物理层:比特流(也就是二进制数据流)<br />数据链路层:数据帧(Frame)<br /><br />网络层:数据包(Packet)<br /><br />传输层:数据段(Segment)<br />
TCP/IP协议簇
TCP/IP协议栈实际是遭遇OSI参考模型开发的,其主要包括各类不同功能的协议,如果TCP、UDP、ICMP、IP等协议,下图是针对TCP/IP协议簇的分布图:
由于TCP/IP协议栈总体协议较多,按照相应层次来对不同协议进行描述效果会比较好,所以下面将按照4层模型来介绍不同层次中的协议:
对于TCP/IP协议栈而言:
- TCP、UDP协议用于为网络进程做数据传输
- ICMP是网络控制报文协议,用于主机和网关进行差错报告、控制和进行请求/应答的报文
- ARP是地址转换协议,用于将IP地址转化为MAC地址
- IP协议是网络中的核心协议,负责生成发往目的地址数据包以及逻辑寻址,完成数据从网络的节点间数据传输
- IGMP是网络组管理协议,用于实现组播中的组成员管理
- RARP是反向地址转换协议,将MAC地址转化为IP地址
IP
IP地址在网络层中实现了底层网络地址的统一,使得网络层地址具有全局唯一性以及一致性。IP地址作为网络层中的寻址机制,是网络寻址和路由选择的依据。其作为网络层中的逻辑地址,具有很重要的地位。
IP层只负责数据的路由和传送,在源地址和目的地址之间传送数据报,但是并不会处理数据内容。
**
IP地址的格式
32位的二进制数字,一般将其分为四段,当前主要将其通过(网络号 + 子网号 + 主机号)来进行划分
IP数据格式
IP数据报格式主要包括首部和数据。首部部分为首部长度20个字节,最多可以到达60个字节。
- 版本表示IP协议的版本信息,版本号可以为4或者6
- 首部长度以及服务类型
- 总长度以及标识(标识用于为数据分片的数据单元提供唯一标识)和标志、分片位移
- 当IP数据报被分片时,每个数据分片仍然沿用该分片所属的IP数据报标识符,源主机可以通过源IP和标识判断分片属于那个数据报,进而完成重组
- 标志用于IP数据报是否允许分片以及是否是最后一片(有3位)
- 分片偏移描述数据报分片在所属原始数据报数据区中的偏移量,为目的主机进行分片重组提供顺序依据
- TTL生存时间,主要为了解决数据报长时间在网络中无法打到目的地从而消耗大量网络资源的问题。通过TTL。每经过一个路由器,生存时间就减去一定的值
- 协议号(指定具体的上层传输协议,也就是TCP、UDP传输层、ICMP)
-
IP协议的特性以及基本功能
特性:无连接、不可靠的、点对点的协议(保证数据传输可靠性是通过tcp协议实现的,差错检测和可靠传输),其效率相应就会更高
- 基本功能:寻址(路由实现,从发送端到接收端的路径选择) ;分片(对数据大小进行分片以及重组)
数据报的分片以及重组
- 首先底层物理网络具有一个能够封装最大数据长度的阈值,该阈值也称之为MTU(最大传输单元),数据报封装为帧必须要其总长度小于MTU(一般采用的协议是以太网,那么MTU为1500字节)
- 数据报分片,将IP数据报化为更小的单元,但是分片后每个片都会得到一个首部,这时需要通过IP首部中的标志、标识以及分片偏移字段进行区分;
- 重组需要采用一组重组定时器,如果超时说明重组失败并丢弃数据报,产生超时错误;重组只能在目的主机上进行(减轻路由器的负担)
IP软件实现
IP协议接受到上层传下的数据,首先添加IP首部,然后处理模块判断目的地址是否为环回地址,如果时环回地址则表示在本地,那么直接将数据重组会送到上一层即可;如果不为环回地址,经过路由选择以及分片将数据发送到网络接口层;
*显示拥塞通告
这种拥塞后因为重传和低链路使用而带来的吞吐量问题,是仅仅通过发送端来管理拥塞的结果。为了避免因为路由器拥塞而带来的丢包而产生的一系列问题,TCP/IP的设计者们创建了一些用于主机和路由器的标准。这些标准描述了在IP路由器上进行的主动队列管理算法(AQM),使得路由器能够监控转发队列的状态,以提供一个路由器向发送端报告发生拥塞的机制,让发送端在路由器开始丢包前降低发送速率。这种路由器报告和主机响应机制被称为显式拥塞通告;
需要在IP数据报首部源服务类型字段中使用最后两个位来设置ECN字段,一个是ECT字段、另一个是CE位字段。通过路由器设置,来表示是否发生了拥塞(11表示正在经历拥塞)
ARP和RARP
协议出现来由
IP地址到MAC物理地址之间的映射主要是通过静态映射和动态映射两种方法。
- 静态映射
主要是通过地址映射表来实现两者之间的地址映射,但是随着网络的变化地址之间的映射也是会发生改变的,所以就需要地址映射表的及时更新。而地址映射表主要通过手动方式来维护,那一适应频繁变化和规模较大的网络
- 动态映射
动态映射是自动维护逻辑地址与物理地址之间的映射关系,利用网络协议直接从其他节点来获得映射信息。这时候就需要ARP以及RARP来实现IP地址与MAC物理地址之间的映射。
ARP协议的报文格式
- 硬件类型用于定于物理网络类型,例如Ethernet、IEEE 802、帧中继等;
- 协议类型
- 操作码,用于区分ARP请求、应答等消息
ARP基本原理
1、当网络中的需要知道另一个节点的MAC地址时,需要发送ARP报文,这个报文包括发送方的IP地址和物理地址,以及接收方的IP地址。
2、由于发送方并不知道接收方的物理地址,那么需要在网络内进行广播,从而使得每个节点都会接受到ARP报文,将该报文中的IP地址与自身IP地址进行比较,只有相同IP地址的节点才会想查询者发会送ARP应答报文,应答报文中包括接收方ip地址以及mac地址(也就是先广播,再单播)
地址解析的过程:
- IP请求ARP产生ARP请求报文,填入发送方的mac地址和ip地址。目标物理地址字段写入0
- 报文发送到数据链路层,使用发送方物理地址作为源地址进行广播
- 之后每个主机或者路由器都受到这个帧。只有目标机器识别这个ip
- 目标机器会送ARP应答报文,采用单播方式
- 发送方收到目标放的mac地址
跨子网的地址解析:
只需要明白一点,ARP的广播采用的时物理网络中的广播,如果是跨子网的情况下,需要先将ip通过路由器进行转发,直到转发到目标主机对应的子网后,再封装为帧在数据链路层中进行广播。
ICMP
ICMP是网络控制报文协议,主要用于主机和网关进行差错报告、控制等。通过该协议可以实现网络的故障排查。一般对网络进行故障排查是从网络层开始,进而后续分析是网络层上层出现问题还是网络层下层出现问题。
ICMP报文格式
其首部位八个字节,具体如下图所示:
- 类型字段用于指定报文的形式,比如差错报告、查询报文等
- 代码表示报文类型的具体信息(例如报文类型为3,对于代码而言0表示网咯不可达,1表示主机不可达)
- TTL用于防止因路由表问题而无休止地在网络中传输,当TTL为0时,路由器会丢失当前的数据报
ICMP功能
差错报告
报告路由器或者目的主机在处理IP数据报时可能遇到的问题,可能引发的错误原因是通信线路故障、通信设备故障、路由器中路由表错误等等,一般表现为数据报不能到达目的地、超时或者拥塞。注意:其只能发现那些涉及到传输路径和可达相关的差错问题,并不能解决数据本身的问题
ICMP报文源抑制
当大量数据报进入到路由器或者目的主机时,会造成缓存区溢出也就是拥塞。ICMP利用源抑制方法进行拥塞控制,减缓源节点的数据报发送速率。该报文主要目的:
- 通知源节点数据报已被丢弃
- 警告发送方在路径中的某处出现了拥塞,因俄日源节点必须一直发送
- 源抑制报文的格式:
源抑制报文的三个阶段:
- 发现拥塞,路由器对缓冲区进行监测,一旦发现拥塞,立即向源节点发送ICMP源抑制报文
- 解决拥塞,源节点收到报文后对发往某个特定目标节点的数据流进行抑制,通常按照一定规则来降低该目标节点的数据传输速率
- 恢复阶段,拥塞解除后,源节点逐渐恢复数据报传输速率(未收到目标节点的源抑制报文,默认情况下大多数路由器并不会发送源抑制报文)
查询
用于查询帮助主机或者管理员从一个路由器或者主机得到特定的信息。一般这些报文是成对出现,一方主动向另一方发出查询请求,另一方查询结果报告给请求方
回送与回送应答报文
ICMP回送与回送应答报文使用相同的格式,如下图所示,类型8表明是回送(请求)报文,类型0表明是回送应答报文,代码都是0。标识符(Identifer)和序列号(Sequence)字段用于匹配回送应答报文与回送报文。这两个字段值在协议中没有正式定义,标识符一般为发起请求进程的进程ID,序列号在发送每一个回送(请求)报文时被增加。可供选择的数据字段包含一个消息,这个报文必须由接收方在回送应答报文中完全一样地重复着。
ping指令就是通过ICMP回送与回送应答报文来实现的
**
ICMP协议的应用
- 通过ping指令测试网络的联通性
- 分别ping目的主机、同一子网网关、环回地址
- 使用tracert用于跟踪路由,确定IP数据报访问目的主机所采取的路径(扩展了ping的功能,通过向目的地址发送具有不同生存时间的ICMP回送请求报文来确定到达目的地的路由)
以一个分层排查的实例来举例子
对于不同层次网络故障的排查方法图下:
- ping远程计算机。如果成功,说明系统网络正常,可以判断网络问题一般发生在更高层,转向服务或者应用程序测试,比如DNS域名解析是否正确等;如果失败,说明主机离线或者网络故障,继续往下走
- ping同一子网的网关来确定主机是否能够连接到本地网关。如果成功,说明本地网关与目标计算机之间的路由有问题,使用tracert命令进行跟踪测试路由;如果失败,继续下面
- ping环回地址127.0.0.1。如果成功,说明本地网关与本地计算机之间有问题,可以检查路线是否出错,IP地址的分配是否有问题;如果失败,需要确认电脑上TCP/IP协议软件是否有问题,有问题那么需要重装。
传输层协议TCP
TCP协议的特点:面向连接的、全双工的、可靠的、传输数据格式为字节流的形式,适合传输精度较高的场景。对于TCP的主要功能:
- 创建进程到进程的通信,进程即正在运行的应用程序。进程之间通过传输层进行通信
- 提供流量控制、差错控制
-
进程之间的通信
TCP/UDP协议都能提供进程之间的通信,通信过程需要通过一个三元组来标识这个进程(协议标识、主机地址以及端口号)。
为了区分不同进程之间的网络通信与连接,将这个三元组联合起来组成了一个Socket套接字,其提供了进程通信的端点,利用C/S模式解决了进程之间的通信连接问题,连接过程主要包括以下三个步骤:
源端口、目的端口(用于定位发送端的进程以及接收端的进程)
- 序列号(用于TCP数据段的唯一标识)
- 确认号(接收方期望从对方接受的序列号,指明下一次希望得到的序列号)
- 首部长度
- 标志位(URG(紧急指针是否有效)、ACK应答标志位、PSH直接数据推送、RST连接复位、SYN连接同步、FIN连接终止)
- 接收窗口大小(指明TCP接收方缓冲区的长度)
- 校验和
- 紧急指针(不必等待接收方缓冲区累计到一定程度,直接发送到对方)
- 选项:
- 最大段长度(MSS):定义接收方接受TCP的最大数据单元(默认536个字节)
- 窗口扩大因子:扩大滑动窗口的值
- 时间戳:可以测量往返时间;防止序列号绕回(TCP序列号只有32位,又可能在高速连接中序列号发生重复情况,联合序列号和时间戳标识整个报文段)
TCP建立连接
创建三次握手连接
主要目的:解决重复连接的问题,如果采用两次握手连接,那么实际网络中可能导致请求或者相应丢失,那么就需要通过超时重传来解决,但是重传确可能会导致重复连接问题
- 客户端向服务段发送连接请求,请求报文包括SYN= 1,以及序列号(进入到SYN-SEND状态)
- 服务端接收连接请求后,向客户端发送应答报文,其中包括序列号、确认号、SYN = 1以及ACK(服务端进入到SYN-RCVD状态)
- 客户端再向客户端发送一个TCP连接确认报文,ACK = 1、确认号以及序列号 (最后都进入到Established状态)
如果当服务端初于半开连接状态时,其一直没有接收到客户端发送的ACK确认报文。说明客户端丢失了网络连接或者是客户端发生了死机。但是服务端并不知道客户端发生了死机,那么服务段会不断发送SYN/ACK报文段,尝试完成三次握手过程,这样的话会大量消耗网络资源(SYN泛洪攻击就是利用了这一握手过程)
**
SYN洪泛攻击及其防范
三次握手过程中处于半开连接状态时会出现洪泛攻击。TCP服务端收到SYN而并没接到ACK时,会为每个SYN包分配一个特定的数据区。如果这时发送大量的SYN给服务器,那么就会给服务器造成很大的系统负担,最终导致系统不能正常工作;
- 直接攻击 : 攻击者阻止一切要到达服务器的数据包,阻止一切传入的包使SYN-ACK包在到达客户端时就被丢弃
- 欺骗式攻击:伪装源IP地址或者伪装许多的源地址
TCP连接完成后数据传输
数据传输的过程
客户端和服务端分别记录对方的序列号,主要是为了同步数据。但是数据的传输过程一般都是连续发送多个报文段,然后等待服务器的回应;之后再发送几段报文,再等待回应;推送数据
一般来讲,TCP中的数据传输是延迟传送以及延迟交付的,这样的话能够提高传输的效率,不需要每次接受到一个报文段就直接发送,而是达到缓冲区的发送阈值后才进行整体的发送。发送方TCP使用缓冲区来存储由发送方发送过来的数据流,接收方对其进行缓存,当应用程序就绪时才对这些数据进行交付。
对于TCP首部中包括PSH标志位,该标志位就是用来告知网络此报文段是否是立即发送的,直接交付给应用程序而不在缓冲区中停留。
紧急数据传输
发送紧急数据,希望该数据接受时并不按照发送的顺序一次接收。可以通过URG标志的报文段来实现,另外紧急指针也标识了紧急数据的结束位置以及正常数据的开始位置。当TCP接收方接受到带有URG置位的报文段后,利用紧急指针从报文段中提取紧急数据,并优先交付。
TCP关闭连接
四次握手关闭连接
- 客户端发送带有FIN标志位的报文段,包括序列号、确认号、ACK以及FIN (客户端进入到Fin-wait-1状态)
- 服务段回送应答报文,包括序列号、确认号以及ACK (服务端进入到Close-wait状态),此时进入到半关闭状态
- 当服务端任务处理完成之后,向客户端发送序列号、FIN以及ACK(客户端接受到服务器的确认报文后,进入到Fin-wait-2状态)
- 客户端接收到之后,向服务段发送确认报文,序列号、确认号以及ACK (进入到time-wait状态)
半关闭状态
发送方结束了它的发送,但是还能接受到接收方对它的应答
TIME-WAIT状态
在发送方进入到该状态之后,还需要等待2个MSL时间,目的是保证发送的ACK段能够成功发送到对方。2MSL使得客户端等待足够长的时间,使得就算是ACK丢失了还可以等到下一个FIN的到来
TCP可靠性
差错控制
主要用于检测损坏的报文段、丢失的报文段、失序的报文段以及重复的报文段。
- 校验和:检车是否出现损坏的报文段
- 确认 : 当收到一个序列号比期望的序列号要大时,立即发送ACK报文段,让对方重传;收到重复报文段,发送ACK报文进行确认
- 重传 :
- 超时重传 : 为每一个报文段设置一个超时重传计时器,时间到之后就开始进行重传
- 快重传 : 采用三个重复的ACK报文段之后再进行重传的规则,发送方只要接受到了3个重传的ACK报文段,那么立刻重传这个丢失的报文段
流量控制
在TCP层实现端到端的流量控制,为接收方对发送方发送数据进行控制,以避免大量的数据导致接收方瘫痪,主要通过滑动窗口来实现。
滑动窗口机制:(滑动窗口的大小就是socket的接收缓冲区大小的字节数)
在缓冲区中使用缓冲窗口,TCP发送的数据多少 有这个滑动窗口来定义。
发送方缓冲区包括:已发送并被确认的数据、已发送但没有确认、可以立即发送、暂时不能发送这四个部分所组成。具体使用滑动窗口的过程为:
- TCP连接,双方协商窗口大小,同时接收方预留数据缓存区
- 发送方根据协商结果,发送窗口大小的数据字节流,等待接收方确认
- 发送方根据确认信息,改变窗口大小,增加或减少发送字节数(根据是否出现网络拥塞或者数据丢失等问题来决定)
但是滑动窗口可能会出现发送端数据产生很慢或者接收端数据消耗很慢,都会使得发送数据的报文很小,从而导致效率的降低,这个问题叫做糊涂窗口问题
发送方产生的糊涂窗口问题
为防止TCP逐个字节发送数据,强迫发送方TCP等待并凑成大块数据再发送,采用Nagle算法解决此问题:**
- 先发第一块数据
- 之后在发送方缓冲区中累计数据,直到达到一定的数据之后才进行发送
*Nagle算法可能会出现拆包粘包的问题
拆包原因:数据包大于发送缓冲区大小或者最大报文大小;
粘包原因:数据报小于发送缓冲区或者小于接受缓冲区大小;
解决方案 : 给每个数据报添加边界信息
- 发送端给每个数据报添加首部,首部中标注数据报的长度
- 发送端将每个数据报封装成固定长度
- 在数据包之间设置特殊符号作为分隔符
接收方出现的糊涂窗口问题
接收缓存区已满,每消耗掉一个字节的数据就会新添加一个字节的数据,解决方法:
- 延迟通告:只要有数据到达就发送确认,但是在缓冲区有足够大的空间放入MSS之前,一直都宣布窗口值为0;
- 推迟确认:当报文到达时并不立即发送确认,接收方在对收到报文段进行确认之前一直等待,直到输入缓冲区有足够空间为止;
拥塞控制
流量控制是由于接收方不能即使处理数据而引发的控制机制,拥塞控制是由于网络中的路由超载而引起严重延迟的现象。拥塞控制主要有四个控制机制:慢启动、拥塞避免、快重传、快恢复
- 慢启动: 首先使用慢启动在建立连接时将拥塞窗口设置为MSS(最大报文段大小),接着按照指数方式进行增长,达到慢启动阈值之后,转而进入到拥塞避免阶段
- 拥塞避免 : 当拥塞窗口达到慢开始阈值后,进入到该阶段。词是拥塞窗口时进行线行增长的,知道拥塞被检测到;
- 快重传是属于拥塞检测部分 : 发送方判断拥塞已经发生的唯一方法就是它必须重传一个报文段。但是重传发生存在两种情况,超时或者接收到三个重复的ACK
- 如果接受到三个重复的ACK报文,那么说明出现拥塞的可能比较小,那么就直接进行快重传与快恢复,直接将阈值设为当前拥塞窗口的一半
- 如果发生超时,说明很大可能出现了拥塞问题,那么直接将拥塞窗口设置为1,重新进入到慢开始阶段
DNS域名解析
- 操作系统先检查本地hosts文件是否有这个网址的映射关系
- 如果没有域名的映射,查找本地DNS解析器缓存
- 如果缓存中没有,那么通过DNS解析器进行递归查询,首先是通过根域名服务器进行解析,然后是顶级域名服务器、之后部分域名解析
Socket编程
socket编程主要考点在于如何通过TCP建立可靠连接后,然后再进行数据通信。使用TCP协议的流程图为:
服务器端 : socket ——- bind ——- listen —— while (1) { accpet —-recv —— send —— close} —- close
客户端 : socket ——- bind ——————————————-connect ——- send ——- recv —— close
对于服务器端而言:
- 首先需要创建socket套接字进行TCP之间的系统调用
- 然后调用bind()方法绑定该socket套接字中对应的ip地址以及端口号
- 服务端执行listen函数,实现监听服务
- 后续调用accept()函数,该函数从已完成的连接队列返回下一个建立成功的连接,如果已完成的连接为空,那么该线程实际上是进入到阻塞状态;
- accpet()成功后会返回一个socket描述符,表示已连接套接字
- 在连接建立完成之后,那么就可以进行cs之间的数据发送
- 服务端调用listen函数后,会在内核空间中创建两个队列,分别为:SYN队列以及accept队列
- 服务端在调用accpet之后,将阻塞等待accpet队列中直到存在元素
- 客户端进而调用connect函数,发起连接SYN请求,此时为第一次连接
- 服务端在接收到SYN请求之后,把请求方放入SYN队列中,并给客户端回送一个ACK确认报文
- 客户端在接到SYN + ACK报文之后,connect连接返回并发送确认建立连接帧ACK给服务端
- 服务端在接收到第三次应答报文后,会将请求方从SYN队列中拿出并放入到accept队列,accept函数被唤醒并取出accept队列中的请求方,重新建立一个新的sock文件描述符 (注意accept函数必须要建立连接之后才能被调用)