Golang8小时基础入门

Golang8小时基础入门

  • 1 Golang安装和环境变量
  • 2 Golang语言特性
    • 2.1 优势
    • 2.2 适合做什么
    • 2.3 明星作品
    • 2.4 缺点
  • 3 Golang语法新奇
    • 3.1 从main函数初见go的语法
    • 3.2 变量
      • 3.2.1 单变量声明
      • 3.2.2 多变量声明
    • 3.3 常量与iota
    • 3.4 函数
    • 3.5 init函数与导包
    • 3.6 指针
    • 3.7 defer关键字
    • 3.8 数组和动态数组slice
      • 3.8.1 slice声明方式
      • 3.8.2 slice使用方式
    • 3.9 map
    • 3.10 面向对象特征
      • 3.10.1 封装
      • 3.10.2 继承
      • 3.10.3 多态
      • 3.10.4 万能类型
    • 3.11 反射
    • 3.12 结构体标签Tag
      • 3.12.1 结构体标签的应用
  • 4 Golang高阶
    • 4.1 协程
      • 4.1.1 co-routine
      • 4.1.2 Golang的协程——Goroutine
    • 4.2 协程的通信——channel
      • 4.2.1 无缓存的channel
      • 4.2.2 有缓存的channel
      • 4.2.3 关闭channel
      • 4.2.4 channel和range
      • 4.2.5 channel和select
  • 5 Go modules 模块管理
    • 5.1 淘汰的Go path
    • 5.2 Go modules模式
      • 5.2.1 go mod命令
      • 5.2.2 go mod环境变量
    • 5.3 使用Go modules初始化项目
    • 5.4 修改项目模块的版本依赖关系
  • 6 Golang案例——即时通信系统
    • 6.1 版本一:构建基础Server
    • 6.2 用户上线功能
    • 6.3 用户消息广播机制
    • 6.4 用户业务层封装
    • 6.5 在线用户查询
    • 6.6 修改用户名
    • 6.7 超时强踢功能
    • 6.8 私聊功能
    • 6.9 客户端实现
  • 7 Golang生态拓展介绍

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

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都是比较快的。
Golang8小时基础入门_第1张图片

2.2 适合做什么

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

2.3 明星作品

Docker和Kubernetes
Golang8小时基础入门_第2张图片

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方法。
    对外开放的方法首字母大写。
    Golang8小时基础入门_第3张图片
  • 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
}

Golang8小时基础入门_第4张图片
经典例子:交换数据
如果这样交换,并不能交换成功,因为是值传递

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、切片容量的追加
长度小于等于容量
Golang8小时基础入门_第5张图片
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)

Golang8小时基础入门_第6张图片
扩容机制:根据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
    Golang8小时基础入门_第7张图片
    示例,赋值的时候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是

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。从图中可以看出主要需要优化的就是协程调度器。
Golang8小时基础入门_第8张图片

4.1.2 Golang的协程——Goroutine

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

  • 服用线程
  • 利用并行
  • 抢占
  • 全局G队列
    Golang8小时基础入门_第9张图片

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协程能顺利取到管道的值。
Golang8小时基础入门_第10张图片

4.2.1 无缓存的channel

  对于无缓存的channel,传递消息的一方如果提前到达了要传递channel的指令,但此时接收协程还没有执行到接收channel,那么发送方就需要一直等待,直到接收方来接收。
Golang8小时基础入门_第11张图片

4.2.2 有缓存的channel

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

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/[email protected]
//自动download
执行go run会自动下载

go.mod文件会多一行如下,如果指定版本,在go.mod中指定
在这里插入图片描述
再打开go.sum文件
Golang8小时基础入门_第13张图片
h1加哈希

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

使用命令:

go mod edit -replace=版本号1=版本号2
//例如
go mod edit -replace=[email protected]=[email protected]

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

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

  项目源码:
  目的是覆盖大部分go语法特性,特别是网络,系统基础结构如下,使用了读写分离模型,使用九个版本迭代:
Golang8小时基础入门_第14张图片

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,可以看到成功上线。
Golang8小时基础入门_第15张图片
Golang8小时基础入门_第16张图片

6.3 用户消息广播机制

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

6.4 用户业务层封装

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

6.5 在线用户查询

  当客户端输入who时,发送所有当前登录用户给客户端。
Golang8小时基础入门_第18张图片

6.6 修改用户名

  消息格式rename|张三,修改效果如下:
Golang8小时基础入门_第19张图片

6.7 超时强踢功能

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

6.8 私聊功能

  消息格式:to|张三|你好呀,我是...
Golang8小时基础入门_第21张图片
Golang8小时基础入门_第22张图片

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、菜单显示
Golang8小时基础入门_第23张图片
4、更新用户名
Golang8小时基础入门_第24张图片
Golang8小时基础入门_第25张图片
5、公聊模式
在这里插入图片描述
6、私聊模式
首先查询当前有哪些用户在线,提示选择一个用户
Golang8小时基础入门_第26张图片
Golang8小时基础入门_第27张图片

Golang8小时基础入门_第28张图片

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更好

你可能感兴趣的:(Golang,golang,开发语言,后端)