Skip to content

Latest commit

 

History

History
713 lines (516 loc) · 44 KB

File metadata and controls

713 lines (516 loc) · 44 KB

一、TCP/IP 协议套件和 Python 综述

欢迎来到网络工程的新时代。18 年前,在千年之交,当我第一次开始担任网络工程师时,这个角色与其他技术角色截然不同。网络工程师主要拥有管理和操作局域网和广域网的特定领域知识,偶尔也会涉及系统管理,但并不期望编写代码或理解编程概念。现在已经不是这样了。多年来,DevOps 和软件定义的网络SDN)运动以及其他因素显著模糊了网络工程师、系统工程师和开发人员之间的界限

你已经拿起这本书的事实表明你可能已经是 NetworkDevOps 的使用者,或者你正在考虑走这条路。也许你和我一样,已经做了多年的网络工程师,想知道 Python 编程语言的流行是什么。或者您可能已经精通 Python,但想知道它在网络工程中的应用是什么。如果您属于这些阵营中的任何一个,或者只是对网络工程领域中的 Python 感到好奇,我相信这本书适合您:

The intersection between Python and network engineering

许多深入研究网络工程和 Python 主题的书籍已经编写完成。我不打算在这本书上重复他们的努力。相反,本书假设您有一些管理网络的实际操作经验,并且对网络协议和 Python 语言有基本的了解。您不需要是 Python 或网络工程方面的专家,但应该发现本章中的概念构成了一个总体回顾。本章的其余部分应设定充分利用本书所需的先前知识的期望水平。如果你想重温一下本章的内容,有很多免费或低成本的资源可以帮助你跟上进度。我推荐免费的可汗学院(https://www.khanacademy.org/)和位于的 Python 教程 https://www.python.org/.

本章将快速浏览相关的网络主题。根据我在该领域的工作经验,典型的网络工程师或开发人员可能不记得完成日常任务所需的 TCP 状态机(我知道我不记得),但他们会熟悉 OSI 模型的基本知识、TCP 和 UDP 操作,不同的 IP 头字段和其他基本概念。

我们还将研究 Python 语言的高级回顾;对于那些每天不使用 Python 编写代码的读者来说,这已经足够让他们在本书的其余部分继续阅读下去了。

具体而言,我们将涵盖以下主题:

  • 互联网概述
  • OSI 和客户机-服务器模型
  • TCP、UDP 和 IP 协议套件
  • Python 语法、类型、运算符和循环
  • 用函数、类和包扩展 Python

当然,本章提供的信息并不详尽;请查看参考资料以了解更多信息

互联网概述

什么是互联网?根据你的背景,这个看似简单的问题可能会得到不同的答案。互联网对不同的人意味着不同的东西;年轻人、老年人、学生、教师、商人、诗人都可以对这个问题给出不同的答案。

对于网络工程师来说,互联网是一个全球性的计算机网络,由连接大小网络的网络组成。换句话说,它是一个没有集中所有者的网络网络。以家庭网络为例。它可能由一个家庭以太网交换机和一个无线接入点组成,将智能手机、平板电脑、计算机和电视连接在一起,以便设备相互通信。这是您的局域网局域网。当您的家庭网络需要与外部世界通信时,它会将信息从 LAN 传送到更大的网络,通常适当地命名为互联网服务提供商ISP。ISP 通常由边缘节点组成,这些节点将流量聚合到其核心网络。核心网络的功能是通过高速网络互连这些边缘网络。在特殊的边缘节点,您的 ISP 连接到其他 ISP,以便将您的流量适当地传递到您的目的地。从目的地到家用电脑、平板电脑或智能手机的返回路径可能与通过所有这些网络返回设备的路径相同,也可能不相同,而源和目的地保持不变

让我们来看看组成这个网络的网络组件。

服务器、主机和网络组件

主机是网络上与其他节点通信的终端节点。在当今世界,主机可以是传统的计算机,也可以是您的智能手机、平板电脑或电视。随着物联网物联网的兴起,主机的广泛定义可以扩展到包括 IP 摄像头、电视机顶盒以及我们在农业、农业、汽车等领域使用的不断增加的传感器类型。随着连接到 internet 的主机数量激增,所有这些主机都需要寻址、路由和管理。对适当联网的需求从未如此之大。

大多数时候,当我们在互联网上时,我们会提出服务请求。这可以是查看网页、发送或接收电子邮件、传输文件等。这些服务由服务器提供。顾名思义,服务器向多个节点提供服务,因此通常具有更高级别的硬件规范。在某种程度上,服务器是网络上的特殊超级节点,为其对等方提供额外的功能。我们将在稍后的客户机-服务器模型部分中介绍服务器。

如果你把服务器和主机想象成城镇,网络组件是连接它们的道路和高速公路。事实上,当描述在全球传输不断增加的比特和字节的网络组件时,人们会想到信息高速公路这个术语。在我们稍后将介绍的 OSI 模型中,这些网络组件是一到三层设备。它们是第二层和第三层路由器和交换机,用于指挥通信,以及第一层传输,例如光纤电缆、同轴电缆、双绞线和一些 DWDM 设备等等。

主机、服务器和网络组件共同构成了我们今天所知道的互联网。

数据中心的崛起

在最后一节中,我们研究了服务器、主机和网络组件在跨网络中扮演的不同角色。由于服务器需要更高的硬件容量,它们通常被放在一个中心位置,因此可以更高效地管理它们。我们通常将这些位置称为数据中心。

企业数据中心

在典型的企业中,公司通常需要内部工具,如电子邮件、文档存储、销售跟踪、订购、人力资源工具和知识共享内部网。这些服务转换为文件和邮件服务器、数据库服务器和 web 服务器。与用户计算机不同,这些计算机通常是需要大量电源、冷却和网络连接的高端计算机。硬件的一个副产品也是它们产生的噪音。它们通常放置在企业的中心位置,称为主配线架MDF),以提供必要的供电、电源冗余、冷却和网络连接。

为了连接到 MDF,用户的流量通常在捆绑并连接到 MDF 之前聚集在离用户更近的位置,有时称为中间配线架IDF)。IDF-MDF 分布遵循企业建筑或校园的物理布局并不罕见。例如,每个建筑楼层可以由一个 IDF 组成,该 IDF 聚合到另一楼层的 MDF。如果企业由多个建筑物组成,则可以在将建筑物连接到企业数据中心之前,通过组合建筑物的通信量来进行进一步的聚合。

企业数据中心通常遵循三层网络设计。这些层是访问、分发和核心。访问层类似于每个用户连接到的端口,IDF 可以被视为分发层,而核心层由到 MDF 和企业数据中心的连接组成。当然,这是对企业网络的概括,因为其中一些网络不会遵循相同的模型。

云数据中心

随着云计算和软件或基础设施即服务(infrastructure as a service)的兴起,云提供商构建的数据中心正处于超规模。由于服务器的数量,它们通常需要比任何企业数据中心都多得多的电源、冷却、网络速度和馈送容量。即使在云提供商数据中心工作多年后,每次访问云提供商数据中心时,我仍然对其规模感到惊讶。事实上,云数据中心如此之大,耗电量如此之大,以至于它们通常建在靠近发电厂的地方,在那里它们可以获得最便宜的电价,而不会在电力传输过程中损失太多效率。他们的冷却需求非常大,一些人被迫对数据中心的建设地点进行创新,在通常寒冷的气候下建设,这样他们就可以在需要时打开门窗,让服务器在安全温度下运行。当涉及到为亚马逊、微软、谷歌和 Facebook 等公司建立和管理云数据中心的科学时,任何搜索引擎都能给你一些惊人的数字:

Utah data center (source: https://en.wikipedia.org/wiki/Utah_Data_Center)

在云提供商的规模上,他们需要提供的服务通常不具有成本效益,也不可能安装在单个服务器中。它们分布在一组服务器之间,有时跨越许多不同的机架,为服务所有者提供冗余和灵活性。延迟和冗余要求给网络带来了巨大的压力。互连的数量相当于网络设备的爆炸性增长;这转化为需要对该网络设备进行跟踪、调配和管理的次数。典型的网络设计是多阶段的 CLOS 网络:

CLOS network 

在某种程度上,云数据中心是网络自动化成为速度和可靠性的必要条件的地方。如果我们遵循通过终端和命令行界面管理网络设备的传统方式,所需的工程小时数将不允许在合理的时间内提供服务。这并不是说人类的重复很容易出错,效率低下,而且是对工程人才的严重浪费

云数据中心是几年前我开始使用 Python 实现网络自动化的地方,从那以后我再也没有回首过。

边缘数据中心

如果我们在数据中心级别拥有足够的计算能力,那么除了这些数据中心,为什么还要在其他任何地方保留任何东西呢?来自世界各地客户机的所有连接都可以路由回提供服务的数据中心服务器,我们可以称之为一天,对吗?当然,答案取决于用例。将请求和会话从客户端路由回大型数据中心的最大限制是传输中引入的延迟。换句话说,大延迟是网络成为瓶颈的地方。延迟数永远不会为零:即使光在真空中传播的速度很快,物理传输仍然需要时间。在现实世界中,当数据包通过多个网络,有时通过海底电缆、慢速卫星链路、3G 或 4G 蜂窝链路或 Wi-Fi 连接时,真空中的延迟将远高于光。

解决方案是什么?减少最终用户通过的网络数量。在用户进入您的网络的边缘尽可能与用户保持密切的联系,并在边缘位置放置足够的资源以满足请求。让我们想象一下,你正在构建下一代视频流媒体服务。为了提高流畅流媒体的客户满意度,您需要将视频服务器放置在尽可能靠近客户的位置,无论是在客户的 ISP 内部还是非常靠近客户的 ISP。此外,视频服务器场的上游将不仅连接到一个或两个 ISP,还连接到我可以连接的所有 ISP,以减少跳数。所有连接都将具有所需的带宽,以减少高峰时间的延迟。这种需求催生了大型 ISP 和内容提供商的对等交换边缘数据中心。即使网络设备的数量没有云数据中心那么多,它们也可以从网络自动化带来的可靠性、安全性和可视性的提高中获益。

我们将在本书后面的章节中介绍安全性和可见性。

OSI 模型

没有经过开放系统互连OSI模型)的检查,网络手册就不完整。该模型是一个概念模型,将电信功能组件化为不同的层。该模型定义了七个层,每个层独立地位于另一层之上,只要它们遵循定义的结构和特征。例如,在网络层中,IP 可以位于不同类型的数据链路层之上,例如以太网或帧中继。OSI 参考模型是一种很好的方法,可以将不同的技术规范化为一组人们可以同意的通用语言。这大大减少了在各个层上工作的各方的范围,并允许他们深入查看特定任务,而不必太担心兼容性:

OSI model 

OSI 模型最初是在 20 世纪 70 年代末开发的,后来由国际标准化组织ISO)和电信标准化****部门联合发布国际电信联盟ITU-T)。在电信领域引入一个新的话题时,它被广泛接受并被普遍提及。

大约在 OSI 模型开发的同一时期,互联网正在形成。原始设计器使用的参考模型通常称为 TCP/IP 模型。传输控制协议****TCP互联网协议****IP是设计中包含的原始协议套件。这有点类似于 OSI 模型,因为它们将端到端数据通信划分为抽象层。不同的是,该模型在应用层结合 OSI 模型中的第 5 层到第 7 层,而物理层和数据链路层结合在链路层:

Internet protocol suite 

OSI 和 TCP/IP 模型都有助于提供端到端数据通信的标准。然而,在大多数情况下,我们将更多地参考 TCP/IP 模型,因为这是 internet 的基础。我们将在需要时指定 OSI 模型,例如在接下来的章节中讨论 web 框架时。

客户机-服务器模型

参考模型演示了两个节点之间数据通信的标准方式。当然,到目前为止,我们都知道并非所有节点都是平等创建的。即使在 DARPA 网络时代,也有工作站节点,并且有向其他节点提供内容的节点。这些服务器节点通常具有更高的硬件规格,并且由工程师进行更紧密的管理。由于这些节点向其他节点提供资源和服务,因此它们通常被称为服务器。服务器通常处于空闲状态,等待客户端启动对其资源的请求。客户机请求的这种分布式资源模型称为客户机-服务器模型。

为什么这很重要?如果您想一想,这个客户机-服务器模型突出了网络的重要性。没有它,网络互连的需求就真的不多了。正是需要将比特和字节从客户机传输到服务器,这一点使人们认识到网络工程的重要性。当然,我们都知道其中最大的网络,互联网,是如何改变我们所有人的生活的,并将继续这样做。

你问,每个节点如何在每次需要彼此通话时确定时间、速度、来源和目的地?这就引出了网络协议。

网络协议套件

在计算机联网的早期,协议是专有的,由设计连接方法的公司严格控制。如果您在主机中使用Novell 的 IPX/SPX协议,您将无法与 Apple 的AppleTalk主机通信,反之亦然。这些专有协议套件通常具有与 OSI 参考模型类似的层,并遵循客户机-服务器通信方法。它们通常在封闭的局域网LAN中工作良好,无需与外部世界通信。当流量确实需要移动到本地 LAN 之外时,通常使用 internet 工作设备(如路由器)将协议从一个协议转换为另一个协议。例如,路由器将 AppleTalk 网络连接到基于 IP 的网络。翻译通常并不完美,但由于早期大部分通信都发生在局域网内,因此也可以。

然而,随着对网络间通信的需求超出 LAN,对网络协议套件标准化的需求变得更大。专有协议最终让位于 TCP、UDP 和 IP 的标准化协议套件,这大大增强了一个网络与另一个网络通信的能力。互联网是其中最大的网络,依靠这些协议来正常运行。在接下来的几节中,我们将查看每个协议套件。

传输控制协议

传输控制协议TCP)是当今互联网上使用的主要协议之一。如果您打开了网页或发送了电子邮件,则表明您遇到了 TCP 协议。该协议位于 OSI 模型的第 4 层,它负责以可靠和错误检查的方式在两个节点之间传递数据段。TCP 由 160 位报头组成,其中包括源端口和目标端口、序列号、确认号、控制标志和校验和:

TCP header 

TCP 的功能和特点

TCP 使用数据报套接字或端口建立主机到主机的通信。被称为互联网分配号码管理局IANA)的标准机构指定了一些众所周知的端口来表示某些服务,例如用于 HTTP(web)的端口80和用于 SMTP(邮件)的端口25。客户机-服务器模型中的服务器通常侦听这些众所周知的端口之一,以便接收来自客户机的通信请求。TCP 连接由操作系统通过表示连接的本地端点的套接字进行管理。

协议操作由状态机组成,在状态机中,机器需要在通信会话期间跟踪何时侦听传入连接,以及在连接关闭后释放资源。每个 TCP 连接都会经历一系列状态,例如ListenSYN-SENTSYN-RECEIVEDESTABLISHEDFIN-WAITCLOSE-WAITCLOSINGLAST-ACKTIME-WAITCLOSED

TCP 消息和数据传输

TCP 与用户数据报协议UDP)的最大区别在于,它以有序、可靠的方式传输数据。操作保证传递的事实通常被称为 TCP,它是面向连接的协议。它通过首先建立三方握手来同步发射机和接收机之间的序列号SYNSYN-ACKACK

确认用于跟踪会话中的后续段。最后,在对话结束时,一方将发送一条FIN消息,另一方将ACK发送FIN消息,同时发送自己的FIN消息。FIN发起者随后将ACK发送其收到的FIN消息。

正如我们中许多对 TCP 连接有问题的人所告诉您的,操作可能会变得相当复杂。我们当然可以理解,大多数时候,操作只是在后台无声地进行。

可以写一本关于 TCP 协议的书;事实上,已经有很多关于该协议的优秀书籍。

As this section is a quick overview, if interested, The TCP/IP Guide (http://www.tcpipguide.com/) is an excellent free resource that you can use to dig deeper into the subject.

用户数据报协议

用户数据报协议UDP)也是互联网协议套件的核心成员。与 TCP 一样,它在 OSI 模型的第 4 层上运行,该层负责在应用程序和 IP 层之间传递数据段。与 TCP 不同,标头只有 64 位,它只包含源端口和目标端口、长度和校验和。轻量级标头使其非常适合那些希望更快地交付数据而无需在两台主机之间建立会话或需要可靠数据交付的应用程序。也许很难想象今天的快速互联网连接,但是在 X.21 和帧中继链路的早期,额外的报头对传输速度产生了很大的影响。尽管速度差异非常重要,但不必维护各种状态(如 TCP),也可以节省两个端点上的计算机资源:

UDP header 

你现在可能想知道为什么 UDP 在现代会被使用;鉴于缺乏可靠的传输,我们难道不希望所有的连接都是可靠和无错误的吗?如果您考虑多媒体视频流或 Skype 呼叫,当应用程序只希望尽快交付数据报时,这些应用程序将受益于较轻的标题。还可以考虑基于 UDP 协议的快速 DNS 查找过程。当您在浏览器上键入的地址被转换为计算机可理解的地址时,用户将受益于一个轻量级的过程,因为这必须在您最喜爱的网站向您发送第一点信息之前发生。

同样,本节并没有公正地讨论 UDP 主题,如果您有兴趣了解更多有关 UDP 的信息,我们鼓励读者通过各种资源来探讨该主题。

因特网协议

网络工程师会告诉你,他们生活在互联网协议(IP)层,这是 OSI 模型的第 3 层。IP负责端节点间的寻址和路由等工作。IP 地址可能是它最重要的工作。地址空间分为两部分:网络部分和主机部分。子网掩码用于通过将网络部分与 1 和主机部分与 0 相匹配来指示网络地址中的哪个部分由网络组成,哪个部分是主机。IPv4 和后来的 IPv6 都用点符号表示地址,例如,192.168.0.1。子网掩码可以是点符号(255.255.255.0),也可以使用正斜杠表示网络位(/24)中应考虑的位数:

IPv4 header 

IPv6 报头是 IPv4 的下一代 IP 报头,具有固定部分和各种扩展报头:

IPv6 fixed header 

固定标头部分中的下一个标头字段可以指示要遵循的扩展标头,该标头包含附加信息。扩展头可以包括路由和片段信息。尽管协议设计者希望从 IPv4 转移到 IPv6,但今天的互联网仍然基本上是通过 IPv4 解决的,一些服务提供商的网络在内部是通过 IPv6 解决的。

IP-NAT 与安全

网络地址转换NAT通常用于将一系列专用 IPv4 地址转换为可公开路由的 IPv4 地址。但它也可能意味着 IPv4 到 IPv6 之间的转换,例如,当他们在网络内部使用 IPv6 时,在运营商边缘,当数据包离开网络时,需要将其转换为 IPv4。有时,出于安全原因,也会使用 NAT6 到 6。

安全性是一个连续的过程,它集成了网络的所有方面,包括自动化和 Python。本书旨在使用 Python 帮助您管理网络;安全性将在本书的以下章节中讨论,例如通过 telnet 使用 SSHv2。我们还将研究如何使用 Python 和其他工具在网络中获得可见性。

IP 路由概念

在我看来,IP 路由就是让两个端点之间的中间设备根据 IP 报头在它们之间传输数据包。对于通过互联网进行的所有通信,数据包将穿过各种中间设备。如前所述,中间设备由路由器、交换机、光学设备和各种其他设备组成,它们不会超出网络和传输层进行检查。在公路旅行的类比中,你可能会在美国从加利福尼亚州的圣地亚哥市旅行到华盛顿州的西雅图市。IP 源地址类似于圣地亚哥,目标 IP 地址可视为西雅图。在你的公路之旅中,你会停在许多不同的中间点,比如洛杉矶、旧金山和波特兰;这些可以被认为是源和目标之间的路由器和交换机。

为什么这很重要?在某种程度上,这本书是关于管理和优化这些中间设备。在跨越多个美国足球场的巨型数据中心时代,高效、灵活、可靠和经济高效的网络管理方式成为公司竞争优势的一个主要方面。在未来的章节中,我们将深入探讨如何使用 Python 编程来有效地管理网络。

Python 语言概述

简而言之,这本书是关于使用 Python 使我们的网络工程生活变得更容易。但是 Python 是什么?为什么它是许多 DevOps 工程师选择的语言?用 Python 基金会执行摘要的话(损坏)https://www.python.org/doc/essays/blurb/

"Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level, built-in data structure, combined with dynamic typing and dynamic binding, makes it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together. Python's simple, easy-to-learn syntax emphasizes readability and therefore reduces the cost of program maintenance."

如果您对编程有点陌生,那么前面提到的面向对象的动态语义可能对您意义不大。但我认为,对于快速的应用程序开发,简单易学的语法听起来是件好事。Python 作为一种解释语言,意味着不需要编译过程,因此编写、测试和编辑 Python 程序的时间大大减少。对于简单的脚本,如果脚本失败,通常只需要一个print语句就可以调试正在发生的事情。使用解释器还意味着 Python 可以很容易地移植到不同类型的操作系统,如 Windows 和 Linux,并且在一个操作系统上编写的 Python 程序可以在另一个操作系统上使用。

面向对象的特性通过将大型程序分解为简单的可重用对象以及具有函数、模块和包的其他可重用格式来鼓励代码重用。事实上,所有 Python 文件都是可以重用或导入到另一个 Python 程序中的模块。这使得在工程师之间共享程序变得容易,并鼓励代码重用。Python 还有一个包含电池的咒语,这意味着对于普通任务,您不需要下载任何额外的包。为了在代码不太臃肿的情况下实现这一点,在安装 Python 解释器时会安装一组标准库。对于正则表达式、数学函数和 JSON 解码等常见任务,您只需要import语句,解释器就会将这些函数移动到您的程序中。这就是我认为 Python 语言的杀手特性之一。

最后,Python 代码可以从一个相对较小的脚本开始,只需几行代码,然后发展成一个完整的生产系统,这一事实对于网络工程师来说非常方便。正如我们许多人所知,网络通常在没有总体规划的情况下有机地发展。一种能够随网络规模增长的语言是无价的。您可能会惊讶地看到,许多尖端公司(使用 Python 的组织,)正在将一种被许多人视为脚本语言的语言用于完整的生产系统 https://wiki.python.org/moin/OrganizationsUsingPython )。

如果您曾经在一个必须在不同供应商平台(如 Cisco IOS 和 Juniper Junos)之间切换的环境中工作过,那么您知道在尝试完成相同任务时在语法和使用之间切换是多么痛苦。由于 Python 对于大型和小型程序都具有足够的灵活性,因此不存在这样的上下文切换,因为它只是 Python。

在本章的其余部分中,我们将对 Python 语言进行一次高层次的介绍,以进行一点复习。如果您已经熟悉了基础知识,请随意快速浏览或跳过本章的其余部分。

Python 版本

正如许多读者已经知道的,Python 在过去几年中经历了从 Python 2 到 Python 3 的过渡。Python3 发布于 2008 年,10 多年前,最近发布的 3.7 进行了积极的开发。不幸的是,Python3 与 Python2 不向后兼容。在撰写这本书的第二版时,在 2018 年年中,Python 社区已经基本上转移到 Python 3。Python2.x 的最新版本 2.7 是在六年前的 2010 年年中发布的。幸运的是,两个版本可以在同一台机器上共存。就我个人而言,当我在命令提示符下键入 Python 时,我使用 Python 2 作为默认解释器,当我需要使用 Python 3 时,我使用 Python 3。下一节将提供有关调用 Python 解释器的更多信息,但以下是在 Ubuntu Linux 机器上调用 Python 2 和 Python 3 的示例:

$ python
Python 2.7.12 (default, Dec 4 2017, 14:50:18)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> exit() 
$ python3
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit() 

随着 2.7 版本的结束,大多数 Python 框架现在都支持 Python3。Python3 还有很多好的特性,比如异步 I/O,当我们需要优化代码时可以利用这些特性。除非另有说明,本书将使用 Python 3 作为其代码示例。如果适用,我们还将尝试指出 Python2 和 Python3 的区别。

如果某个特定的库或框架更适合 Python2,例如 Ansible(请参阅以下信息),那么将指出它,我们将使用 Python2。

At the time of writing, Ansible 2.5 and above have support for Python 3. Prior to 2.5, Python 3 support was considered a tech preview. Given the relatively new supportability, many of the community modules are still yet to migrate to Python 3. For more information on Ansible and Python 3, please see https://docs.ansible.com/ansible/2.5/dev_guide/developing_python_3.html

操作系统

如前所述,Python 是跨平台的。Python 程序可以在 Windows、Mac 和 Linux 上运行。实际上,当您需要确保跨平台兼容性时,需要特别小心,例如注意 Windows 文件名中反斜杠之间的细微差别。因为这本书是为 DevOps、系统和网络工程师准备的,所以 Linux 是预期读者的首选平台,尤其是在生产中。本书中的代码将在 Linux Ubuntu 16.06 LTS 机器上进行测试。我还将尽最大努力确保代码在 Windows 和 MacOS 平台上运行相同。

如果您对操作系统的详细信息感兴趣,请参阅以下内容:

$ uname -a
Linux packt-network-python 4.13.0-45-generic #50~16.04.1-Ubuntu SMP Wed May 30 11:18:27 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux 

运行 Python 程序

Python 程序由解释器执行,这意味着代码将通过该解释器输入底层操作系统执行,并显示结果。Python 开发社区有几种不同的解释器实现,例如 IronPython 和 Jython。在本书中,我们将使用目前最常用的 Python 解释器 CPython。在本书中,每当我们提到 Python 时,除非另有说明,否则我们指的是 CPython。

使用 Python 的一种方法是利用交互式提示。当您希望在不编写整个程序的情况下快速测试一段 Python 代码或概念时,这非常有用。通常只需输入Python关键字即可:

Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for 
more information.
>>> print("hello world")
hello world
>>>

In Python 3, the print statement is a function; therefore, it requires parentheses. In Python 2, you can omit the parentheses.

交互模式是 Python 最有用的特性之一。在交互式 shell 中,您可以键入任何有效语句或语句序列,并立即返回结果。我通常使用它来探索我不熟悉的功能或库。谈论即时满足!

On Windows, if you do not get a Python shell prompt back, you might not have the program in your system search path. The latest Windows Python installation program provides a checkbox for adding Python to your system path; make sure that is checked. Or you can add the program in the path manually by going to Environment Settings.

然而,运行 Python 程序的一种更常见的方法是保存 Python 文件,并在运行后通过解释器运行它。这将避免您像在交互式 shell 中那样反复键入相同的语句。Python 文件只是常规的文本文件,通常使用.py扩展名保存。在*Nix 世界中,您还可以在顶部添加shebang#!行)以指定将用于运行文件的解释器。#字符可用于指定解释器不会执行的注释。以下文件helloworld.py包含以下语句:

# This is a comment
print("hello world") 

这可以按如下方式执行:

$ python helloworld.py
hello world
$

Python 内置类型

Python 在解释器中内置了几种标准类型:

  • Null对象
  • 数字intlongfloatcomplexbool(具有TrueFalse值的int子类)
  • 序列str、列表、元组和范围
  • 映射dict
  • 设置setfrozenset

无类型

None类型表示没有值的对象。None类型在不显式返回任何内容的函数中返回。None类型也用于函数参数中,以在调用方未传入实际值时出错。

数字

Python 数值对象基本上是数字。除布尔型外,intlongfloatcomplex的数字类型都是有符号的,这意味着它们可以是正的,也可以是负的。布尔值是整数的子类,可以是两个值之一:1表示True,而0表示False。其余数字类型的区别在于它们能够精确地表示数字;例如,int是有限范围的整数,long是无限范围的整数。浮点数是计算机上使用双精度表示(64 位)的数字。

序列

序列是具有非负整数索引的有序对象集。在本节和接下来的几节中,我们将使用交互式解释器来说明不同的类型。请随意在自己的电脑上打字。

有时人们会惊讶于string实际上是一种序列类型。但如果仔细观察,字符串是一系列字符组合在一起的。字符串用单引号、双引号或三引号括起来。注意:在以下示例中,引号必须匹配,三重引号允许字符串跨越不同的行:

>>> a = "networking is fun"
>>> b = 'DevOps is fun too'
>>> c = """what about coding?
... super fun!"""
>>>

其他两种常用的序列类型是列表和元组。列表是任意对象的序列。可以通过将对象括在方括号中来创建列表。与字符串一样,列表由从零开始的非零整数索引。通过引用索引号检索列表的值:

>>> vendors = ["Cisco", "Arista", "Juniper"]
>>> vendors[0]
'Cisco'
>>> vendors[1]
'Arista'
>>> vendors[2]
'Juniper'

元组类似于列表,通过将值括在括号中创建。与列表一样,元组中的值通过引用其索引号进行检索。与列表不同,创建后无法修改值:

>>> datacenters = ("SJC1", "LAX1", "SFO1")
>>> datacenters[0]
'SJC1'
>>> datacenters[1]
'LAX1'
>>> datacenters[2]
'SFO1' 

某些操作对于所有序列类型都是通用的,例如按索引返回元素以及切片:

>>> a
'networking is fun'
>>> a[1]
'e'
>>> vendors
['Cisco', 'Arista', 'Juniper']
>>> vendors[1]
'Arista'
>>> datacenters
('SJC1', 'LAX1', 'SFO1')
>>> datacenters[1]
'LAX1'
>>>
>>> a[0:2]
'ne'
>>> vendors[0:2]
['Cisco', 'Arista']
>>> datacenters[0:2]
('SJC1', 'LAX1')
>>>

Remember that index starts at 0. Therefore, the index of 1 is actually the second element in the sequence.

还有一些常见功能可应用于序列类型,例如检查元素数量以及最小值和最大值:

>>> len(a)
17
>>> len(vendors)
3
>>> len(datacenters)
3
>>>
>>> b = [1, 2, 3, 4, 5]
>>> min(b)
1
>>> max(b)
5

不足为奇的是,有各种方法只适用于字符串。值得注意的是,这些方法并不修改底层字符串数据本身,而是始终返回一个新字符串。如果要使用新值,则需要捕获返回值并将其分配给其他变量:

>>> a
'networking is fun'
>>> a.capitalize()
'Networking is fun'
>>> a.upper()
'NETWORKING IS FUN'
>>> a
'networking is fun'
>>> b = a.upper()
>>> b
'NETWORKING IS FUN'
>>> a.split()
['networking', 'is', 'fun']
>>> a
'networking is fun'
>>> b = a.split()
>>> b
['networking', 'is', 'fun']
>>>

下面是列表的一些常用方法。这个列表是一个非常有用的结构,可以将多个项目放在一起,并一次遍历一个项目。例如,我们可以创建一个数据中心脊椎交换机列表,并通过逐个迭代将相同的访问列表应用于所有交换机。由于列表的值可以在创建后修改(与元组不同),因此我们还可以在程序中扩展和对比现有列表:

>>> routers = ['r1', 'r2', 'r3', 'r4', 'r5']
>>> routers.append('r6')
>>> routers
['r1', 'r2', 'r3', 'r4', 'r5', 'r6']
>>> routers.insert(2, 'r100')
>>> routers
['r1', 'r2', 'r100', 'r3', 'r4', 'r5', 'r6']
>>> routers.pop(1)
'r2'
>>> routers
['r1', 'r100', 'r3', 'r4', 'r5', 'r6']

映射

Python 提供了一种映射类型,称为字典。字典就是我所认为的穷人的数据库,因为它包含可以通过键索引的对象。在其他语言中,这通常被称为关联数组或哈希表。如果您在其他语言中使用过任何类似字典的对象,您就会知道这是一种功能强大的类型,因为您可以使用人类可读的键引用该对象。对于试图维护和排除代码故障的可怜人来说,这个键更有意义。在您编写代码并在凌晨 2 点进行故障排除几个月后,这个家伙可能就是您。字典值中的对象也可以是另一种数据类型,例如列表。可以使用大括号创建字典:

>>> datacenter1 = {'spines': ['r1', 'r2', 'r3', 'r4']}
>>> datacenter1['leafs'] = ['l1', 'l2', 'l3', 'l4']
>>> datacenter1
{'leafs': ['l1', 'l2', 'l3', 'l4'], 'spines': ['r1',  
'r2', 'r3', 'r4']}
>>> datacenter1['spines']
['r1', 'r2', 'r3', 'r4']
>>> datacenter1['leafs']
['l1', 'l2', 'l3', 'l4']

设置

集合用于包含无序的对象集合。与列表和元组不同,集合是无序的,不能用数字索引。但有一个特点使集合变得非常有用:集合的元素永远不会重复。假设您有一个 IP 列表,需要将其放入访问列表中。这个 IP 列表中唯一的问题是它们都是重复的。现在,想一想您将使用多少行代码在 IP 列表中循环以排序唯一的项目,一次一行。但是,内置的集合类型允许您仅用一行代码消除重复的条目。老实说,我不怎么使用 set,但当我需要它时,我总是非常感谢它的存在。创建一个或多个集合后,可以使用并集、交集和差集相互比较:

>>> a = "hello"
>>> set(a)
{'h', 'l', 'o', 'e'}
>>> b = set([1, 1, 2, 2, 3, 3, 4, 4])
>>> b
{1, 2, 3, 4}
>>> b.add(5)
>>> b
{1, 2, 3, 4, 5}
>>> b.update(['a', 'a', 'b', 'b'])
>>> b
{1, 2, 3, 4, 5, 'b', 'a'}
>>> a = set([1, 2, 3, 4, 5])
>>> b = set([4, 5, 6, 7, 8])
>>> a.intersection(b)
{4, 5}
>>> a.union(b)
{1, 2, 3, 4, 5, 6, 7, 8}
>>> 1 *
{1, 2, 3}
>>>

Python 运算符

Python 有一些您所期望的数字运算符;请注意,截断除法(//,也称为楼层除法)将结果截断为整数和浮点,并返回整数值。模(%运算符返回除法中的余数值:

>>> 1 + 2
3
>>> 2 - 1
1
>>> 1 * 5
5
>>> 5 / 1
5.0
>>> 5 // 2
2
>>> 5 % 2
1

还有比较运算符。请注意,双等号表示比较,单等号表示变量赋值:

>>> a = 1
>>> b = 2
>>> a == b
False
>>> a > b
False
>>> a < b
True
>>> a <= b
True 

我们还可以使用两种常见的成员资格运算符来查看对象是否属于序列类型:

>>> a = 'hello world'
>>> 'h' in a
True
>>> 'z' in a
False
>>> 'h' not in a
False
>>> 'z' not in a
True

Python 控制流工具

ifelseelif语句控制条件代码执行。正如所料,条件语句的格式如下:

if expression:
  do something
elif expression:
  do something if the expression meets
elif expression:
  do something if the expression meets
...
else:
  statement

下面是一个简单的例子:

>>> a = 10
>>> if a > 1:
...   print("a is larger than 1")
... elif a < 1:
...   print("a is smaller than 1")
... else:
...   print("a is equal to 1")
...
a is larger than 1
>>>

while循环将继续执行,直到条件为false,因此如果不想继续执行(并使进程崩溃),请小心此循环:

while expression:
  do something
>>> a = 10
>>> b = 1
>>> while b < a:
...   print(b)
...   b += 1
...
1
2
3
4
5
6
7
8
9

for循环与任何支持迭代的对象一起工作;这意味着所有内置序列类型,如列表、元组和字符串,都可以在for循环中使用。以下for循环中的字母i是一个迭代变量,因此您通常可以在代码上下文中选择有意义的内容:

for i in sequence:
  do something
>>> a = [100, 200, 300, 400]
>>> for number in a:
...   print(number)
...
100
200
300
400

您还可以创建自己的对象,该对象支持迭代器协议,并且能够对该对象使用for循环。

Constructing such an object is outside the scope of this chapter, but it is useful knowledge to have; you can read more about it at https://docs.python.org/3/c-api/iter.html.

Python 函数

大多数情况下,当您发现自己复制和粘贴了一些代码片段时,您应该将其分解为一个自包含的块,并将其分解为函数。这种做法允许更好的模块化,更易于维护,并允许代码重用。Python 函数是使用带有函数名的def关键字定义的,后跟函数参数。函数体由要执行的 Python 语句组成。在函数结束时,您可以选择向函数调用方返回一个值,或者默认情况下,如果您没有指定返回值,它将返回None对象:

def name(parameter1, parameter2):
  statements
  return value

我们将在以下章节中看到更多函数示例,因此这里是一个快速示例:

>>> def subtract(a, b):
...   c = a - b
...   return c
...
>>> result = subtract(10, 5)
>>> result
5
>>>

Python 类

Python 是一种面向对象编程OOP语言)。Python 创建对象的方式是使用class关键字。Python 对象通常是函数(方法)、变量和属性(属性)的集合。定义类后,可以创建此类类的实例。该类用作后续实例的蓝图。

OOP 的主题超出了本章的范围,因此这里有一个简单的router对象定义示例:

>>> class router(object):
...   def __init__(self, name, interface_number,
vendor):
...     self.name = name
...     self.interface_number = interface_number
...     self.vendor = vendor
...
>>>

定义后,您可以创建任意数量的该类实例:

>>> r1 = router("SFO1-R1", 64, "Cisco")
>>> r1.name
'SFO1-R1'
>>> r1.interface_number
64
>>> r1.vendor
'Cisco'
>>>
>>> r2 = router("LAX-R2", 32, "Juniper")
>>> r2.name
'LAX-R2'
>>> r2.interface_number
32
>>> r2.vendor
'Juniper'
>>>

当然,Python 对象和 OOP 还有很多内容。我们将在以后的章节中看更多的例子。

Python 模块和包

任何 Python 源文件都可以用作模块,并且可以重用在该源文件中定义的任何函数和类。要加载代码,引用模块的文件需要使用import关键字。导入文件时会发生三种情况:

  1. 该文件为源文件中定义的对象创建新的命名空间
  2. 调用方执行模块中包含的所有代码
  3. 该文件在调用方中创建一个名称,该名称引用正在导入的模块。该名称与模块的名称匹配

还记得您使用交互式 shell 定义的subtract()函数吗?要重用该函数,我们可以将其放入名为subtract.py的文件中:

def subtract(a, b):
  c = a - b
  return c

subtract.py同一目录下的文件中,您可以启动 Python 解释器并导入此函数:

Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for  
more information.
>>> import subtract
>>> result = subtract.subtract(10, 5)
>>> result
5

这是因为默认情况下,Python 将首先搜索当前目录中的可用模块。如果您在不同的目录中,您可以使用带有sys.pathsys模块手动添加搜索路径位置。还记得我们刚才提到的标准库吗?您猜对了,这些只是用作模块的 Python 文件。

包允许将模块集合分组在一起。这进一步将 Python 模块组织到一个更具名称空间保护的环境中,以进一步提高可重用性。包是通过创建一个目录来定义的,该目录的名称要用作名称空间,然后可以将模块源文件放在该目录下。为了让 Python 将其识别为 Python 包,只需在此目录中创建一个__init__.py文件。在与subtract.py文件相同的示例中,如果要创建名为math_stuff的目录并创建a __init__.py文件:

echou@pythonicNeteng:~/Master_Python_Networking/
Chapter1$ mkdir math_stuff
echou@pythonicNeteng:~/Master_Python_Networking/
Chapter1$ touch math_stuff/__init__.py
echou@pythonicNeteng:~/Master_Python_Networking/
Chapter1$ tree .
.
├── helloworld.py
└── math_stuff
 ├── __init__.py
 └── subtract.py

1 directory, 3 files
echou@pythonicNeteng:~/Master_Python_Networking/
Chapter1$

您现在引用模块的方式需要包括包名:

>>> from math_stuff.subtract import subtract
>>> result = subtract(10, 5)
>>> result
5
>>>

正如您所看到的,模块和包是组织大型代码文件并使共享 Python 代码更容易的好方法。

总结

在本章中,我们介绍了 OSI 模型并回顾了网络协议套件,如 TCP、UDP 和 IP。它们作为层处理任意两台主机之间的寻址和通信协商。这些协议在设计时考虑到了可扩展性,与最初的设计基本上没有变化。考虑到互联网的爆炸性增长,这是一个相当大的成就

我们还快速回顾了 Python 语言,包括内置类型、运算符、控制流、函数、类、模块和包。Python 是一种功能强大、易于阅读的产品级语言。这使得该语言成为网络自动化的理想选择。网络工程师可以利用 Python 从简单的脚本开始,然后逐渐转向其他高级功能

第 2 章底层网络设备交互中,我们将开始研究使用 Python 以编程方式与网络设备交互。