一  Linux配置静态路由

比如linux有双网口或网口加wifi俩个网路不同网段还要保持上网就需要如下

1. Ubuntu16

# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto ens33
iface ens33 inet static
...
up route add -net 192.168.1.0 netmask 255.255.255.0 gw 10.0.0.4 dev ens33
$ sudo /etc/init.d/networking restart

2. Ubuntu20

$ cat /etc/netplan/01-network-manager-all.yaml 
# Let NetworkManager manage all devices on this system
network:
  version: 2
  renderer: NetworkManager
  ethernets:
    eno1:
      ...
      routes:
      - to: 192.168.1.0/24
        via: 10.0.0.4
$ sudo netplan apply
$ ip route show
...
192.168.1.0/24 via 10.0.0.4 dev eno1 proto static metric 100 
...

二 TCP和UDP协议的原理和区别

tcp和udp都是工作再传输层,用于程序之间传输数据的。数一般包含:文件类型,视频类型,jpg图片等

linux网路相关_服务器

区别

    TCP是基于连接的,而UDP是基于非连接的。

    tcp传输数据稳定可靠,适用于对网络通讯质量要求较高的场景,需要准确无误的传输给对方,比如,传输文件,发送邮件,浏览网页等等。

    udp的优点是速度快,但是可能产生丢包,所以适用于对实时性要求较高但是对少量丢包并没有太大要求的场景。比如:域名查询,语音通话,视频直播等。udp还有一个非常重要的应用场景就是隧道网络,比如:vpn,VXLAN。

    以人与人之间的通信为例:UDP协议就相当于是写信给对方,寄出去信件之后不能知道对方是否收到信件,信件内容是否完整,也不能得到及时反馈,而TCP协议就像是打电话通信,在这一系列流程都能得到及时反馈,并能确保对方及时接收到。如下图:

linux网路相关_数据_02

TCP通信的过程

    tcp是如何保证以上过程的:分为三个步骤,三次握手,传输确认,四次挥手。三次握手是建立连接的过程。

三次握手

    当客户端向服务端发起连接时,会先发一包连接请求数据,过去询问一下,能否与你建立连接?这包数据称之为SYN包,如果对端同意连接,则回复一包SYN+ACK包,客户端收到之后,发送一包ACK包,连接建立,因为这个过程中互相发送了三包数据,所以称之为三次握手。

linux网路相关_服务器_03

为什么要三次握手而不是两次握手?

    **这是为了防止,因为已失效的请求报文,突然又传到服务器,引起错误,**

    这是什么意思?

    假设采用两次握手建立连接,客户端向服务端发送一个syn包请求建立连接,因为某些未知的原因,并没有到达服务器,在中间某个网络节点产生了滞留,为了建立连接,客户端会重发syn包,这次的数据包正常送达,服务端发送syn+ack之后就建立起了连接,但是第一包数据阻塞的网络突然恢复,第一包syn包又送达到服务端,这是服务端会认为客户端又发起了一个新的连接,从而在两次握手之后进入等待数据状态,服务端认为是两个连接,而客户端认为是一个连接,造成了状态不一致,如果在三次握手的情况下,服务端收不到最后的ack包,自然不会认为连接建立成功,所以三次握手本质上来说就是为了解决网络信道不可靠的问题,为了在不可靠的信道上建立起可靠的连接,经过三次握手之后,客户端和服务端都进入了数据传输状态。

数据传输

    一包数据可能会被拆成多包发送,如何处理丢包问题,这些数据包到达的先后顺序不同,如何处理乱序问题?针对这些问题,tcp协议为每一个连接建立了发送缓冲区,从建立链接后的第一个字节的序列号为0,后面每个字节的序列号就会增加1,发送数据时,从数据缓冲区取一部分数据组成发送报文,在tcp协议头中会附带序列号和长度,接收端在收到数据后需要回复确认报文,确认报文中的ack等于接受序列号加长度,也就是下包数据发送的起始序列号,这样一问一答的发送方式,能够使发送端确认发送的数据已经被对方收到,发送端也可以发送一次的连续的多包数据,接受端只需要回复一次ack就可以了如图:

 

linux网路相关_运维_04

四次挥手 

linux网路相关_DNS_05

处于连接状态的客户端和服务端,都可以发起关闭连接请求,此时需要四次挥手来进行连接关闭,假设客户端主动发起连接关闭请求,他给服务端发起一包FIN包,标识要关闭连接,自己进入终止等待1装填,服务端收到FIN包,发送一包ACK包,标识自己进入了关闭等待状态,客户端进入终止等待2状态。这是第二次挥手,服务端此时还可以发送未发送的数据,而客户端还可以接受数据,待服务端发送完数据之后,发送一包FIN包,最后进入确认状态,这是第3次挥手,客户端收到之后恢复ACK包,进入超时等待状态,经过超时时间后关闭连接,而服务端收到ACK包后,立即关闭连接,这是第四次挥手。为什么客户端要等待超时时间这是为了保证对方已经收到ACK包,因为假设客户端发送完最后一包ACK包后释放了连接,一旦ACK包在网络中丢失,服务端将一直停留在 最后确认状态,如果等待一段时间,这时服务端会因为没有收到ack包重发FIN包,客户端会响应 这个FIN包进行重发ack包,并刷新超时时间,这个机制跟第三次握手一样。也是为了保证在不可靠的网络链路中进行可靠的连接断开确认。

UDP协议

 udp:首先udp协议是非连接的,发送数据就是把简单的数据包封装一下,然后从网卡发出去就可以了,数据包之间并没有状态上的联系,正因为udp这种简单的处理方式,导致他的性能损耗非常少,对于cpu,内存资源的占用也远小于tcp,但是对于网络传输过程中产生的丢包,udp并不能保证,所以udp在传输稳定性上要弱于tcp,所以,tcp和udp的主要却别:tcp传输数据稳定可靠,适用于对网络通讯质量要求较高的场景,需要准确无误的传输给对方,比如,传输文件,发送邮件,浏览网页等等,udp的优点是速度快,但是可能产生丢包,所以适用于对实时性要求较高但是对少量丢包并没有太大要求的场景。比如:域名查询,语音通话,视频直播等。udp还有一个非常重要的应用场景就是隧道网络,比如:vpn,VXLAN。

linux网路相关_运维_06


三、为什么 DNS 根服务器只有 13 台

在分享个网路知识

 域名系统是最重要的互联网服务之一,没有它,我们将无法访问在线内容,甚至无法发送电子邮件。每当我们尝试连接到其他网站或在线服务时,根 DNS 服务器都会帮助我们的计算机找到并到达我们想要的地址。

    DNS 根服务器是所有 DNS 的组成部分,因此也是 Internet 的组成部分,但关于它的信息并不多。今天我们将学习什么是根服务器?它们的作用是什么?以及实际上有多少根服务器?

什么是 DNS 根服务器?

    根服务器或 DNS 根服务器是负责 DNS 和整个 Internet 功能的名称服务器,它们是确定任何域名名称的第一步,DNS可以将域名转换为 IP 地址。

    根区是顶级域的全局列表,它包含通用顶级域(.com、.net、.org)、国家代码顶级域(.no、.se、.uk)和国际化的顶级域名,即以各国本地字符编写的 ccTLD,根区来自互联网号码分配机构 (IANA),它是互联网名称与数字地址分配机构 (ICANN) 的一部分。

    域名到 IP 地址的映射是使用 DNS 区域以分层顺序完成的,根服务器为根区域提供服务,根区域位于层次结构的顶部并发布根区域文件,根区域文件又包含所有 TLD 授权服务器的资源记录。因此,它们可以通过两种方式工作:

  • 直接响应对根区域中的此资源记录的请求
  • 将请求转发到所请求 TLD 的适当名称服务器

    尽管在后一种情况下它们不直接参与名称解析,但根服务器位于整个 Internet 基础结构的根目录中,没有它们,我们今天所了解和使用的大部分网络都将无法使用。

根服务器如何工作?

    根服务器的工作方式取决于名称解析过程:

linux网路相关_数据_07

  • 当您在 Web 浏览器中输入 www.wljslmz.com 时,它会首先迁移到您的 ISP 的 DNS 服务器或您配置的其他 DNS 服务器。有时,这些 DNS 服务器会缓存域信息,在这种情况下,它只回答信息并让您可以访问该网站。
  • 但是,如果未存储此信息,则 DNS 服务器会向主服务器发送请求,根服务器没有关于 www.wljslmz.com 的特定 IP 地址的信息,但它们知道为该 TLD (.com) 提供服务的名称服务器的位置。
  • 根服务器返回一个 TLD 服务器列表,以便提供者或配置的服务器可以重新发送请求,这次是到 TLD 服务器。
  • TLD 服务器然后返回存储所需域的授权名称服务器。
  • 当请求服务器向域所在的授权服务器发送请求时就是这种情况。
  • 一旦请求到达授权服务器,它就会用 www.wljslmz.com 的 IP 地址回复请求服务器
  • 当请求服务器获得此信息时,它会将其保存以备将来请求,并将响应发送回您的转换器,转换器将其发送到您的 Web 浏览器并允许您访问所需的网站。

有多少根 DNS 服务器?

    这个大家都知道,一共有13台,为什么只有 13 个根服务器?

    这是由于本地 DNS 基础设施的限制,它使用的是 32 字节 IPv4,IP 地址必须对应一个数据包,当时数据包限制为 512 字节,所以每个IPv4地址是32位,其中13位是416字节,剩下的96字节是协议信息。

    起初,13 个 IP 地址中的每一个都有一个服务器,今天我们为每个 IP 地址赋予一个服务器集群,使用广播路由创建了一个由全球数百台服务器组成的网络。

    这有助于平衡去中心化并确保可靠性,即使其中一个根服务器由于 DDoS 攻击或其他类型的 DNS 攻击也可以保证网络的可用性。

    然而,由于 IPv6 的小数据包大小限制,因此几乎可以肯定,未来会出现更多的根 DNS 服务器。

根 DNS 服务器位于何处?

linux网路相关_服务器_08

中国DNS情况

    13个DNS根服务器为啥不能分一台给中国?

    这个问题,相信不用回答了,根本不可能,不是政治问题,而是技术问题,本身已经超出协议范围能力了,已经有根DNS的组织是不可能让给中国的。

    在中国,一共有26个根域名服务器节点:

  • 北京市I、L、J、K、F
  • 香港特别行政区A、I、H、F、F、E、J
  • 台北市I、E、F、F、K、L
  • 澳门特别行政区E、F
  • 上海市L
  • 杭州市F
  • 郑州市L
  • 武汉市L
  • 西宁市L
  • 贵阳市K

免费公共DNS服务器IP地址(常用部分)

    114 DNS

首选:114.114.114.114 备选:114.114.115.115

    AliDNS 阿里公共 DNS

首选:223.5.5.5 备用:223.6.6.6

    百度 BaiduDNS

IPv4 地址:180.76.76.76 IPv6 地址:2400:da00::6666

    Public DNS+

首选:119.29.29.29 备用:119.28.28.28 备用:182.254.118.118 备用:182.254.116.116

    CNNIC sDNS

首选:1.2.4.8 备用:210.2.4.8

    清华大学 DNS 服务器

2001:da8:200:ffff::28 上海交大 DNS 服务器 2001:da8:8000:1:202:120:2:101

    北京邮电大学 DNS 服务器

2001:da8:202:10::36  或  2001:da8:202:10::37

    等等,还有很多,等有机会给大家整理一个全的。

我们在DNS根领域还有机会吗?

    我们都知道,网络掌握在其他国家,安全性会受到极大的安全风险,我们错过了13个根服务器,难道永远都要“寄人篱下”吗?

    当然不是!

    虽然我们不能在ipv4领域拥有根DNS服务器,但是我们在ipv6的根DNS服务器中已经占有一席之地了。随着国家对ipv6的支持,中国在ipv6网络建设上已经有了惊人的速度!

    随之而来的ipv6根DNS服务器中,中国拥有4台服务器,其中,一台为主根,其他三台为辅根!!!

总结

    DNS 负责几乎所有与 Internet 相关的事情,根 DNS 服务器是互联网的支柱。

    希望通过本文,大家能够对于13个DNS根服务器有所了解。

    在安全方面,DNS 仍然是企业基础设施中最容易被忽视的部分之一,这就是频繁执行 DNS 审计如此重要的原因。

    重视技术的本质,才能立足于技术公司之本!!!

四、TCP总结

/ 计网分层结构  / 

    考虑最简单的情况:两台主机之间的通信。这个时候只需要一条网线把两者连起来,规定好彼此的硬件接口,如都用USB、电压10v、频率2.4GHz等,这一层就是物理层,这些规定就是物理层协议 。

linux网路相关_服务器_09

    我们当然不满足于只有两台电脑连接,因此我们可以使用交换机把多个电脑连接起来,如下图:

linux网路相关_运维_10

这样连接起来的网络,称为局域网,也可以称为以太网(以太网是局域网的一种)。在这个网络中,我们需要标识每个机器,这样才可以指定要和哪个机器通信。这个标识就是硬件地址MAC。硬件地址随机器的生产就被确定,永久性唯一。在局域网中,我们需要和另外的机器通信时,只需要知道他的硬件地址,交换机就会把我们的消息发送到对应的机器。

    这里我们可以不管底层的网线接口如何发送,把物理层抽离,在他之上创建一个新的层次,这就是数据链路层 。

    我们依然不满足于局域网的规模,需要把所有的局域网联系起来,这个时候就需要用到路由器来连接两个局域网:

linux网路相关_运维_11

 但是如果我们还是使用硬件地址来作为通信对象的唯一标识,那么当网络规模越来越大,需要记住所有机器的硬件地址是不现实的;同时,一个网络对象可能会频繁更换设备,这个时候硬件地址表维护起来更加复杂。这里使用了一个新的地址来标记一个网络对象:IP地址 。

    通过一个简单的寄信例子来理解IP地址。

    我住在北京市,我朋友A住在上海市,我要给朋友A写信:

  1. 写完信,我会在信上写好我朋友A的地址,并放到北京市邮局(给信息附加目标IP地址,并发送给路由器)
  2. 邮局会帮我把信运输到上海市当地邮局(信息会经过路由传递到目标IP局域网的路由器)
  3. 上海市当地路由器会帮我把信交给朋友A(局域网内通信)

    因此,这里IP地址就是一个网络接入地址(朋友A的住址),我只需要知道目标IP地址,路由器就可以把消息给我带到。在局域网中,就可以动态维护一个MAC地址与IP地址的映射关系,根据目的IP地址就可以寻找到机器的MAC地址进行发送 。

    这样我们不需管理底层如何去选择机器,我们只需要知道IP地址,就可以和我们的目标进行通信。这一层就是网络层。网络层的核心作用就是 提供主机之间的逻辑通信 。这样,在网络中的所有主机,在逻辑上都连接起来了,上层只需要提供目标IP地址和数据,网络层就可以把消息发送到对应的主机。

    一个主机有多个进程,进程之间进行不同的网络通信,如边和朋友开黑边和女朋友聊微信。我的手机同时和两个不同机器进行通信。那么当我的手机收到数据时,如何区分是微信的数据,还是王者的数据?那么就必须在网络层之上再添加一层:运输层 :

linux网路相关_运维_12

 运输层通过socket(套接字),将网络信息进行进一步的拆分,不同的应用进程可以独立进行网络请求,互不干扰。这就是运输层的最本质特点:提供进程之间的逻辑通信 。这里的进程可以是主机之间,也可以是同个主机,所以在android中,socket通信也是进程通信的一种方式。

    现在不同的机器上的应用进程之间可以独立通信了,那么我们就可以在计算机网络上开发出形形式式的应用:如web网页的http,文件传输ftp等等。这一层称为应用层。

    应用层还可以进一步拆分出表示层、会话层,但他们的本质特点都没有改变:完成具体的业务需求 。和下面的四层相比,他们并不是必须的,可以归属到应用层中。

    最后对计网分层进行小结:

linux网路相关_DNS_13

  1. 最底层物理层,负责两个机器之间通过硬件的直接通信;
  2. 数据链路层使用硬件地址在局域网中进行寻址,实现局域网通信;
  3. 网络层通过抽象IP地址实现主机之间的逻辑通信;
  4. 运输层在网络层的基础上,对数据进行拆分,实现应用进程的独立网络通信;
  5. 应用层在运输层的基础上,根据具体的需求开发形形式式的功能。

    这里需要注意的是,分层并不是在物理上的分层,而是逻辑上的分层。通过对底层逻辑的封装,使得上层的开发可以直接依赖底层的功能而无需理会具体的实现,简便了开发。

    这种分层的思路,也就是责任链设计模式,通过层层封装,把不同的职责独立起来,更加方便开发、维护等等。okHttp中的拦截器设计模式,也是这种责任链模式。

/  运输层  /

    本文主要是讲解TCP,这里需要增加一些运输层的知识。

 本质:提供进程通信

linux网路相关_运维_14

 在运输层之下的网络层,是不知道该数据包属于哪个进程,他只负责数据包的接收与发送。运输层则负责接收不同进程的数据交给网络层,同时把网络层的数据拆分交给不同的进程。从上往下汇聚到网络层,称为多路复用,从下往上拆分,称为多路拆分 。

    运输层的表现,受网络层的限制。这很好理解,网络层是运输层的底层支持。所以运输层是无法决定自己带宽、时延等的上限。但可以基于网络层开发更多的特性:如可靠传输。网络层只负责尽力把数据包从一端发送到另一端,而不保证数据可以到达且完整。

底层实现:socket

    前面讲到,最简单的运输层协议,就是提供进程之间的独立通信 ,但底层的实现,是socket之间的独立通信 。在网络层中,IP地址是一个主机逻辑地址,而在运输层中,socket是一个进程的逻辑地址;当然,一个进程可以拥有多个socket。应用进程可以通过监听socket,来获取这个socket接受到的消息。

linux网路相关_数据_15

  socket并不是一个实实在在的东西,而是运输层抽象出来的一个对象。运输层增加了端口这个概念,来区分不同的socket。端口可以理解为一个主机上有很多的网络通信口,每个端口都有一个端口号,端口的数量由运输层协议确定。

    不同的运输层协议对socket有不同的定义方式。在UDP协议中,使用目标IP+目标端口号来定义一个socket;在TCP中使用目标IP+目标端口号+源IP+源端口号来定义一个socket。我们只需要在运输层报文的头部附加上这些信息,目标主机就会知道我们要发送给哪个socket,对应监听该socket的进程就可获得信息。

运输层协议

    运输层的协议就是大名鼎鼎的TCP和UDP。其中,UDP是最精简的运输层协议,只实现了进程间的通信;而TCP在UDP的基础上,实现了可靠传输、流量控制、拥塞控制、面向连接等等特性,同时也更加复杂。

    当然除此之外,还有更多更优秀的运输层协议,但目前广为使用的,就是TCP和UDP。UDP在后面也会总结到。

/   TCP协议首部   /

    TCP协议,表现在报文上,就是会在应用层传输下来的数据前附加上一个TCP首部,这个首部附加了TCP信息,先来整体看一下这个首部的结构:

linux网路相关_运维_16

 这张图是来自我大学老师的课件, 非常好用,所以一直拿来学习。最下面部分表示了报文之间的关系,TCP数据部分就是应用层传下来的数据。

    TCP首部固定长度是20字节,下面还有4字节是可选的。内容很多,但其中有一些我们比较熟悉的:源端口,目标端口。嗯?socket不是还需要IP进行定位吗?IP地址在网络层被附加了。其他的内容后面都会慢慢讲解,作为一篇总结文章,这里放出查阅表,方便复习:

linux网路相关_运维_17

linux网路相关_服务器_18

    选项字段中包含以下其他选项:

linux网路相关_DNS_19

讲完下面内容,再回来看这些字段就熟悉了。

/   TCP面向字节流特性   /

    TCP并不是把应用层传输过来的数据直接加上首部然后发送给目标,而是把数据看成一个字节 流,给他们标上序号之后分部分发送。这就是TCP的 面向字节流 特性:

linux网路相关_数据_20

  • TCP会以流的形式从应用层读取数据并存放在自己的发送缓存区中,同时为这些字节标上序号
  • TCP会从发送方缓冲区选择适量的字节组成TCP报文,通过网络层发送给目标
  • 目标会读取字节并存放在自己的接收方缓冲区中,并在合适的时候交付给应用层

    面向字节流的好处是无需一次存储过大的数据占用太多内存,坏处是无法知道这些字节代表的意义,例如应用层发送一个音频文件和一个文本文件,对于TCP来说就是一串字节流,没有意义可言,这会导致粘包以及拆包问题,后面讲。

/   可靠传输原理   /

    前面讲到,TCP是可靠传输协议,也就是,一个数据交给他,他肯定可以完整无误地发送到目标地址,除非网络炸了。他实现的网络模型如下:

linux网路相关_DNS_21

 对于应用层来说,他就是一个可靠传输的底层支持服务;而运输层底层采用了网络层的不可靠传输。虽然在网络层甚至数据链路层就可以使用协议来保证数据传输的可靠性,但这样网络的设计会更加复杂、效率会随之降低。把数据传输的可靠性保证放在运输层,会更加合适。

    可靠传输原理的重点总结一下有:滑动窗口、超时重传、累积确认、选择确认、连续ARQ 。

停止等待协议

    要实现可靠传输,最简便的方法就是:我发送一个数据包给你,然后你跟我回复收到,我继续发送下一个数据包。传输模型如下:

linux网路相关_运维_22

   这种“一来一去”的方法来保证传输可靠就是停止等待协议(stop-and-wait)。不知道还记不记得前面TCP首部有一个ack字段,当他设置为1的时候,表示这个报文是一个确认收到报文。

    然后再来考虑一种情况:丢包。网络环境不可靠,导致每一次发送的数据包可能会丢失,如果机器A发送了数据包丢失了,那么机器B永远接收不到数据,机器A永远在等待。解决这个问题的方法是:超时重传 。当机器A发出一个数据包时便开始计时,时间到还没收到确认回复,就可以认为是发生了丢包,便再次发送,也就是重传。

    但重传会导致另一种问题:如果原先的数据包并没有丢失,只是在网络中待的时间比较久,这个时候机器B会受到两个数据包,那么机器B是如何辨别这两个数据包是属于同一份数据还是不同的数据?这就需要前面讲过的方法:给数据字节进行编号。这样接收方就可以根据数据的字节编号,得出这些数据是接下来的数据,还是重传的数据。

    在TCP首部有两个字段:序号和确认号,他们表示发送方数据第一个字节的编号,和接收方期待的下一份数据的第一个字节的编号。前面讲到TCP是面向字节流,但是他并不是一个字节一个字节地发送,而是一次截取一整段。截取的长度受多种因素影响,如缓存区的数据大小、数据链路层限制的帧大小等。

连续ARQ协议

    停止等待协议已经可以满足可靠传输了,但有一个致命缺点:效率太低。发送方发送一个数据包之后便进入等待,这个期间并没有干任何事,浪费了资源。解决的方法是:连续发送数据包。模型如下:

linux网路相关_DNS_23

  和停止等待最大的不同就是,他会源源不断地发送,接收方源源不断收到数据之后,逐一进行确认回复。这样便极大地提高了效率。但同样,带来了一些额外的问题:

    发送是否可以无限发送直到把缓冲区所有数据发送完?不可以。因为需要考虑接收方缓冲区以及读取数据的能力。如果发送太快导致接收方无法接受,那么只是会频繁进行重传,浪费了网络资源。所以发送方发送数据的范围,需要考虑到接收方缓冲区的情况。这就是TCP的流量控制 。解决方法是:滑动窗口 。基本模型如下:

linux网路相关_DNS_24

  • 发送方需要根据接收方的缓冲区大小,设置自己的可发送窗口大小,处于窗口内的数据表示可发送,之外的数据不可发送。
  • 当窗口内的数据接收到确认回复时,整个窗口会往前移动,直到发送完成所有的数据

    在TCP的首部有一个窗口大小字段,他表示接收方的剩余缓冲区大小,让发送方可以调整自己的发送窗口大小。通过滑动窗口,就可以实现TCP的流量控制,不至于发送太快,导致太多的数据丢失。

    连续ARQ带来的第二个问题是:网络中充斥着和发送数据包一样数据量的确认回复报文,因为每一个发送数据包,必须得有一个确认回复。提高网络效率的方法是:累积确认 。接收方不需要逐个进行回复,而是累积到一定量的数据包之后,告诉发送方,在此数据包之前的数据全都收到。例如,收到 1234,接收方只需要告诉发送方我收到4了,那么发送方就知道1234都收到了。

    第三个问题是:如何处理丢包情况。在停止等待协议中很简单,直接一个超时重传就解决了。但,连续ARQ中不太一样。例如:接收方收到了 123 567,六个字节,编号为4的字节丢失了。按照累积确认的思路,只能发送3的确认回复,567都必须丢掉,因为发送方会进行重传。这就是GBN(go-back-n) 思路。

    但是我们会发现,只需要重传4即可,这样不是很浪费资源,所以就有了:选择确认SACK 。在TCP报文的选项字段,可以设置已经收到的报文段,每一个报文段需要两个边界来进行确定。这样发送方,就可以根据这个选项字段只重传丢失的数据了。

可靠传输小结

    到这里关于TCP的可靠传输原理就已经介绍的差不多。最后进行一个小结:

  • 通过连续ARQ协议与发送-确认回复模式来保证每一个数据包都到达接收方
  • 通过给字节编号的方法,来标记每一个数据是属于重传还是新的数据
  • 通过超时重传的方式,来解决数据包在网络中丢失的问题
  • 通过滑动窗口来实现流量控制
  • 通过累积确认+选择确认的方法来提高确认回复与重传的效率

    当然,这只是可靠传输的冰山一角,但是和面试官聊天已经差不多了,感兴趣可以再深入去研究。

/   拥塞控制   /

    拥塞控制考虑的是另外一个问题:避免网络过分拥挤导致丢包严重,网络效率降低 。

    拿现实的交通举例子:

    高速公路同一时间可通行的汽车数量是一定的,当节假日时,就会发生严重的堵车。在TCP中,数据包超时,会进行重传,也就是会进来更多的汽车,这时候更堵,最后导致的结果就是:丢包-重传-丢包-重传。最后整个网络瘫痪了。

    这里的拥塞控制和前面的流量控制不是一个东西,流量控制是拥塞控制的手段:为了避免拥塞,必须对流量进行控制。拥塞控制目的是:限制每个主机的发送的数据量,避免网络拥塞效率下降。就像广州等地,限制车牌号出行是一个道理。不然大家都堵在路上,谁都别想走。

    拥塞控制的解决方法是流量控制,流量控制的实现是滑动窗口,所以拥塞控制最终也是通过限制发送方的滑动窗口大小来限制流量 。当然,拥塞控制的手段不只是流量控制,导致拥塞的因素有:路由器缓存、带宽、处理器处理速度等等。提升硬件能力(把4车道改成8车道)是其中一个方法,但毕竟硬件提升是有瓶颈的,没办法不断提升,还是需要从tcp本身来增加算法,解决拥塞。

    拥塞控制的重点有4个:慢开始、快恢复、快重传、拥塞避免。这里依旧献祭出大学老师的ppt图片:

linux网路相关_数据_25

   Y轴表示的是发送方窗口大小,X轴表示的是发送的轮次(不是字节编号)。

  • 最开始的时候,会把窗口设置一个较小的值,然后每轮变为原来的两倍。这是慢开始。
  • 当窗口值到达ssthresh值,这个值是需要通过实时网络情况设置的一个窗口限制值,开始进入拥塞避免,每轮把窗口值提升1,慢慢试探网络的底线。
  • 如果发生了数据超时,表示极可能发生了拥塞,然后回到慢开始,重复上面的步骤。
  • 如果收到三个相同的确认回复,表示现在网络的情况不太好,把ssthresh的值设置为原来的一半,继续拥塞避免。这部分称为快恢复。
  • 如果收到丢包信息,应该尽快把丢失的包重传一次,这是快重传。
  • 当然,窗口的最终上限是不能无限上涨的,他不能超过接收方的缓存区大小。

    通过这个算法,就可以在很大程度上,避免网络拥挤。

    除此之外,还可以让路由器在缓存即将满的时候,告知发送方我快满了,而不是等到出现了超时再进行处理,这是主动队列管理AQM。此外还有很多方法,但是上面的算法是重点。

/   面向连接   /

    这一小节讲的就是无人不晓的TCP三次握手与四次挥手这些,经过前面的内容,这一小节其实已经很好理解。

    TCP是面向连接的,那连接是什么?这里的连接并不是实实在在的连接,而是通信双方彼此之间的一个记录 。TCP是一个全双工通信,也就是可以互相发送数据,所以双方都需要记录对方的信息。根据前面的可靠传输原理,TCP通信双方需要为对方准备一个接收缓冲区可以接收对方的数据、记住对方的socket知道怎么发送数据、记住对方的缓冲区来调整自己的窗口大小等等,这些记录,就是一个连接。

    在运输层小节中讲到,运输层双方通信的地址是采用socket来定义的,TCP也不例外。TCP的每一个连接只能有两个对象,也就是两个socket,而不能有三个。所以socket的定义需要源IP、源端口号、目标IP、目标端口号四个关键因素,才不会发生混乱。

假如TCP和UDP一样只采用目标IP+目标端口号来定义socket,那么就会出现多个发送方同时发送到同一个目标socket的情况。这个时候TCP无法区分这些数据是否来自不同的发送方,就会导致出现错误。

    既然是连接,就有两个关键要点:建立连接、断开连接。

建立连接

    建立连接的目的就是交换彼此的信息,然后记住对方的信息。所以双方都需要发送彼此的信息给对方:

linux网路相关_运维_26

  但前面的可靠传输原理告诉我们,数据在网络中传输是不可靠的,需要对方给予我们一个确认回复,才可以保证消息正确到达。如下图:

linux网路相关_数据_27

    机器B的确认收到和机器B信息可以进行合并,减少次数;而且发送机器B给机器A本身就代表了机器B已经收到了消息,所以最后的示例图是:

linux网路相关_服务器_28

步骤如下:

  1. 机器A发送syn包向机器B请求建立TCP连接,并附加上自身的接收缓冲区信息等,机器A进入SYN_SEND状态,表示请求已经发送正在等待回复;
  2. 机器B收到请求之后,根据机器A的信息记录下来,并创建自身的接收缓存区,向机器A发送syn+ack的合成包,同时自身进入SYN_RECV状态,表示已经准备好了,等待机器A 的回复就可以向A发送数据;
  3. 机器A收到回复之后记录机器B 的信息,发送ack信息,自身进入ESTABLISHED状态,表示已经完全准备好了,可以进行发送和接收;
  4. 机器B收到ACK数据之后,进入ESTABLISHED状态。

    三次消息的发送,称为三次握手。

断开连接

    断开连接和三次握手类似,直接上图:

linux网路相关_服务器_29

1. 机器A发送完数据之后,向机器B请求断开连接,自身进入FIN_WAIT_1状态,表示数据发送完成且已经发送FIN包(FIN标志位为1);

2. 机器B收到FIN包之后,回复ack包表示已经收到,但此时机器B可能还有数据没发送完成,自身进入CLOSE_WAIT状态,表示对方已发送完成且请求关闭连接,自身发送完成之后可以关闭连接;

3. 机器B数据发送完成之后,发送FIN包给机器B ,自身进入LAST_ACK状态,表示等待一个ACK包即可关闭连接;

4. 机器A收到FIN包之后,知道机器B也发送完成了,回复一个ACK包,并进入TIME_WAIT状态

    TIME_WAIT状态比较特殊。当机器A收到机器B的FIN包时,理想状态下,确实是可以直接关闭连接了;但是:

  • 我们知道网络是不稳定的,可能机器B 发送了一些数据还没到达(比FIN包慢);
  • 同时回复的ACK包可能丢失了,机器B会重传FIN包;

    如果此时机器A马上关闭连接,会导致数据不完整、机器B无法释放连接等问题。所以此时机器A需要等待2个报文生存最大时长,确保网络中没有任何遗留报文了,再关闭连接

5. 最后,机器A等待两个报文存活最大时长之后,机器B 接收到ACK报文之后,均关闭连接,进入CLASED状态

    双方之间4次互相发送报文来断开连接的过程,就是四次挥手。

    现在,对于为什么握手是三次挥手是四次、一定要三次/四次吗、为什么要停留2msl再关闭连接等等这些问题,就都解决了。

/   UDP协议   /

    运输层协议除了TCP,还有大名鼎鼎的UDP。如果说TCP凭借他完善稳定的功能独树一帜,那UDP就是精简主义乱拳打死老师傅。

    UDP只实现了运输层最少的功能:进程间通信。对于应用层传下来的数据,UDP只是附加一个首部就直接交给网络层了。UDP的头部非常简单,只有三部分:

  • 源端口、目标端口:端口号用来区分主机的不同进程
  • 校验码:用于校验数据包在传输的过程中没有出现错误,例如某个1变成了0
  • 长度:报文的长度

    所以UDP的功能也只有两个:校验数据报是否发生错误、区分不同的进程通信。

但,TCP的功能虽然多,但同时也是要付出相对应的代价。例如面向连接的特性,在建立和断开连接的时候会有开销;拥塞控制的特性,会限制传输的上限等等。下面来罗列一下UDP的优缺点:

UDP的缺点

  • 无法保证消息完整、正确到达,UDP是一个不可靠的传输协议;
  • 缺少拥塞控制容易互相竞争资源导致网络系统瘫痪

UDP的优点

  • 效率更快;不需要建立连接以及拥塞控制
  • 连接更多的客户;没有连接状态,不需要为每个客户创建缓存等
  • 分组首部字节少,开销小;TCP首部固定首部是20字节,而UDP只有8字节;更小的首部意味着更大比例的数据部分
  • 在一些需要高效率允许可限度误差的场景下可以使用。如直播场景,并不需要保证每个数据包都完整到达,允许一定的丢包率,这个时候TCP的可靠特性反而成为了累赘;精简的UDP更高的效率是更加适合的选择
  • 可以进行广播;UDP并不是面向连接的,所以可以同时对多个进程进行发送报文

UDP适用场景

    UDP适用于对传输模型需要应用层高度自定义、允许出现丢包、需要高效率的场景、需要广播;例如

  • 视屏直播
  • DNS
  • RIP路由选择协议

/   其他补充   /

分块传输

    我们可以发现,运输层在传输数据的时候,并不是把整个数据包加个首部直接发送过去,而是会拆分成多个报文分开发送;那他这样做原因是什么?

    有读者可能会想到:数据链路层限制了数据长度只能有1460。那数据链路层为什么要这么限制?他的本质原因就是:网络是不稳定的。如果报文太长,那么极有可能在传输一般的时候突然中断了,这个时候就要整个数据重传,效率就降低了。把数据拆分成多个数据报,那么当某个数据报丢失,只需要重传该数据报即可。

    那是不是拆分得越细越好?报文中数据字段长度太低,会使得首部的占比太大,这样首部就会成为网络传输最大的负担了。例如1000字节,每个报文首部是40字节,如果拆分成10个报文,那么只需要传输400字节的首部;而如果拆分成1000个,那么需要传输40000字节的首部,效率就极大地降低了。

路由转换

    先看下图:

linux网路相关_数据_30

  • 正常情况下,主机A的数据包可以又 1-3-6-7路径进行传送
  • 如果路由3坏掉了,那么可以从 1-4-6-7进行传送
  • 如果4也坏掉了,那么只能从2-5-6-7传送
  • 如果5坏掉了,那么就中断线路了

    可以看出来,使用路由转发的好处是:提高网络的容错率,本质原因依旧是网络是不稳定的 。即使坏掉几个路由器,网络依旧畅通。但是如果坏掉路由器6那就直接导致主机A和主机B无法通信,所以要避免这种核心路由器的存在。

    使用路由的好处还有:分流。如果一条线路太拥堵,可以从别的路线进行传输,提高效率。

粘包与拆包

    在面向字节流那一小节讲过,TCP不懂这些数据流的意义,他只知道从应用层拿到数据流,切割成一份份报文,然后发送给目标对象。而如果应用层传输下来的是两个数据包,那么极有可能出现这种情况:

linux网路相关_数据_31

  • 应用层需要向目标进程发送两份数据,一份音频,一份文本
  • TCP只知道接收到一个流,并把流拆分成4段进行发送
  • 中间第二个报文的数据就出现两个文件的数据混在一起,这就是粘包
  • 目标进程应用层在接收到数据之后,需要把这些数据拆分成正确的两个文件,就是拆包

    粘包与拆包都是应用层需要解决的问题,可以在每个文件的最后附加上一些特殊的字节,如换行符;或者控制每个报文只包含一个文件的数据,不足的用0补充等等。

恶意攻击

    TCP的面向连接特点可能会被恶意的人利用,对服务器进行攻击。

    前面我们知道,当我们向一个主机发送syn包请求创建连接时,服务器会为我们创建缓冲区等,然后向我们返回syn+ack报文;如果我们伪造IP和端口,向一个服务器进行海量的请求,会使得服务器创建了大量的创建一半的TCP连接,使得其无法正常响应用户的请求,导致服务器瘫痪。

    解决的方法可以有限制IP的创建连接数、让创建一半的tcp连接在更短的时间内自行关闭、延缓接收缓冲区内存的分配等等。

长连接

    我们向服务器的每一次请求都需要创建一个TCP连接,服务器返回数据之后就会关闭连接;如果在短时间内有大量的请求,那么频繁创建TCP连接关闭TCP连接是一个很浪费资源的行为。所以我们可以让TCP连接不要关闭,在这个期间进行请求,提高效率。

需要注意长连接维持时间、创建条件等,避免被恶意利用创建大量的长连接,消耗殆尽服务器的资源。

五、详解TCP/IP协议

TCP/IP协议

    TCP/IP不是一个协议,而是一个协议族的统称。里面包括IP协议、IMCP协议、TCP协议。

    这里有几个需要注意的知识点:

  • 互联网地址:也就是IP地址,一般为网络号+子网号+主机号
  • 域名系统:通俗的来说,就是一个数据库,可以将主机名转换成IP地址
  • RFC:TCP/IP协议的标准文档
  • 端口号:一个逻辑号码,IP包所带有的标记
  • Socket:应用编程接口

    数据链路层的工作特性:

  • 为IP模块发送和接收IP数据报
  • 为ARP模块发送ARP请求和接收ARP应答(ARP:地址解析协议,将IP地址转换成MAC地址)
  • 为RARP发送RARP请求和接收RARP应答

    接下来我们了解一下TCP/IP的工作流程:

    数据链路层从ARP得到数据的传递信息,再从IP得到具体的数据信息。

IP协议

linux网路相关_运维_32

  IP协议头当中,最重要的就是TTL(IP允许通过的最大网段数量)字段(八位),规定该数据包能穿过几个路由之后才会被抛弃。

IP路由选择

linux网路相关_服务器_33

ARP协议工作理

linux网路相关_服务器_34

ICMP协议(网络控制文协议)

    将IP数据包不能传送的错误信息传送给主机

    查询报文

  1. ping查询:主机是否可达,通过计算间隔时间和传送多少个包的数量
  2. 子网掩码
  3. 时间戳:获得当前时间

    差错报文

    不产生的情况:

  1. ICMP差错报文不产生差错报文
  2. 源地址为零地址、环目地址、广播地址、多播地址

IP路由器选择协议

静态路由选择

    先来看路由选择工作流程:

linux网路相关_服务器_35

静态路由选择

  1. 配置接口以默认方式生成路由表项,或者使用route add手动添加表项
  2. ICMP报文(ICMP重定向报文)更新表项
  3. 动态路由选择(只使用在路由之间)

RIP(路由信息协议)

    分布式的基于距离向量(路由器到每一个目的网络的距离记录)的路由选择协议router承担的工作:

  1. 给每一个已知路由器发送RIP请求报文,要求给出完整的路由表
  2. 如果接受请求,就将自己的路由表交给请求者;如果没有,就处理IP请求表项(自己部分+跳数/没有的部分+16)
  3. 接受回应,更新路由表
  4. 定期更新路由表(一般为30s,只能说太频繁~)

OSPF(开放最短路径优先协议)

    分布式链路状态(和这两个路由器都有接口的网络)协议

  1. 当链路状态发生变化时,采用可靠的洪泛法,向所有的路由器发送信息(相邻的所有路由器的链路状态)
  2. 最终会建立一个全网的拓扑结构图

TCP/IP的三次握手,四次分手

linux网路相关_运维_36

重要的标志我在图中也有标记,重点了解标志位:

  • ACK:确认序号有效
  • RST:重置连接
  • SYN:发起了一个新连接
  • FIN:释放一个连接

三次握手的过程(客户端我们用A表示,服务器端用B表示)

    前提:A主动打开,B被动打开

linux网路相关_运维_37

  1. 在建立连接之前,B先创建TCB(传输控制块),准备接受客户进程的连接请求,处于LISTEN(监听)状态
  2. A首先创建TCB,然后向B发出连接请求,SYN置1,同时选择初始序号seq=x,进入SYN-SEND(同步已发送)状态
  3. B收到连接请求后向A发送确认,SYN置1,ACK置1,同时产生一个确认序号ack=x+1。同时随机选择初始序号seq=y,进入SYN-RCVD(同步收到)状态
  4. A收到确认连接请求后,ACK置1,确认号ack=y+1,seq=x+1,进入到ESTABLISHED(已建立连接)状态。向B发出确认连接,最后B也进入到ESTABLISHED(已建立连接)状态。

    简单来说,就是:

  1. 建立连接时,客户端发送SYN包(SYN=i)到服务器,并进入到SYN-SEND状态,等待服务器确认
  2. 服务器收到SYN包,必须确认客户的SYN(ack=i+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN-RECV状态
  3. 客户端收到服务器的SYN+ACK包,向服务器发送确认报ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

    在此穿插一个知识点就是SYN攻击,那么什么是SYN攻击?发生的条件是什么?怎么避免?

    在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是 Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址 是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网 络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行:

#netstat -nap | grep SYN_RECV

四次分手的过程(客户端我们用A表示,服务器端用B表示)

    由于TCP连接时是全双工的,因此每个方向都必须单独进行关闭。这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的链接。收到一个FIN只是意味着这一方向上没有数据流动,既不会在收到数据,但是在这个TCP连接上仍然能够发送数据,知道这一方向也发送了FIN,首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

前提:A主动关闭,B被动关闭

linux网路相关_DNS_38

  有人可能会问,为什么连接的时候是三次握手,而断开连接的时候需要四次挥手?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再 发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

  1. A发送一个FIN,用来关闭A到B的数据传送,A进入FIN_WAIT_1状态。
  2. B收到FIN后,发送一个ACK给A,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),B进入CLOSE_WAIT状态。
  3. B发送一个FIN,用来关闭B到A的数据传送,B进入LAST_ACK状态。
  4. A收到FIN后,A进入TIME_WAIT状态,接着发送一个ACK给B,确认序号为收到序号+1,B进入CLOSED状态,完成四次挥手。

    简单来说就是:

  1. 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。
  2. 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
  3. 服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。
  4. 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

    A在进入到TIME-WAIT状态后,并不会马上释放TCP,必须经过时间等待计时器设置的时间2MSL(最长报文段寿命),A才进入到CLOSED状态。为什么?

  1. 为了保证A发送的最后一个ACK报文段能够到达B
  2. 防止“已失效的连接请求报文段”出现在本连接中

OK~是不是很难懂的感觉?那我们来说的“人性化点的”吧

三次握手流程

  1. 客户端发个请求“开门呐,我要进来”给服务器
  2. 服务器发个“进来吧,我去给你开门”给客户端
  3. 客户端有很客气的发个“谢谢,我要进来了”给服务器

四次挥手流程

  1. 客户端发个“时间不早了,我要走了”给服务器,等服务器起身送他
  2. 服务器听到了,发个“我知道了,那我送你出门吧”给客户端,等客户端走
  3. 服务器把门关上后,发个“我关门了”给客户端,然后等客户端走(尼玛~矫情啊)
  4. 客户端发个“我知道了,我走了”,之后自己就走了