版权声明:我已加入“维权骑士”(http://rightknights.com)的版权保护计划,知乎专栏“网路行者”下的所有文章均为我本人(知乎ID:弈心)原创,未经允许不得转载。
如果你喜欢我的文章,请关注我的知乎专栏“网路行者”https://zhuanlan.zhihu.com/c_126268929, 里面有更多像本文一样深度讲解计算机网络技术的优质文章。
前言
时间过得很快,距离我《网络工程师的Python之路》系列文章的连载、视频教学的发布以及电子书和纸质书的发行已经过去快四年了。作为华语圈Netdevops的领路人和布道者,这四年来坚持不断的创作让国内成千上万名计算机网络界的同行从完完全全的编程零基础逐渐走入了Netdevops的世界,体会到了Netdevops的强大以及它带给传统计算机网络运维方式颠覆性的改变。在这里也感谢大家一直以来的支持,目前《网络工程师的Python之路》的微信和QQ读者群加起来已有2000多人,购买了《网络工程师的Python之路》一书并且希望加入华语圈Netdevops学习大家庭的可以在知乎上私信联系我。
不可否认,Netdevops这个近几年崛起的新兴领域的技术话语权被牢牢地掌握在欧美国家的同行手里。起步晚、语言劣势以及访问外网不够方便等因素导致国内的Netdevops圈子和国外有着不小的信息差。随着scrapligo和gornir的诞生,目前国外Netdevops界有着从Python朝Go语言发展的趋势,很多这一领域的知名人物比如Kirk Byers、David Barroso、Carl Montanari、John McGovern、Patrick Ogenstad等都逐渐开始了对Netdevops在Go语言中实现的探索。并且目前国内有一些大厂和外企也开始在招聘广告里放出了对掌握了Golang语言的Netdevops人才的需求,比如今天(2022年1月5日)刚放出的特斯拉招聘Sr. Network DevOPS Engineer职位里就提到了对Golang的需求。

为了拉近与国外同行的差距,让国内的网络工程师们也能紧跟国外同行的步伐,同时提升自己在职场中的竞争力,我决定继《网络工程师的Python之路》、《网络工程师的Django之路》之后继续创作《网络工程师的Golang之路》。和Python之路一样,我会先尝试在我的知乎专栏上进行Golang之路教学的连载,大概每隔2-3个星期大幅更新一次,预计2022年上半年可以完全写完。如果反响不错的话,下半年我会联系出版社出书。
和当时写Python之路时的初衷一样,创作Golang之路并不是想让大家成为Go语言的专家(毕竟术业有专攻,我们不是职业的程序员),但至少能让我们在Netdevops中较为熟练地使用Go语言,丰富我们在编程领域的知识库并在提升计算机网络运维的效率中有一番作为(我会从网络工程师的角度出发,挑选适合网工学习的Go语言知识点来教学)。另外因为几乎所有接触Netdevops的都是从Python学起的,因此在Golang之路的基础教学里我会大量给出两种语言的对比,帮助已经有Python基础的大家更好的融会贯通,更快地掌握Go语言。
Go语言的前世今生
Go是一门开源的编译型编程语言,支持Linux、MacOS、Windows等所有主流操作系统。Go语言诞生于2007年末,最初是Google公司的内部项目,由罗伯特·格里泽默(Robert Griesemer,Java HotSpot虚拟机和js v8引擎的开发者)、肯·汤姆森(Ken Thomson,C语言设计者,Unix创始人,1983年图灵奖获得者)和罗伯·派克(Rob Pike,Unix核心成员)等人主持开发,2009年11月开源,并在2012年初发布了较为稳定的Go 1版本。经过10几年的锤炼,现在Go语言的开发已经完全公开并且拥有十分成熟、活跃的社区。
Robert Griesemer,Ken Thomson,Rob Pike等人开发Go语言的初衷是希望设计一门高效、可靠并且具有高健壮性,面向专业程序员的编程语言,因为诞生年代较晚,Go很好的借鉴了其他程序语言的特点。下图是直接或间接地影响过Go的程序语言:

可以看到Go语言的语法上与C语言类似,而对包(package)的使用则是受到了Modula-2语言的影响。
很多人应该都听说过Go语言的另外一个名字:Golang。Go之所以又被叫做Golang,完全是因为当年Google官方无法给Go语言注册域名http://go.org,只能以http://golang.org作为替代(如今Go的官网域名已变为go.dev)。这样做其实还有一个好处就是如果你在网上搜索Go语言的相关词条时,因为Go这个词使用太广泛,往往会引出一些我们不感兴趣的内容,用Golang的话则能让搜索结果更精确,另外Go在推特的的主题标签使用的也是#golang,也是出于同样的考虑。本文中Go和Golang我会随意使用,不存在任何歧义。
Golang vs Python
鉴于大家已经有了Python的基础,这里讲解一下Golang对比Python的一些主要区别:
- Go是编译型语言,Python是编译+解释型语言。要理解两者的区别,首先我们需要知道计算机是不能识别高级语言的,所谓高级语言是指独立于机器,面向过程或对象的语言,Go和Python都属于高级语言。既然计算机不能识别高级语言,那当我们运行高级语言的程序时就需要一个翻译器来把高级语言转化成计算机能够识别的机器语言,这个过程分为两类,第一种是编译,第二种是解释。所谓编译型语言,是指在程序运行之前通过编译器将高级语言的程序转换成机器语言,运行时就不再需要翻译直接执行就可以了,因此编译型语言效率很高。而解释型语言则没有编译这个过程,而是在程序运行的时候通过解释器来对高级语言的代码做逐行翻译。Python是一门先编译后解释的语言(从Python一路学过来的网工读者肯定在运行自己的py脚本后看到过pyc这个文件,这里的c就是compiled的缩写,即编译的意思)。Python执行程序的时候是由Python解释器逐行编译+解释后才运行,所以Python的性能会低于编译型语言。为了提高性能,Python解释器会将模块的编译+解释的结果保存在.pyc中,这样下次执行脚本的时候就省了编译这个环节从而达到提高性能的目的。
- Go不是面向对象的语言,因此Go中是没有类(class)的概念的。Go中有Python不存在的结构体(struct)的概念。
- Python是动态类型语言,创建变量时无需指定变量的数据类型。而Go是静态类型语言,创建变量时需要手动指定变量的数据类型(当然Go在某种程度上也支持动态类型)。不过两者有个共同点是:Python和Go分别在动态语言和静态语言中都是最易学易用的编程语言之一。
- Go天生支持并发(基于goroutine和channel实现),而Python直到Py3.2才引入concurrent.futures, 直到Py3.4才引入asyncio等内置库。
- Go和Python的定位存在差别,Python主要专注于后端服务,Web开发,数据处理或者单纯作为脚本使用,更适合做运维开发。而Go的定位则是系统级的编程语言,比如最近几年特别火的K8S,Prometheus ,Terraform等都是用Go开发的。
- Go相较于Python最大的优势在于速度,Go本质上是C/C++的进化版本(Google开发Golang最初的目的就是为了解决C++处理高并发服务时的很多问题)因此Go的执行效率可与C/C++相媲美,当年零基础从单线程开始学习Python的网工肯定会惊叹于异步、协程、多线程实现并发后带来的速度的提升,但是Go的并发速度理论上还要比Python快上30-50倍。
- 不过鱼与熊掌是不可兼得的,虽然Go的速度很快,但是Python相较于Go的优势在于代码简洁,实现同样的功能,Python的代码量基本上只有Go的一半甚至更少。
- 因为是编译型语言,Go的编译器能在我们写代码的时候自动帮我们识别并纠正一些编程中常见的错误,比如语法错误、缩进、变量类型错误、变量声明或包导入后却没有使用等,这一点是Python不具备的。
- Go的生态链相较于Python也还有些差距,Go的标准库和Python的标准库一样丰富,但是第三方库方面和Python还有不小的差距,尤其是Netdevops领域(目前国外的Netdevops圈子正在为Go添砖加瓦)。
- 对网工来说,从目前(2022年)来看,Python更适合做网络运维自动化的开发,尤其对一些比较古老的不支持netconf、restconf、gRPC等API的网络设备来说,Python相比Go是有绝对优势的。但是随着scrapligo、gornir等工具的诞生以及旧设备的淘汰更新,我相信Go会在未来吸引更多网工的关注,尤其是那些在超大型网络里工作,对Netdevops效率有追求的网工。
安装Golang
根据Python之路的教学经验,使用Windows的读者占多数,因此Golang之路我会以Windows为操作系统进行教学。
1. 进入Golang官方下载网站: https://golang.org/dl/ ,选择自己平台需要的Golang版本,截止2021年8月3号,Golang最新的版本为go1.1.16,这里我选择下载Windows版本的msi安装文件。

2. 下载好Golang Windows安装包后直接运行,Golang的Windows版本安装很简单,一路点击Next即可,这里我将Golang安装在C:\Program Files\Go文件夹下:

3. 安装完成后,进入CMD命令行,输入go version确认GO的版本。

如果继续输入命令path,则可以看到Go文件夹下有一个叫做bin的子文件夹,所有和Golang相关的可执行命令都安装在该子文件夹下。

最后输入Go env命令,如果能看到下面的内容,则说明Go安装成功。

IDE的选择和设置
Go的IDE选择很多,知名的JetBrains(Pycharm的发行公司)专为Go语言开发了Goland,不过Goland只有30天的试用期,过后需要付费。之前我在Python之路课程里用到的Sublime Text 3对Go语言的支持较差,必须安装额外的插件才能在Sublime IDE里直接运行Go的脚本文件,比如Gosublime,对新人不太友好。除此之外还有Goclipse,LiteIDE和VSCode等几种选择,Goclipse和LiteIDE是专门针对Go开发的IDE,前者更适合专业的Go语言程序员使用,而后者则是轻量级的Go IDE,比较简单易用。不过相较于Goclipse和LiteIDE,从Python转战Go的大家应该对微软的VSCode更为熟悉,VSCode完全免费并且支持在IDE里直接运行Go的脚本文件,加上不少读者正在或曾经使用过VSCode,因此本文将选择VScode作为Go的IDE来讲解。
- 在VSCode里使用Go很简单,在左边工具栏里点击Extensions,然后输入Golang进行查找,然后安装Go 0.26.0这个扩展包即可。

2. 然后在桌面或者任意位置创建一个文件夹,取名为Golang。
3. 在VSCode里依次点击File --> Open Folder,打开刚才创建好的Golang文件夹,然后点击New File这个按钮,创建我们的第一个Go脚本:hello.go


4. 写入我们的第一个Go程序(Hello World!):

5. 然后点击Run-->Run Without Debugging,如果在DEBUG CONSOLE没有报错,并且看到Hello World!的话则说明第一个Go程序在VSCode里运行成功。

注:如果你在VSCode中运行Go程序时遇到下面的错误:
Build Error: go build -o c:\Users\WANGY0L\Desktop\Golang\__debug_bin.exe -gcflags all=-N -l c:\Users\WANGY0L\Desktop\Golang go: go.mod file not found in current directory or any parent directory; see 'go help modules' (exit status 1)

这是因为从Go 1.16版本开始,GO111MODULE这个环境变量的默认值是设为on的,意味着Go需要找到go.mod这个文件才能运行,解决方法是在VSCode里打开TERMINAL,然后输入go env -w GO111MODULE=auto将其重新设为auto:

Go语言的main包和main()函数
我们把上面创建的第一个Go程序:hello.go作为范例,来逐行讲解其中代码的含义及其背后的Go语言的基础知识:
包(package)是Go语言最基本的管理单位,它是多个Go源码的集合,是一种高级的代码复用方案(是的,和Python中的库或模块是一个意思)。package main表示一个可独立执行的程序,这里我们在hello.go里的第一行写入了pakage main,表明hello.go这个程序属于main这个包。每个Go程序必须有一个叫做main的包,正常情况下,我们可以使用go build这个命令来编译一个go程序,编译成功后,系统会生成一个和go文件同名的exe可执行文件,举例如下:
在vSCode的工具栏里点击Terminal-->New Terminal打开终端,然后在命令行里输入go build hello.go对hello.go进行编译,随后便可以在hello.go所在的GOLANG文件夹下看见编译后新生成的hello.exe文件。

有了exe文件后,我们可以直接在命令行里输入.\hello.exe来执行它,可以看到该exe程序同样返回了Hello World!的内容。

使用go build命令先编译go文件,生成exe文件后再运行的这个方式显得很繁琐,同样的操作,我们可以直接使用go run hello.go来完成。这里我们将hello.exe文件删掉,然后在终端命令行里输入go run hello.go,可以看到程序同样返回了Hello World!的内容:

注:go build和go run都可以用来执行Go程序,它俩的本质区别在于go build会先将go程序进行编译,然后创建一个exe可执行文件,然后我们再手动运行该exe文件,这是典型的编译型语言的做法。而go run则是在背地里替我们对go程序进行编译、创建exe文件、执行该exe文件、执行结束后删除该exe文件等操作(这一系列操作是我们肉眼看不到的)。通俗来说,go run更类似于Python执行脚本的方法,本文中大部分代码示例我都会使用go run来执行Go程序,因为更便捷。
在有go run这个更便捷的方式下,这里之所以要介绍go build这个命令是为了强调“每个Go程序必须有一个叫做main的包”这个原理。如果Go程序里没有使用main包,那么我们将无法通过go build命令编译出可执行的exe文件。举个例子,这里我们把package main改成package abc,然后在TERMINAL下面输入go build hello.go,这时发现编译后无法再生成可执行的exe文件。

同样的道理,如果这时我们尝试使用go run .\hello.go来执行这个程序,会发现系统返回了一个错误“go run: cannot run non-main package”,提示我们Go无法运行没有main的包。

那么是否说package后面就只能接main,不能接其他任何内容呢?答案是否定的,如果我们编写的go程序的目的是让它用来被其他程序当作第三方模块调用的话,那么使用main以外的包名是允许的,比如这里我们用package abc作为这个go程序的包名,那我们可以在另外一个使用package main的主程序里通过import "abc"来调用这个叫做abc的包,只不过我们确实不能通过go build或者go run来直接编译这个叫做abc的包,关于非main包的调用方式笔者会在后面的章节中详细介绍。
Go的import这个关键字和Python里的import一样,用来导入一个已经存在的Go语言包(也可以和Python一样叫做“库”或者“模块”),导入的这个包既可以是标准包也可以是第三方包。和Python不同,Go中更容易区别一个包是标准包还是第三方包,因为所有第三方包都需要在import后面将它的完整下载路径写出来(通常是该包所在Github的网址),比如import "https://github.com/spf13/cobra",而导入标准包时则不需要写任何网址,很容易区别。
这里我们导入的fmt(英文format的缩写)是最常用的Go语言标准包之一,它提供了我们这里用到的Println()这个函数,在Go中,fmt包提供了三种和print输出相关的基础函数,分别为:
Print(): 和Python的print()类似,用来打印出纯字符串内容,不会在结尾加上\n换行符,也不具备字符串格式化的功能。
Printf(): Printf是"Print Formatter"的缩写,顾名思义,它提供字符串格式化的功能,和Print()一样,Printf()也不会在打印内容的结尾加上\n换行符。
Println(): Println是"Print Line"的缩写,它和Printf()一样具备字符串格式化的功能,两者唯一的区别是Println()会给打印内容的结尾加上\n换行符。
一个Go语言包只能有一个main()入口函数
在Go语言中,main()函数属于我们前面讲到的main包,main()函数是程序的入口,一个能独立运行的Go程序必须有一个main(),main()下面包含的代码就是整个程序要做的事情,注意Go中函数的语法格式和Python有较大差异,首先,Go中定义函数的关键字为func(Python中为def),其次函数的内容是写在{}括号里面,函数名后面不再像Python那样需要接一个冒号:。
在Go语言中,一个程序里有且只能有一个main(),所有我们通过import导入的包里的函数,或者用户自定义的函数都必须直接或间接地在main()函数中调用,另外main()函数本身不带任何参数也不会返回任何值。
综上,一个可执行的Go程序必须具备一个main包,main包里必须包含main()这个入口函数。运行Go程序的本质就是运行main()函数,main()函数运行完毕也就意味着程序执行完毕。
下面来做练习,首先回到VSCode中,在GOLANG文件夹下新建一个Go文件,将其取名为variable.go,然后写入下列代码:
这时如果你观察仔细的话,会在VSCode里的PROBLEMS里看到“main redelcared in this block”的警告提示,如下图所示:

这是因为在Go语言中一个目录(文件夹)代表一个包,一个文件夹下的同级go文件归属于同一个包,一个包下面只能有一个main()入口函数。这里我们在hello.go文件所在的GOLANG文件夹下另外创建了一个叫做variable.go的文件,此时hello.go和variable.go都在GOLANG这个文件夹下面,也就意味着它们同属于一个包,因为前面我们已经在hello.go里使用了main()函数,因此不能再在variable.go里面使用main()函数(注意当出现一个文件夹下出现多个main()函数的情况时,VSCode给出的“main redelcared in this block”只是警告提示,并不是错误提示,这也就意味着hello.go和variable.go这两个程序其实依然可以正常运行,读者可以通过go run hello.go和go run variable.go来分别运行这两个程序做验证)。
解决这个警告提示的办法有两种:
1. 为variable.go单独创建一个目录,比如这里我们在GOLANG文件下面新建一个叫做VARIABLE的子文件夹,然后将variable.go放进去,此时因为variable.go和hello.go已经被隔离开分属不同的目录(包),所以之前的VSCode给出的“main redelcared in this block”警告提示就此消失。

2. 对一个目录(包)下存在两个main()函数放任不管,因为“main redelcared in this block”只是一个警告,并不是错误,因此并不妨碍我们在终端下通过go run来分别运行variable.go和hello.go两个程序:

上述两种方法出于严谨的考虑,我推荐使用第一种方法,即给每个go程序都额外创建一个独立的目录。
下一篇连接: