2020->C++程序员之Go语言学习之旅总结
温馨提示:Go语言开发必备的开发手册: https://studygolang.com/pkgdocGo语言进阶学习书籍->Go语言圣经: https://www.k8stech.net/gopl/Go语言是典型的面向对象编程语言,特性有继承(匿名字段、实名字段),封装(方法),多态(接口interface)。面向对象是一种编程思想,本身与编程语言没有关系,只是一些语言很好的支持了面...
温馨提示:
Go语言开发必备的开发手册: https://studygolang.com/pkgdoc
Go语言进阶学习书籍->Go语言圣经: https://www.k8stech.net/gopl/
Go语言是典型的面向对象编程语言,特性有继承(匿名字段、实名字段),封装(方法),多态(接口interface)。
面向对象是一种编程思想,本身与编程语言没有关系,只是一些语言很好的支持了面向对象,C语言、Go语言的结构体就是后来面向对象编程语言中的类,面向对象编程可实现高内聚,低耦合。
凌晨五点了,C++程序员在考虑内存泄漏的问题,Java程序员在考虑提高效率的问题,Python程序员在考虑怎么对齐代码,而Go程序员在考虑怎么样找error
C/C++程序员学习Go语言的知识要点88条:
1.函数名首字母大写会给他一个public属性,否则是private属性
2.不定参数只能放在参数中的最后一个参数
3.for循环不用加括号 for i:=0;i<len(xx);i++{}
4.goland软件常用快捷键
查看源码 CTRL+B (或者CTRL+鼠标左键)
从源码返回 CTRL+E+回车
自动排版快捷键CTRL+ALT+L
复制一行CTRL+D
5.defer延迟调用,多个defer连续写是栈性结构执行,中间defer报错不会影响后面defer执行。
6.切片slice类似动态数组,它通过内部指针和相关属性引用数组片段,以实现变长方案
s1:=[]int{1,2,3,4}
s2:=make([]int,5,10) //长度只有5想添加元素要用函数
7.自动推导变量类型不能用在全局,只支持局部变量操作。go支持多重赋值特性。
8.make只能创建slice、map和channel,并且返回一个有初始值(非零)的对象
9.切片截取操作
s[low:high:max] 从切片s的索引位置low到high处所获得的切片,len = high-low,cap = max-low
[low,high)为左闭右开区间,low和high就是具体的下标数字,截取从low下标到high-1下标的元素
10.修改截取来的切片会使原切片做相应改变,相当于引用截取。
11.切片只可以尾部追加数据,可以自动为切片扩容,使用append()函数添加数据。
append函数底层智能容量增长的,通常2倍(1024以下)容量重新分配底层数组,并复制原来的数据。故此,做扩容时地址可能发生变化,但不影响使用,超过1024通常时四分之一倍扩容。可参考C语言中realloc()学习。
12.切片作为函数参数时,是值传递,本质如下。
切片不是指向底层数组的指针,而是重新定义的一种新数据结构。结构中包含指向底层数组的指针。
type slice struct{ //go语言源码包runtime/slice.go 可以查看源码
array unsafe.Pointer -->指向底层数组
len int
cap int
}
13.map(映射、字典)是一种内置的数据结构,无序的key-value对的集合,go语言没有提供set类型,但是map中的key也是不相同的,可以用map实现类似set的功能。格式:map[keyType]valueType,注意:map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺利有可能不同。
14.只声明一个map,没有初始化,map为空(nil),不能直接添加元素,切片可以(使用append函数),故此map能用make创建空间m0:= make(map[int]string,长度) [长度可加可不加] 或者用m1:=map[int]string{}这种方式。对于map而言,可以使用len()函数,不可以使用cap()函数, 添加重复key值会后来者覆盖前者。
15.map遍历是无序的,所以只能用for+range的方式遍历,第一个返回值为key,第二个返回值为value。只返回一个值时返回的时key值,也可以用key值的到value值。
16.map中判断key是否存在,value,trueOrfalse :=m[1],成功返回value值和true,失败返回零值和false
17.if常用的语法。if value,trueOrfalse := m[1]; trueOrfalse{…} 可以在判断前先执行一句话。
18.delete(m,2) 删除map对象m中key为2的元素,操作时安全的,即使要删除元素不再map中也没关系。如果查找删除失败将返回value类型对应的零值。
19.map在做函数参数的时候时引用传递,map做函数返回值时返回的依然是引用
20.go程序员不用考虑内存泄漏和内存回收问题,直接用就完事了。因为go中有内存逃逸机制。
21.使用 strings.Fields() 函数可以将字符串中的单词按照空格拆分出来,返回一个切片保存所有单词。
22.go语言中保留了指针,但与C语言中的指针不同,比C语言中的指针差远了。
1.go指针的默认值都是nil,没有随机数,就没有野指针的概念。
2.操作符“&”是取变量地址,“*”是通过指针访问目标对象。
3.不支持指针数学运算,不支持"->"运算符,直接用"."访问成员。
22.go中make和new函数的区别,make只能创建slice、map和channel,但是new可创建很多(不常用new)。
1.new(T)创建一个T类型的匿名变量,分配一块内存空间,并清零,返回这块空间的地址。
2.new出来的对象可能在堆上也可能在栈上,根据实际情况内部实现决定的。
3.只需要使用就完事了,不用担心内存的生命周期或怎么删除,go语言的内存管理系统会帮我们解决。
23.垃圾回收机制正确理解
Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。
24.与C不同,Go语言中没有typedef,而定义结构体定义时,不能缺少type关键字。
25.定义结构体时,可以通过这些默认标签来设定结构体成员变量,使之在序列化之后得到特殊的输出
26.结构体初始化有两种方式,一个是顺序初始化(全部初始化),还有一个指定成员初始化(部分初始化)
27.如果结构体的全部成员都是可以比较的(基础类型即可,不能使引用类型成员),那么结构体也是可以比较的,那样的话两个结构体将可以使用==或!=运算符进行比较,但不支持>或<运算符。
28.字符串处理常用部分函数(用到的使strings包中的函数,用的时候翻文档即可)
1.func Contains(s, subStr string) bool
功能:判断字符串s是否包含子串substr
2.func Join(a []string, sep string) string
功能:将一系列字符串连接为一个字符串,之间用sep来分隔。
3.func Trim(s string, cutset string) string
功能:将s前后端所有cutset字符串去除。返回处理后的新字符串
4.func Replace(s, old, new string, n int) string
功能:将s中的old子串替换为new,替换n处,n<0替换全部。返回替换后的新字符串
5.func Split(s, sep string) []string
功能:把s字符串按照sep分割,返回slice
6.func Fields(s string) []string
功能:去除s字符串的空格符,并且按照空格分割,返回slice
7.func HasSuffix(s, suffix string) bool
功能:判断s字符串是否有后缀子串suffix
8.func HasPrefix(s, prefix string) bool
功能:判断s字符串是否有前缀子串suffix
29.文件操作常用API(用到os包里的函数)
1.创建、打开文件
func Create(name string) (file *File, err Error) //创建文件对象可读写
func Open(name string) (file *File, err Error) //只读打开
func OpenFile(name string, flag int, perm uint32) (file *File, err Error) //万能操作
2.关闭文件函数
func (f *File) Close() error
3.写文件
func (file *File) WriteString(s string) (ret int, err Error)
func (file *File) Write(b []byte) (n int, err Error)
4.读文件(用到bufio包函数)
func NewReader(rd io.Reader) *Reader
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
func (file *File) Read(b []byte) (n int, err Error)
5.删除文件
func Remove(name string) Error
30.Go语言中没有类(class)的概念,可以将结构体比作类,结构体可以添加属性(成员)和方法(函数)
31.结构体绑定方法时,在func和函数名之间将此函数要绑定的结构体写在括号里即可。类似func (s xxx) yyy(){…},绑定括号内叫方法接收者或绑定对象。
32.Go语言中的继承就是结构体嵌套结构体对象即可。分两种继承方式:匿名字段和实名字段
33.下方结构体统称为类,子类继承父类,子类中存在与父类同名字段时,按照就近原则来执行
34.子类继承匿名父类指针,子类初始化时要给匿名父类指针分配空间,不然给子类初始化时会报错。(不能解引用指针为nil)
35.多重继承分两类:一类C->B->A (C继承B,B继承A) ,另一类为C->B,C->A (C分别继承A和B),注意:在程序中尽量减少多重继承,否则会增加程序的复杂度,耦合度增高,如果有同名字段处理难度加大。
36.Go语言中没有方法重载,只有方法重写。
37.可以使用type给类型起别名,type XXX int (表示给int起了个别名叫XXX,此时XXX成了一个类)
38.绑定对象如果为指针,就是传引用,可读/可写。绑定对象如果是普通对象,就是传值,只可读。
39.方法的继承就是首先要子类继承父类,然后在绑定子类的方法里使用继承的父类的方法即可。
40.方法的重写,绑定子类的方法名可以与绑定父类的方法名相同,子类重写父类方法后,按照就近原则,子类对象就可以调用子类重写父类方法后的方法了。
41.Go语言的接口(规则和标准),单纯用来指定方法原型,不参与具体实现。对应C++中的抽象类和虚函数。type xxx interface{…} ,接口变量默认是个nil类型,适配其他类型。
1.定义接口(建议er结尾)
2.按接口定义语法,实现方法(创建类,绑定方法)
3.创建接口变量
4.使用类对象给接口变量赋值
5.使用接口变量调用方法。
42.Go语言实现多态,方法就是封装一个普通函数,将接口设计为函数的参数。函数内使用参数(接口),调用类方法。按接口实现多态,能适应将来新的类对象、方法调用。
1.定义接口
2.创建类,绑定方法,实现接口
3.创建函数,指定接口类型为参数
4.函数内,使用接口调用方法
43.Go语言中switch语句的case分支中不需要写break(和C/C++不同),不会发生case穿透情况。
44.空接口var xx interface{} 默认值nil,默认类型nil,作用是接收任意类型的数据。特性是接收何种数据,就变为何种类型。只能存储,不能用来运算,如果需要运算,需要类型断言。
45.注意类型断言只能应用于空接口类型。value,ok:=变量名.(类型) ->判断变量是否为()内的类型
46.slice := []interface{}{7,3.14,“hello”,‘m’,[]int{1,3,2}},此接口切片可存任何类型,在switch取出interface变量的类型时可以switch v.(type){…}(v就是遍历时那个元素)使用此语法方便。
47.error异常处理的时候,内建error接口类型时约定用于表示错误信息,nil值表示无错误。可运用errors包下的func New(text string) error函数。
48.panic异常和error异常都是运行时异常,但panic比error更为严重,是不可逆的。可能自动产生(由系统判断产生),也可能手动产生(立即终止程序)。解决的方式需要使用defer关键字、recover错误拦截,但这只是缓兵之计,错误时解决不掉的,还是代码出问题了,需要后期修改代码,保证暂时正常运行。
手动产生panic时可调用:func panic(v interface{})
defer用来对指令(函数、表达式)进行延迟调用。延迟到当前指令对应的函数运行结束,栈帧释放前。
要拦截panic异常,recover无法单独工作,必须依赖defer
固定用法:
//函数其实位置,注册recover拦截监听。在函数调用结束时,返回监听结果。
defer func(){
fmt.Println(recover())
}()
panic异常。
49.goroutine时Go语言并行设计的核心,有人称之为go程。从量级上看很想协程,它比线程更小。比thread更易用、更高效、更轻便。一般情况下,一个普通计算机跑几十个线程就有点负载过大了,但是同样的机器却可以轻松的让成百上千goroutine进行资源竞争。只需要在函数调用语句前添加go关键字,就可以创建并发执行单元。
50.注意主goroutine退出后,其他所有的工作goroutine也会自动退出,要借助其他方式来让其他工作goroutine结束后才让主goroutine退出。
51.func Goexit()此函数(runtime包下的函数)会终止调用它的go程,其他go程不会受影响,并且回在终止该go程前执行所有defer的函数。但是注意main go程不能调用本函数,会使得程序崩溃的。(显示错误:fatal error: no goroutines (main called runtime.Goexit) - deadlock!)
52.os包下的Exit函数可以终止调用该函数进程。os.Exit(0),会返回code为0,可自定义返回数字。
53.注意对比return(函数退出)、Goexit() (go程退出的)、os.Exit(0) (进程退出的)的区别与使用。
54.channel时Go语言中的一个核心类型,可以把它看成管道,注意在Go中它时一种数据类型。主要用channel来解决go程的同步问题以及go程之间数据共享(数据传递)的问题。goroutine运行在相同的地址空间,因此访问共享内存必须做好同步,goroutine奉行通过通信来共享内存,而不是共享内存来通信。
54.Go语言主流实现同步的方式就是goroutine+channel,当然Go也支持一些锁的应用(非主流)。
55.channel通过操作符<- 来接收和发送数据,默认情况下channel接收和发送数据都是阻塞的,除非另一端已经准备好了,这样就使得goroutine同步变得更加简单,而不需要显式的lock
56.当你用channel协调了主/子go程通信,如果主/子go程中都使用了屏幕打印(或者其他共享资源),就需要在创建一个channel来协调主子go程使用stdout,不然主子go程会争抢系统屏幕打印资源,使其看起来不同步。意思就是说多个go程间,如果有多份共享资源时,需要分别使用channel同步。
57.channel的特性
1.通道中的数据只能单向流动。一端读端、另外必须写端
2.通道中的数据只能以此读取,不能重复读。先进先出
3.读端和写端在不同的goroutine之间
4.读端读,写端不在线,读端阻塞。写端写,读端不再先,写端阻塞。
58.通过无缓冲channel和有缓冲channel可以实现goroutine之间的同步通信(相当于打电话)和异步通信(相当于发短信)。有缓冲channel的定义时给出容量,ch:=make(chan 类型,容量)
59.关闭channel。接收者不知道发送者有多少数据发送,可以通过内置的close函数来关闭channel以此来让接收者停止不必要的接收等待。写端在写完后close(ch),关闭channel即可,读端可通过 if data,ok:= <-ch; ok{…}中的ok来判断写端channel是否关闭,ok=false说明channel已经关闭。写端关闭,读端可以接着读,但读到的都是零值,没有意义。写端关闭后再写会报异常,也没有意义。注意close(ch)操作只出现再写端。
60.(常用)读channel的简便写法使用for+range(可以判断关闭也能正常读取)。for num:=range ch{…}即可,注意ch前不能加<-箭头
61.借助channel可以防止主go程提前结束导致其他运行中的工作go程跟着退出。
62.之前默认的channel都是双向channel,就是两端都可以从channel读,向channel写。接下来说的时单向channel,两端只能一端读,一端写。注意双向channel是可以给单向channel进行初始化的,反之不行。
xxx := make(chan int)
1.var chw chan<- int = xxx //定义写channel
2.var chr <-chan int = xxx //定义读channel
63.在函数的参数用单向channel,在语法上有个限定,不至于读写channel的混乱。
64.常用引用类型数据类型有,map、指针、interface、channel【注意slice是值传递,但内部结构体有引用语义】
65.生产者消费者模型(使用带缓冲的channel即可),生产者->缓冲区->消费者
缓冲区的好处:1.解耦 2.处理并发 3.缓存
66.定时器(time包中)也用到了channel
type Timer struct{ //time包下的结构体
C <-chan Time
//内含隐藏或非导出字段
}
eg:(单次定时三秒输出当前时间)
普通版:
timer:=time.NewTimer(time.Second*3)
fmt.Println(time.Now())
t:=<-timer.C
fmt.Println(t)
简便版:
fmt.Println(time.Now())
t:= <-time.After(time.Second*3)
fmt.Println(t)
67.总结定时器常用操作
1.<-time.After(time.Second*3) 【主要掌握这种】
2.time.Sleep(time.Second*3)
3.time.NewTimer(time.Second*3)返回一个timer后 <-timer.C取出数据
68.主/子go程交替数数测试
var glb = 1
func subGo(ch_data chan<- int,ch_stdout <-chan int){
for{
fmt.Println("子:",glb)
ch_data <- glb+1
<-ch_stdout
}
}
func main() {
ch_data := make(chan int)
ch_stdout := make(chan int)
go subGo(ch_data,ch_stdout)
for num := range ch_data{
fmt.Println("主:",num)
glb = num+1
ch_stdout <- 1
}
}
69.可以说goroutine+channel+select是并发铁三角,Go里面提供一个select关键字,可以监听channel上的数据流动。select的用法和switch非常相似,开始一个新的选择块,每个选择条件由case语句来描述。select由比较多的限制,最大的一条限制是每个case语句里必须是一个IO操作。如果阻塞,只会阻塞select的分支而已,就像每个case又开了一个go程(好理解的假设)。通常select都是循环的,为了避免不必要的忙轮询,可以适当选择去掉default分支,等待分支的可用即可。
70.借助select和time.After(…)实现超时处理,注意只要其他分支有数据流动,定时器就会重置问题。
步骤:
-
创建 select , 启动监听 channel
for { select { case num := <-ch: fmt.Println("num =", num) case <-time.After(time.Second * 3): //1.创建 Timer, 2.读 C 成员 fmt.Println("子go程读到系统时间, 定时满 3 秒") quit <- false } }
-
监听超时定时器:
case <-time.After(time.Second * 3)
-
当select监听的其他case分支满足时,time.After所在的case分支,会被重置成初始定时时长。
-
直到在select 监听期间,没有任何case满足监听条件。time.After 才能定时满。
71.Go使用channel解决go程同步(常用),同时也提供了传统的同步工具。在Go的标准库代码包sync和sync/atomic中。有读写锁,互斥锁,条件变量这些。
72.死锁不是一种锁,而是错误使用锁的现象
1. 单个go程使用同一个channel自己读、自己写。
2. 多个go程使用 channel通信,go程创建之前,对channel读、写造成死锁。
3. 多个go程使用多个channel 通信,相互依赖造成死锁。
4. 多个go程使用 锁(读写锁、互斥锁)和 channel 通信。
73.互斥锁(互斥量)mutex,建议锁,不具备强制性。
作用:保护公共区,防止多个控制流共同访问共享资源时造成数据混乱。
74.读写锁,读时共享,写时独占,写锁优先级高于读锁,只有一把锁,具备两种属性(r/w)。建议锁,不具备强制性。
使用注意事项:
1. 访问公共区之前,加锁。访问结束立即解锁。
2. 粒度越小越好
使用方法:
1. 创建读写锁 var rwmutex sync.RWMutex
2. 加读锁 rwmutex.RLock() 加写锁 rwmutex.Lock()
...... 访问公共区
3. 解读锁 rwmutex.RUnlock() 解写锁 rwmutex.Unlock()
- 建议:不要将锁 和 channel 混合 使用。 —— 条件变量。
75.播种随机数种子rand.Seed(time.Now().UnixNano())
num :=rand.Intn(10) //生成一个0-9之间的随机数。
76.条件变量的使用(用到sync包),条件变量实现生产者消费者模型
1.生产者有个条件变量判断公共区是否为满,未满才能获取锁之后向公共区添加数据
如果公共区满了(len(ch) == cap(ch))那么就等待cond.Wait(),等待消费者signal()唤醒阻塞在条件变量上的对端(生产者) [注意等待需要放在循环中]
2.消费者有个条件变量判断公共区是否为空,未空才能获取锁之后从公共区获取数据
如果公共区为空(len(ch) == 0)那么就等待cond.Wait(),等待生产者signal()唤醒阻塞在条件变量上的对端(消费者) [注意等待需要放在循环中]
func (c *Cond) Wait()这个函数的作用:
1. 阻塞等待条件变量满足
2. 释放已经掌握的互斥锁。(解锁)。要求,在调用wait之前,先加锁。c.L.Lock()
。。。。 阻塞等待条件变量满足
3. 解除阻塞,重新加锁。
func (c *Cond) Signal()这个函数的作用:
1.唤醒阻塞在条件变量上的 go程。
func (c *Cond) Broadcast()这个函数的作用:
1.唤醒阻塞在条件变量上的 所有 go程。
条件变量实现生产者具体步骤,消费者同理可得:
1. 创建条件变量 var cond sync.Cond
2. 初始化条件变量的成员 L ,指定锁(互斥、读写) cond.L = new(sync.Mutex)
3. 加锁 cond.L.Lock()
4. 判断条件变量满足, 调用 wait
// 此处必须使用 for 循环判断 wait是否满足条件。for循环会二次判断条件,确保正确性,因为多go程同时生产的时候,用if判断条件(一次判断)可能会导致错误生产。如果用if,刚提示可以公共区未满,可以生产了,但其他go程提前生产,公共区变成了满状态,这就导致生产错误。
for len(ch) == cap(ch) {
cond.Wait() // 1 2 3
}
5. 产生数据,写入公共区
6. 唤醒阻塞在条件变量上的 对端。cond.Signal() //官方文档建议调用signal时保持锁状态,解锁放后面
7. 解锁 cond.L.UnLock()
77.(重要)sync包中waitGroup
主go程等待子go程结束运行。这个是正规方法,要记住!
注意waitGroup创建的对象,如果在实名go程传参的话,必须传&,匿名go程直接使用对象即可。
实现具体步骤:
1. 创建 waitGroup对象。 var wg sync.WaitGroup
2. 添加 主go程等待的子go程个数。 wg.Add(数量)
3. 在各个子go程结束时,调用 wg.Done()。 相当于将主go等待的数量-1。 defer wg.Done
如果是实名子go程的话, 传地址。
4. 在主go程中等待。 wg.wait()
78.Tcp-C/S-Server
实现流程:
1. 创建监听器 listener
2. 启动监听,接收客户端连接请求 listener.Accept() --- conn
for {
3. conn.read 客户端发送的数据
4. 使用、处理数据
5. 回发 数据给客户端 write
6. 关闭连接 Close
}
7. 客户端可以使用 nc (netcat)测试:
语法:nc 服务器IP地址 服务器port
使用方法:
1.Listen函数 —— 创建监听器
func Listen(net, laddr string) (Listener, error)
-
参数1:协议类型:tcp、udp (必须小写)
-
参数2:服务器端的 IP: port (192.168.11.01:8888)
-
返回值:成功创建的监听器
type Listener interface { // Addr返回该接口的网络地址 Addr() Addr // Accept等待并返回下一个连接到该接口的连接 Accept() (c Conn, err error) // Close关闭该接口,并使任何阻塞的Accept操作都会不再阻塞并返回错误。 Close() error }
2.Accept 方法 —— 阻塞等待客户端连接
func (listener *Listener) Accept() (c Conn, err error)
-
返回值:成功与客户端建立的 连接conn(socket)
type Conn interface { // Read从连接中读取数据 // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Read(b []byte) (n int, err error) // Write从连接中写入数据 // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Write(b []byte) (n int, err error) // Close方法关闭该连接 // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误 Close() error // 返回本地网络地址 LocalAddr() Addr // 返回远端网络地址 RemoteAddr() Addr 。。。 }
79.Tcp-C/S-Client
实现流程:
1. 创建与服务器的连接 net.Dial() -- conn(socket)
for {
2. 发送数据给服务器 conn.write
3. 接收服务器回发的 数据 conn.read
4. 关闭连接。
}
使用的方法:
1.Dial方法
func Dial(network, address string) (Conn, error)
-
参数1:使用的协议:udp、tcp
-
参数2:服务器的 IP:port (127.0.0.1:8888)
-
返回:conn 用于与服务器进行数据通信的 socket
type Conn interface { // Read从连接中读取数据 // Read方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Read(b []byte) (n int, err error) // Write从连接中写入数据 // Write方法可能会在超过某个固定时间限制后超时返回错误,该错误的Timeout()方法返回真 Write(b []byte) (n int, err error) // Close方法关闭该连接 // 并会导致任何阻塞中的Read或Write方法不再阻塞并返回错误 Close() error // 返回本地网络地址 LocalAddr() Addr // 返回远端网络地址 RemoteAddr() Addr }
80.Tcp-并发Server
服务器包含: 主go程、子go程们。
- 主go程:负责监听 客户端的 “连接” 事件。当有客户端连接上来 ,创建 子go程 与客户端进行通信
- 子go程:负责与客户端进行实时数据交互。并发。
实现流程:
1. 主go程中,创建监听器 Listener
2. 使用 for 监听 listener —— Accept() —— conn
3. 当有客户端发送连接请求时,创建 子go程。 使用 conn 与客户端进行数据通信
4. 封装、实现函数:完成单个子go程与客户端的 read、write —— handleConnet(conn)
关闭当前go程 conn —— defer
for {
1. 读取客户端的数据 conn.read()
2. 获取客户端的 IP+port 。 conn.RemoteAddr().string()[获取对端地址]
3. 处理数据
4. 写回数据到客户端。
}
81.网络通信中判断关闭可以使用稳妥做法
网络通信中,通过 read 返回值 为 0 判断对端关闭。
n, err := conn.read(buf)
if n == 0 {
// 检查到 conn 对端关闭。
}
if err != nil && err != io.EOF {
// 处理错误。
}
82.UDP-C/S-Server
实现流程:
1.获取UDP地址结构 —— UDPAddr
func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
-
参数1:“udp”
-
参数2:“127.0.0.1:8888” IP+port
-
返回值:UDP地址结构
type UDPAddr struct { IP IP Port int Zone string // IPv6范围寻址域 }
2.绑定地址结构体,得到通信套接字 —— udpConn
func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
- 参数1:“udp”
- 参数2:ResolveUDPAddr 的返回值 : UDPAddr
- 返回值:用于通信的 socket
3.接收客户端发送数据 —— ReadFromUDP() —— n,cltAddr, err
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error)
- 参数1:缓冲区
- 返回值:
- n:读到的实际字节数
- addr:对端的 地址结构(IP+Port)
4.写数据给客户端 —— WriteToUDP(数据,cltAddr) —— n, err
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)
- 参数1:实际写出的数据
- 参数2:对端的地址
- 返回值:实际写出的字节数。
83.UDP-C/S-Client
实现流程:
-
创建用于与服务器通信的 套接字 conn
net.Dial("udp", 服务器的IP+prot) --- conn
-
后续代码实现,参照 TCP-CS-Client。
84.UDP-C/S-并发Server
UDP服务器,默认支持客户端并发访问。
85.获取文件属性函数:stat
func Stat(name string) (FileInfo, error)
type FileInfo interface {
Name() string Size() int64
Mode() FileMode
ModTime() time.Time
IsDir() bool
Sys() interface{}
}
86.命令行参数
-
在main函数启动时,向整个程序传参。
-
语法: go run xxx.go argv1 argv2 argv3 argv4 。。。。
xxx.exe: 第 0 个参数。 argv1 :第 1 个参数。 argv2 :第 2个参数。 argv3 :第 3 个参数。 argv4 :第 4 个参数
-
使用: list := os.Args —— for index, str := range list { fmt.println() 可以遍历 list }
87.回调函数
- 实现本质:函数指针
- 函数指针语法:type 函数指针类型名 指向的函数原型(func 形参列表 返回值列表)。
- type FuncP func(x string, y bool)int
- 回调函数概念:
- 用户自定义一个函数,不直接调用,当满足某一特定条件时,有函数指针完成调用,或者由系统自动调用。
88.Go语言实现的HTTP服务器
1.注册 回调函数:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
-
pattern:服务器提供给客户端访问 url(路径、文件)
-
handler 回调函数。—— 系统自动调用
-
func handler(w http.ResponseWriter, req *http.Request)
-
w: 回写给客户端的 数据(http协议)
-
req:读取到的客户端请求 信息
type ResponseWriter interface { Header() Header Write([]byte) (int, error) WriteHeader(int) }
type Request struct { Method string // 浏览器请求方法 GET、POST… URL *url.URL // 浏览器请求的访问路径 …… Header Header // 浏览器请求头部 Body io.ReadCloser // 浏览器请求包体 RemoteAddr string // 浏览器地址 …… ctx context.Context }
-
2.启动服务
func ListenAndServe(addr string, handler Handler) error
- addr: 服务器地址结构(IP:port)
- handler :回调函数。通常传 nil —— 自动调用默认回调函数。DefaultServeMux
END!
GOOD LUCK!
更多推荐
所有评论(0)