Golang8小时基础入门

参考链接:哔哩哔哩视频在线文章

1 Golang安装和环境变量

  首先下载安装包,Golang镜像网站Golang中文镜像网站。Windows直接安装,Linux解压到要安装的文件夹下即可。
  设置环境变量
GOROOT,设置为安装路径即可,例如:D:\Program Files\go
GOPATH,即我们写go语言的工作路径,可以自定义,例如:D:\Program Files\go\GoWorks
然后在path中加上%GOROOT\bin
  验证是否安装和配置成功:

go version

在这里插入图片描述
  IDE如果是免费的选择VSCode,收费的选择Goland。也可以Vim+go插件。

2 Golang语言特性

2.1 优势

1、极简单的部署方式
  可直接编译成机器码,不依赖其他库,可以直接运行部署。
2、静态类型语言
  编译的时候可以检查出隐藏的大多数问题(一般静态语言的优势)。
3、语言层面的并发
  go语言是基因支持的并发,很多语言其实是“美容”的并发,一层包一层实现高并发。go的语法使得能够充分地利用多核,切换成本低,尽量提高CPU并发效率。
4、强大的标准库支撑
  有runtime的系统调度机制,高效的垃圾回收,丰富的标准库。
5、简单易学
  仅有25个关键字,语法从c语言过渡,内嵌c语法支持,具有面向对象特征(继承、封装、多态),跨平台。
6、大厂领军
  国内外大公司有在使用go语言,例如Google,Facebook,腾讯,百度,字节跳动,京东,小米,阿里巴巴,哔哩哔哩等等。

例子:斐波那契亚数列算法下不同语言效率对比
  就这个例子而言,可以看到不管是编译还是运行Go都是比较快的。
在这里插入图片描述

2.2 适合做什么

1、云计算基础设施领域
2、基础后端软件
3、微服务
4、互联网基础设施

2.3 明星作品

Docker和Kubernetes
在这里插入图片描述

2.4 缺点

1、包管理,大部分第三方库都在Github上,代码稳定性有风险
2、无泛化类型,目前在Go1.18已经加上了泛型
3、全部Exception都采用Error处理
Java是极端地把全部Error都用Exception处理,python是取了中间两种都可以,而Golang是极端地把全部Exception都用Error处理,没有谁对谁错之分
4、对C语言的降级处理并不是无缝的,没有降级到汇编那么完美,但目前只有go能够这样做
c语言是唯一能够和操作系统交流的语言,

3 Golang语法新奇

3.1 从main函数初见go的语法

package main

import "fmt"

func main() {
	fmt.Println("Hello Go!")
}

然后使用go run hello.go既编译又运行,或者先go build hello.go会生成可执行程序,再.\hello执行。
1、有没有分号都可以,对编译影响不大,建议不加
2、导包方式有两种,导入多个包建议后者

import "fmt"
import "time"

或者

import (
	"fmt"
	"time"
)

3、方法的左花括号必须要和函数名同一行

3.2 变量

3.2.1 单变量声明

1、声明一个变量,不初始化,默认值是0

var a int

2、声明一个变量,并初始化

var b int = 100

3、初始化时省去类型声明,通过值的类型自动匹配(不推荐)

var c = 100
fmt.Printf("%T", c)

4、(常用)省去var关键字,直接匹配

d := 100

区别:声明全局变量(方法外)可以用前三种,不能用第四种

3.2.2 多变量声明

1、数据类型相同

var x, y int  = 100,  200

2、数据类型不同

var k, l = 100, "hello"

3、多行写法,类型可不声明

var (
i (int) = 100
j (bool) = true
)

3.3 常量与iota

常量命名

const length int = 10

const定义枚举

const (
	BEIJING  = 0
	SHANGHAI = 1
	SHENZHEN = 2
)

或者使用iota,只要第一个赋值iota,它默认是0,每行依次加1

	const (
		BEIJING = iota
		SHANGHAI
		SHENZHEN
	)

如果写成10*iota,则依次是0,10,20。相当于后面都是符合前面的iota表达式,如果中间改变表达式,后面也会改变,但是iota累加的值保持。

const (
	a, b = 1 + iota, 2 + iota
	c, d
	e, f
	g, h = 2 * iota, 3 * iota
	i, j
)
fmt.Println(a, b, c, d, e, f, g, h, i, j)

在这里插入图片描述

3.4 函数

函数声明,括号内是形参,右边是返回值

func f1(a string, b int) int {
	fmt.Println(a)
	fmt.Println(b)
	c := 100
	return c
}

多返回值,用括号括起来
1、匿名返回值

func f2(a string, b int) (int, int) {
	fmt.Println(a)
	fmt.Println(b)
	return 111, 222
}

2、给返回值命名

func f3(a string, b int) (r1 int, r2 int) {
	fmt.Println(a)
	fmt.Println(b)
	r1 = 111
	r2 = 222
	return 
}

3、如果返回值类型一样,可以只保留一个返回值类型

func f4(a string, b int) (r1, r2 int) {
	fmt.Println(a)
	fmt.Println(b)
	r1 = 111
	r2 = 222
	return
}

3.5 init函数与导包

  • init函数
      从图中可以看出,程序先从main包进入,再递归导包,然后执行包中的常量,变量,init函数,并依次返回,最后执行main方法。
    对外开放的方法首字母大写。
    在这里插入图片描述
  • import导包
    go语言的包导入如果不调用会报错。

1、如果不想使用某一个包的API,但是要使用这个包的init函数,可以匿名导包

import _ "lib1"

2、可以给包起别名,并且可以调用它的方法

import mylib2 "lib2"

3、将包的全部方法导入本包,调用方法时可以不带包名(少使用,防止函数名冲突)

import . lib2

3.6 指针

默认情况下方法是值传递

package main

import "fmt"

func main() {
	a := 1
	changeValue(a)
	fmt.Println(a)//输出为20,说明a的值未改变
}

func changeValue(p int) {
	p = 10
}

可以指针传递,*int表示是指向int类型的指针,p处存储的就是a的地址值,*p表示找到存的地址值对应的地址,然后改变值为10,&表示传入地址

package main

import "fmt"

func main() {
	a := 1
	changeValue(&a)
	fmt.Println(a)//输出10,说明a的值被改变
}

func changeValue(p *int) {
	*p = 10
}

在这里插入图片描述
经典例子:交换数据
如果这样交换,并不能交换成功,因为是值传递

package main

import "fmt"

func main() {
	a := 10
	b := 20
	swap(a, b)
	fmt.Println(a, b)
}

func swap(a int, b int) {
	tmp := a
	a = b
	b = tmp
}

需要使用指针

package main

import "fmt"

func main() {
	a := 10
	b := 20
	swap(&a, &b)
	fmt.Println(a, b)
}

func swap(a *int, b *int) {
	tmp := *a
	*a = *b
	*b = tmp
}

二级指针:指针的指针

3.7 defer关键字

有点像c++的析构函数或者Java中的finally关键字
defer语句放在return之前,在当前函数结束,return返回后执行,defer可以有多个,但是按照栈的顺序,先写的后执行。

package main

import "fmt"

func main() {
	returnAndDefer()
}

func returnFun() int {
	fmt.Println("return...")
	return 0
}

func returnAndDefer() int {
	defer fmt.Println("defer...")
	return returnFun()
}

3.8 数组和动态数组slice

声明数组

var myArray1 [10]int
myArray2 := [10]int{}

数组如果作为形参,要声明长度,而且是值拷贝,方法内不改变数组的值

func printArray(myArray [10]int) {
	for i, value := range myArray {
		fmt.Println(i,value)
	}
}

动态数组声明,相比之下不指定长度

var myArray1 []int
myArray1 := []int{1,2,3,4}

方法如下,动态数组是指针传递,因此方法内会改变原有的值

func printArray(myArray []int) {
	for i, value := range myArray {
		fmt.Println(i, value)
	}
}

另外,再使用range遍历时,如果索引不想使用,可以使用匿名的方式for _, value := range myArray

3.8.1 slice声明方式

四种声明slice方式如下:

slice1 := []int{1, 2, 3}
var slice2 []int
var slice3 []int = make([]int,3)//通过make分配空间
slice4 := make([]int,3)

if slice2 == nil {
	fmt.Println("空切片")
} else {
	fmt.Println("有空间")
}

输出空切片,要注意else要和上一个右花括号放在同一行。

3.8.2 slice使用方式

1、切片容量的追加
长度小于等于容量
在这里插入图片描述
append方法追加一个元素并赋值,如果cap不够会追加cap容量

var slice = make([]int, 3, 5)
fmt.Println(len(slice), cap(slice), slice)
slice = append(slice, 1)
fmt.Println(len(slice), cap(slice), slice)
slice = append(slice, 2)
fmt.Println(len(slice), cap(slice), slice)
slice = append(slice, 3)
fmt.Println(len(slice), cap(slice), slice)

在这里插入图片描述
扩容机制:根据cap增加二倍,即每次翻一倍
2、切片的截取
这里和python有些类似,但是这里是指针传递,改变slice2会改变slice。

slice1 := slice[0:2]//取头不取尾
slice2 := slice[:5]
slice3 := slice[3:]
slice4 := slice[:]

copy函数可以深拷贝,前一个参数是destination,后一个参数是source

slice := []int{0, 1, 2, 3, 4, 5, 6}
slice5 := make([]int,7)
copy(slice5,slice)

3.9 map

三种声明方式:

var myMap1 map[string]int
myMap1 = make(map[string]string, 10)
myMap1["one"] = "java"
myMap1["two"] = "c"
myMap1["three"] = "python"
myMap2 := make(map[string]string)
myMap3 := map[string]string{
	"one":   "c++",
	"two":   "java",
	"three": "python",
}

使用方式:

//遍历
for key, value := range cityMap {
	fmt.Println(key)
	fmt.Println(value)
}
//删除
delete(cityMap, "Japan")
//修改
cityMap["USA"] = "Washington"

3.10 面向对象特征

  type声明一种新的数据类型,可以定义结构体,即把多种基本数据类型组合形成复杂的数据类型。%v可以格式化各种类型的输出。

type Book struct {
	title string
	autu  string
}
var book1 Book
book1.title = "Golang"
book1.autu = "zhangsan"
fmt.Printf("%v\n", book1)

如果需要函数中改变值,需要传指针

changeBook(&book1)

func changeBook(book *Book) {
	book.auth = "666"
}

  go语言中的类其实就是结构体绑定方法,

type Hero struct {
	Name string
	Ad   int
	Level string
}
func (this Hero) GetName() {
	fmt.Println("Name = " + this.Name)
}
func (this Hero) SetName(newName string) {
	this.Name = newName
}
func (this Hero) Show() {
	fmt.Println("Name = ", this.Name)
	fmt.Println("Ad = ", this.Ad)
	fmt.Println("Level = ", this.Level)
}
//创建并初始化对象
hero := Hero{Name: "zhangsan", Ad: 100, Level: 1}

要注意,this Hero是调用这个方法的对象的拷贝,因此SetName不会修改原来的属性值,要实现修改还是需要使用传指针

func (this *Hero) Show() {
	fmt.Println("Name = ", this.Name)
	fmt.Println("Ad = ", this.Ad)
	fmt.Println("Level = ", this.Level)
}
func (this *Hero) GetName() string {
	return this.Name
}
func (this *Hero) SetName(newName string) {
	this.Name = newName
}

3.10.1 封装

  前面已经提到了,方法名首字母如果大写,可以被其他包访问。结构体名字,属性首字母如果大写可以被其他包访问,如果小写只有包内部可以访问。这就是go语言的封装。

3.10.2 继承

  在SuperMan中Human就表示继承了Human这个类。可以重写方法,添加新方法。

type Human struct {
	name string
	sex  string
}
type SuperMan struct {
	Human
	level int
}

创建对象:

human := Human{"zhangsan", "female"}
superMan := SuperMan{Human{"lisi", "female"}, 100}
//或者
var s SuperMan
s.name = "zhangsan"
s.sex = "male"
s.level = 3

3.10.3 多态

interface本质是一个指针,在实现类只要重写三个方法就实现了接口。如果重写不完全,接口就不能指向这个实现类。

type AnimalIF interface {
	Sleep()
	GetColor() string
	GetType() string
}

type Cat struct {
	color string
}
func (this *Cat) Sleep() {
	fmt.Println("Cat is sleeping")
}
func (this *Cat) Getolor() string {
	return this.color
}
func (this *Cat) GetType() string {
	return "Cat"
}

创建接口指向实现类,需要把对象的地址传过去。

var animal AnimalIF
animal = &Cat{"green"}
animal.Sleep()
animal = &Dog{"blue"}
animal.Sleep()

多态的方法

func showAnimal(animal AnimalIF) {
	animal.Sleep()
	fmt.Println("color = ", animal.GetColor())
	fmt.Println("type = ", animal.GetType())
}

cat := Cat{"black"}
dog := Dog{"yellow"}
showAnimal(&cat)
showAnimal(&dog)

3.10.4 万能类型

  • 基本数据类型都实现了interface{},可以用interface{}引用任意类型。
func myFunc(arg interface{}) {
	fmt.Println("myFunc is called...")
	fmt.Println(arg)
}

book := Book{"golang", "zhangsan"}
myFunc(book)
myFunc(100)
myFunc("abc")
myFunc(3.14)
  • go语言提供了“类型断言”机制,判断是否是某种类型
func myFunc(arg interface{}) {
	value, ok := arg.(string)
	fmt.Println(value, ok)
}
  • 变量的内置pair
    一个变量包含类型type和值value,类型要么是静态类型,要么是具体类型。type和value组成pair
    在这里插入图片描述
    示例,赋值的时候pair不会改变
var a string
a = "abc"
var allType interface{}
allType = a
str, ok := allType.(string)
fmt.Println(str, ok)

或者

type Reader interface {
	ReadBook()
}
type Writer interface {
	WriteBook()
}
type Book struct {
}
func (this Book) ReadBook() {
	fmt.Println("Read a Book")
}
func (this Book) WriteBook() {
	fmt.Println("Write a Book")
}

func main() {
	b := Book{}
	var r Reader
	r = b
	r.ReadBook()
	var w Writer
	w = r.(Writer)
	w.WriteBook()
}

上述例子之所以成立是因为“赋值的时候pair不会改变”,复制过去的pair是<type:Book,value:&Book{}>

3.11 反射

  在reflect包,两个重要API:TypeOf和ValueOf

func main() {
	var num float64 = 1.2345
	refelctNum(num)
}
func DoFileAndMethod(input interface{}) {
	//获取类型
	inputType := reflect.TypeOf(input)
	fmt.Println("input type is:", inputType.Name())
	//获取值
	inputValue := reflect.ValueOf(input)
	fmt.Println("input value is:", inputValue)
	//获取字段Field
	for i := 0; i < inputType.NumField(); i++ {
		field := inputType.Field(i)
		value := inputValue.Field(i).Interface()
		fmt.Println(field.Name, field.Type, value)
	}
	//获取方法并调用
	for i := 0; i < inputType.NumMethod(); i++ {
		m := inputType.Method(i)
		fmt.Println(m.Name, m.Type)
	}
}

3.12 结构体标签Tag

注意要用反斜杠,里面是键值对,中间用空格隔开,主要的作用是根据这个标签,判断这个属性在不同包中怎么用。

type resume struct {
	Name string `info:"name" doc:"我的名字"`
	Sex  string `info:"sex"`
}
func findTag(str interface{}) {
	t := reflect.TypeOf(str).Elem()
	for i := 0; i < t.NumField(); i++ {
		tagString := t.Field(i).Tag.Get("info")
		fmt.Println("info:", tagString)
	}
}
func main() {
	var re resume
	findTag(&re)
}

3.12.1 结构体标签的应用

  • 在json中的应用,编解码
    在转换为json时,会先检查是否在标签中有json键值对,有则将值取出来组成json字符串。
type Movie struct {
	Title  string   `json:"title"`
	Year   int      `json:"year"`
	Price  int      `json:"price"`
	Actors []string `json:"actors"`
}
func main() {
	movie := Movie{"喜剧之王", 2000, 10, []string{"周星驰", "张柏芝"}}
	//编码的过程 结构体-->json
	jsonStr, err := json.Marshal(movie)
	if err != nil {
		fmt.Println("json marshal error", err)
		return
	}
	fmt.Printf("jsonStr=%s\n", jsonStr)
	//解码的过程 json-->结构体
	myMovie := Movie{}
	err = json.Unmarshal(jsonStr, &myMovie)
	if err != nil {
		fmt.Println("json unmarshal error", err)
		return
	}
	fmt.Println(myMovie)
}
  • orm映射关系

4 Golang高阶

4.1 协程

4.1.1 co-routine

  进程或线程数量越多,切换成本越高,CPU资源越浪费,此外还有高内存占用的弊端。一个线程分为用户态和内核态,划分之后,内核线程称为线程thread,用户线程称为协程co-routine。通过一个内核线程和协程调度器,绑定多个协程。内核线程和协程之间的关系不适合一对多或者一对一,适合N对M。从图中可以看出主要需要优化的就是协程调度器。
在这里插入图片描述

4.1.2 Golang的协程——Goroutine

  Golang的协程内存小,几KB,灵活调度。G表示goroutine协程,P表示处理器,M表示内核线程。
调度器的设计策略:

  • 服用线程
  • 利用并行
  • 抢占
  • 全局G队列
    在这里插入图片描述

go语言创建协程非常方便,使用go关键字加上函数即可。
第一种调用,go + 方法

//子Goroutine
func newTask() {
	i := 0
	for {
		i++
		fmt.Printf("new Goroutine : i = %d\n", i)
		time.Sleep(1 * time.Second)
	}
}

//主Goroutine
func main() {
	go newTask()
}

第二种调用,匿名的go协程,匿名方法也可以有形参和返回值,但是返回值不能直接用参数去接,如果要接,其实就是要解决协程之间的通信,这里就要用到channel了

func main() {
	//用go创建承载一个形参为空,返回值为空的函数
	go func() {
		defer fmt.Println("A.defer")
		//匿名函数
		func() {
			defer fmt.Println("B.defer")
			runtime.Goexit()//退出当前协程,而不仅是匿名函数
			fmt.Println("B")
		}()
		fmt.Println("A")
	}()
	for {
		time.Sleep(1 * time.Second)
	}
}

4.2 协程的通信——channel

  先定义channel类型变量,然后在协程中通过<-将值赋给channel变量,最后在主协程中获取channel变量的值。channel有同步两个协程的能力,

func main() {
	//定义一个channel
	c := make(chan int)
	go func() {
		defer fmt.Println("goroutine结束")
		fmt.Println("goroutine正在运行...")
		c <- 666
	}()
	num := <-c
	fmt.Println("num = ", num)
	fmt.Println("main goroutine结束...")
}

  示意图如下,如果main协程更快执行到了num := <-c,会被阻塞,等待channel中有值,如果sub协程更快执行,那么main协程能顺利取到管道的值。
在这里插入图片描述

4.2.1 无缓存的channel

  对于无缓存的channel,传递消息的一方如果提前到达了要传递channel的指令,但此时接收协程还没有执行到接收channel,那么发送方就需要一直等待,直到接收方来接收。
在这里插入图片描述

4.2.2 有缓存的channel

  对于有缓存的channel,发送方将数据发送到channel中便继续执行程序,如果管道中有数据,接收方直接取走数据;发送方发现channel中已经存满了数据时才会被阻塞,接收方直到channel中被取空了才会被阻塞。类似生产者消费者模式。
在这里插入图片描述
代码实例:

func main() {
	c := make(chan int, 3)
	fmt.Println(len(c), cap(c))
	go func() {
		defer fmt.Println("子goroutine结束...")
		for i := 0; i < 3; i++ {
			c <- i
			fmt.Println("子goroutine正在运行:len(c)=", len(c), "cap(c)=", cap(c))
		}
	}()
	for i := 0; i < 3; i++ {
		num := <-c
		fmt.Println("num=", num)
	}
	fmt.Println("main goroutine结束")
}

4.2.3 关闭channel

  • channel不像文件一样需要经常去关闭,除非确定不会再向channel中发送数据,或者想显式地结束range循环,才会去关闭channel。
  • 关闭channel后无法向它发送数据
  • 关闭channel后可以继续接收数据
  • 对于nil channel,无论收发都会被阻塞
func main() {
	c := make(chan int)
	go func() {
		for i := 0; i < 5; i++ {
			c <- i
		}
		close(c)
	}()
	for {
		if data, ok := <-c; ok {
			fmt.Println(data)
		} else {
			break
		}
	}
	fmt.Println("Main Finished..")
}

  这里if后面的语法意思是这样的:先执行data, ok := <-c,这样data和ok都是if里面的局部变量,然后把ok作为条件判断是否执行。

4.2.4 channel和range

类似的,可以用range来获取channel中的数据,把上面的例子修改,代码如下:

func main() {
	c := make(chan int)
	go func() {
		for i := 0; i < 5; i++ {
			c <- i
		}
		close(c)
	}()
	for data := range c {
		fmt.Println(data)
	}
	fmt.Println("Main Finished..")

4.2.5 channel和select

  单个goroutine下只能监控一个channel的状态,select能够实现监控多个channel的状态。

func fibonacii(c, quit chan int) {
	x, y := 1, 1
	for {
		select {
		case c <- x:
			x = y
			y = x + y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}
func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()
	fibonacii(c, quit)
}

注意:方法传channel传的是指针。

5 Go modules 模块管理

  Go modules是Go语言的依赖解决方案,正式于go1.14推荐在生产上使用,目前只要安装了go就安装了go modules。

5.1 淘汰的Go path

由于go modules淘汰的是go path,因此需要知道go path工作模式。
Go path弊端:
1、没有版本控制概念
使用go get -u ...时不能指定版本
2、无法同步一致第三方版本号
别人编写的代码使用的库和我们使用的库版本可能不一致
3、无法指定当前项目引用的第三方版本号

5.2 Go modules模式

5.2.1 go mod命令

命令功能
go mod init生成go mod文件
go mod download下载go mod文件中指明的所有依赖
go mod vendor导出项目所有依赖到vendor目录

5.2.2 go mod环境变量

go mod环境变量通过go env来查看,重要的环境变量如下:

  • GO111MODULE
    推荐为on,现在高版本go默认都是on,如果不是可以使用命令go env -w GO111MODULE=on
  • GOPROXY
    自动导包的时候从哪个站点下载,默认是https://proxy.golang.org,direct,国内上不去,因此需要更换站点:
    阿里云:https://mirrors.aliyun.com/goproxy/
    七牛云·:https://goproxy.cn,direct
    设置命令为:go env -w GOPROXY=https://goproxy.cn,direct
    这里解释一下direct表示回源到模块的源地址去拉取,如果找不到会重定向到源地址拉取,最后找不到就会报错,因此加上就行。
  • GOSUMDB
    保证拉取到的模块版本数据是没有篡改过的,如果不一致将会立即终止。默认是sum.golang.org,国内访问不了。但只要GOPROXY设置过了,就可以不用设置。
  • GONOPROXY/GONOSUMDB/GOPRIVATE
    这三个环境变量表示哪些是不需要代理的,不需要校验的,哪些是私有的。只要配置GOPRIVATE一个变量,其余两个变量就被覆盖了。命令:go env -w GOPRIVATE="..."

5.3 使用Go modules初始化项目

1、保证GO111MODULE是on,具体解释参考上面

go env -w GO111MODULE=on

2、初始化项目

  • 任意文件夹创建一个项目
 mkdir modules_test
 cd modules_test
  • 初始化go modules模块,创建go.mod文件,注意要跟上当前模块名称
go mod init github.com/kevin-zkp/modules_test
dir //windows下查看当前目录文件

打开如下:
在这里插入图片描述

  • 引用代码
import (
	"fmt"
	"github.com/aceld/zinx/ziface"
	"github.com/aceld/zinx/znet"
)

//ping test 自定义路由
type PingRouter struct {
	znet.BaseRouter
}

//Ping Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
	//先读取客户端的数据
	fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))

	//再回写ping...ping...ping
	err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
	if err != nil {
		fmt.Println(err)
	}
}

func main() {
	//1 创建一个server句柄
	s := znet.NewServer()

	//2 配置路由
	s.AddRouter(0, &PingRouter{})

	//3 开启服务
	s.Serve()
}
  • 下载包
//手动download
go get github.com/aceld/zinx/znet
go get github.com/aceld/zinx/ziface
//可以指定版本号,如下
go get github.com/aceld/zinx@v1.0.0
//自动download
执行go run会自动下载

go.mod文件会多一行如下,如果指定版本,在go.mod中指定
在这里插入图片描述
再打开go.sum文件
在这里插入图片描述
h1加哈希

5.4 修改项目模块的版本依赖关系

使用命令:

go mod edit -replace=版本号1=版本号2
//例如
go mod edit -replace=zinx@v1.0.1=zinx@v1.0.0

如果报错:go: -replace=zinx@v1: need old[@v]=new[@w] (missing =)
可以尝试改成:go mod edit -replace='zinx@v1.0.1'='zinx@v1.0.0'
执行完会在go.mod文件下产生如下一行:
在这里插入图片描述

6 Golang案例——即时通信系统

  项目源码:
  目的是覆盖大部分go语法特性,特别是网络,系统基础结构如下,使用了读写分离模型,使用九个版本迭代:
在这里插入图片描述

6.1 版本一:构建基础Server

  这里创建main.go和server.go,都是在package main中。在main.go中创建Server并运行。在Server.go中首先创建结构体Server,构造函数,启动函数Start,以及处理业务Handler,每个连接都创建一个Handler协程。
  编写之后先运行main.go,再模拟tcp连接,linux下可以nc 127.0.0.1 8888,windows下telnet 127.0.0.1 8888,显示如下:
在这里插入图片描述

6.2 用户上线功能

  OnlineMap记录上线的用户,使用channel进行广播。
如果使用windows自带的telnet会中文乱码,可以下载putty,选择Other,telnet,输入Ip地址和端口号,点击open,可以看到成功上线。
在这里插入图片描述
在这里插入图片描述

6.3 用户消息广播机制

  用户输入一段话,也进行广播。回车换行"\r\n"。这里如果是windows下,由于telnet一次只能传递一个字符,因此对函数要进行一些改动,需要判断接收的字符是否是回车换行,如果不是则拼接字符串,如果是则进行广播。这里使用了读写分离模式,每个用户都有一个协程接收客户端消息,一个协程广播消息。效果如下:
在这里插入图片描述

6.4 用户业务层封装

  把能够合并的代码封装成函数,sever.go中的用户上线,下线,广播的代码需要提取到user.go中。需要为User添加属性,表示属于哪个Server。

6.5 在线用户查询

  当客户端输入who时,发送所有当前登录用户给客户端。
在这里插入图片描述

6.6 修改用户名

  消息格式rename|张三,修改效果如下:
在这里插入图片描述

6.7 超时强踢功能

  只要执行time.After就会重置,同时它其实是管道,只要监听管道中能否取到数据即可。把isLive写到上面,这样只要isLive触发,其他case会执行条件,但是不会执行括号内的代码,重置定时器。
在这里插入图片描述

6.8 私聊功能

  消息格式:to|张三|你好呀,我是...
在这里插入图片描述
在这里插入图片描述

6.9 客户端实现

这里还是用终端形式,也可以用UI形式。
1、实现连接
windows下编译成可执行文件,linux下去除.exe即可执行。

go build -o server.exe server.go main.go user.go
go build -o client.exe client.go

在这里插入图片描述
2、让客户端指定IP和端口
解析命令行,借助flag库,这样指定.\client.exe -ip 127.0.0.1 -port 8888

func init() {
	flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是127.0.0.1)")
	flag.IntVar(&serverPort, "port", 8888, "设置服务器端口号(默认是8888)")
}

3、菜单显示
在这里插入图片描述
4、更新用户名
在这里插入图片描述
在这里插入图片描述
5、公聊模式
在这里插入图片描述
6、私聊模式
首先查询当前有哪些用户在线,提示选择一个用户
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

7 Golang生态拓展介绍

1、Web框架
beego:国内的框架,文档比较全
gin:国外的轻量级框架,性能较高,比较主流
echo:国外的,轻量级
Iris:国外的,更加重量级,性能较高
2、微服务框架
go kit:包含很多工具,比较灵活
Istio:包括熔断,安全审核等,适合繁琐的大型微服务
3、容器编排
Kubernetes:市场占有率高,谷歌出来的
Swarm:相对不那么高
4、服务发现
类似Java中的zookeeper
consul:服务发现,服务注册
5、存储引擎
k/v存储——etcd:类似Redis,且支持分布式,一致性比较好
分布式存储——tidb:类似MySQL
6、静态建站
hugo
7、中间件
消息队列——nsg
TCP长链接框架(轻量级服务器)——zinx
Leaf(游戏服务器)——Leaf
RPC框架——gRPC
redis集群——codis
8、爬虫框架
go query:效率比python高,但是目前爬虫生态还是python更好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值