Goloang的优缺点
Goloang的优点
极其简单的部署方式
- 可直接编译成机器码
- 不依赖其他库
- 直接运行即可部署
语言层面的并发
- 天生的基因支持
- 充分的利用多核
强大的标准库
- runtime 系统调度机制
- 高效的GC垃圾回收
- 丰富的标准库
Goloang的不足
- 包管理,大部分包都在github上
无泛化类型(Golang 1.18+已经支持泛型)- 所有Excepiton都用Error来处理(比较有争议)。
- 对C的降级处理,并非无缝,没有C降级到asm那么完美(序列化问题)
关于编译运行
package main//包含 main 函数的一定要包这个
// import "fmt"
// import "time"
import(
"fmt"
"time"
)
func main(){//函数的 { 一定是和函数名在同一行的这是语法规定,不然有编译错误
fmt.Println("hello Go!")//可;可不;
fmt.Print("hello Go!\n")
time.Sleep(1*time.Second);
}
01. 变量
//变量
func main(){
//1.默认
var a int
var aa,bb int = 100,"fasgds"
fmt.Println("a=",a)//a=0
//2.初始化
var b int=100
fmt.Printf("b=%d,type of b=%T\n",b,b);
//3.初始化的时候自动推导
var c = 12.6
fmt.Printf("c=%f,type f c=%T\n",c,c);
//4.(常用)省区var 直接自动匹配 —— 但是只能用在函数体内,全局不可以
d:=1000
fmt.Println("d=",d)
fmt.Printf("type of d=%T\n",d);
time.Sleep(1*time.Second);
}
0.2 常量
但是iota只能在const()里面使用,进行累加效果
go可以多参数返回(返回值可以形参的形式或者直接return多个值)
0.3 defer
defer——defer就相当于析构函数,并且执行在return之后
04. 数组
go的数组,但是定长数组传参很麻烦,只能是形参声明的特定的那一个长度,而且是值拷贝
05. 动态数组slice
go的动态数组 slice(切片)
slice的声明方式
slice使用方式
1. 切片容量的增加(扩容 5->10->20)
2. 切片容量的截取
06. map
go的map声明方式
go的map使用方式(传参的时候和slice一样是引用传参)
07. struct
struct的基本定义与使用
和typedef相似
08. go中类的表示与封装
但是大小写,对本包没有区别
//封装
type Hero struct{
nAme string
Sex string
Lv int
}
func (this* Hero) GetName()string{
fmt.Println("Name=",this.nAme);
return this.nAme
}
func (this* Hero)SetName(name string){
this.nAme=name
}
func main(){
hero := Hero{nAme:"zhangsan",Sex:"nan",Lv:7}
hero.GetName()
hero.SetName("lisi")
fmt.Println(hero)
}
09. go的继承
//继承
type Animal struct{
sex string
age int
}
type Cat struct{
Animal //-------------直接把要继承的类名写下来就可以了
color string//猫的颜色
}
func (this* Cat) SetCat(sex string,age int,color string) {
this.sex=sex
this.age=age
this.color=color
}
func (this* Cat) ShowCat(){
fmt.Println("cat sex:",this.sex," cat age",this.age," cat color",this.color)
}
func (this* Cat) GetCatColor() string {
//fmt.Println(this.color)
return this.color
}
func main(){
var cat Cat
cat.color="blue"
cat.SetCat("nv",3,"yel")
cat.ShowCat()
// var ret = cat.GetCatColor()
// fmt.Println(ret)
// fmt.Println("--------")
}
10. go的多态
go的多态就是需要先有一个 interface 的接口,这里面就放需要多态的方法,子类去重写的时候不需要像cpp一样继承,直接重写就相当于继承了,但是注意一定要把接口全部重写才可以。
//多态
type AnimalIF interface{
Sleep()
GetColor() string//获取动物颜色
GetType() string//获取动物类型
}
type Cat struct{
color string//猫的颜色
}
func (this* Cat)Sleep(){
fmt.Println("cat is sleep")
}
func (this* Cat)GetColor()string{
return this.color
}
func (this* Cat)GetType()string{
return "Cat"
}
//-----------
type Dog struct{
color string//狗的颜色
}
func (this* Dog)Sleep(){
fmt.Println("dog is sleep")
}
func (this* Dog)GetColor()string{
return this.color
}
func (this* Dog)GetType()string{
return "Dog"
}
//法二:
func ShowAnimal(animal AnimalIF/*接口的数据类型,父类指针*/){
animal.Sleep()
fmt.Println("animal color:",animal.GetColor())
fmt.Println("animal type",animal.GetType())
}
func main(){
//法一:
// var animal AnimalIF//接口的数据类型,父类指针
// animal = &Cat{"blue"} //注意&
// animal.Sleep()
// animal = &Dog{"yel"}
// animal.Sleep()
//法二: 运用统一的方法去调用
cat := Cat{"blue"}
dog := Dog{"yel"}
ShowAnimal(&cat)
ShowAnimal(&dog)
}
11. interface
interface空接口万能类型与类型断言机制(三种作用:定义一般的普通接口(多态),空接口(万能接口),进行断言(判断类型,与万能接口配套用))
断言:断言有两步,一:得到动态类型type,二:判断type是否实现了目标接口。
arg.(string):判断此时arg指向的变量是否是string类型的(称为类型断言)
变量的内置pair结构详细说明
就是任何一个变量,里面都是有一个pair,称 type 和 value,type里面有两种,只能选其一,一个是属于 static type(像int,string),另一个是 concrete type(具体类型,就是interface所指向的具体类型,这种类型是系统看得见的类型)
关于pair再一个例子:就是打开了一个文件描述符,然后赋值给io里面的读变量和写变量,赋值的过程中,pair里面所存储的数据是不会变的(文件描述符指针类型,和指向的文件描述符),然后是把打开的tty(终端),赋值给了r(读方法),再将 r 赋值(w=r.(io.Writer)是r先强制转换为Writer,然后赋值给我)给w,然后用w去写数据到终端
12. go反射reflect机制用法
反射的两个重要接口,反射就是为了获取当前变量的type和value
基本类型反射
复杂类型反射
package main
import (
"fmt"
"reflect"
)
type User struct{
Id int
Name string
Age int
}
func (this User)Call(){
fmt.Println("user is called..")
fmt.Printf("%v\n",this)
}
func main(){
user := User{1,"ace",18}
DoFiledAndMethod(user)
}
func DoFiledAndMethod(input interface{}){
//获取input的type
inputType := reflect.TypeOf(input)
fmt.Println("input TypeOf:",inputType.Name())
//获取input的value
inputValue := reflect.ValueOf(input)
fmt.Println("input ValueOf:",inputValue)
//通过type获取里面的字段
//1. 通过interface的reflect.Type,通过type得到NumFiled,进行遍历
//2. 得到每个filed数据类型
//3. 通过filed有一个Interface()方法得到对应的value
for i:=0; i<inputType.NumField();i++{
field := inputType.Field(i);
value := inputValue.Field(i).Interface()
fmt.Println("----------")
fmt.Printf("%s: %v = %v\n", field.Name,field.Type,value)
}
//通过type 获取里面的方法,调用
for i:=0; i<inputType.NumMethod();i++{
fmt.Println("****1***")
m:=inputType.Method(i)
fmt.Println("****2***")
fmt.Printf("%s:%v\n",m.Name,m.Type)
}
}
13. go反射解析结构体标签Tag
这个玩意就是相当于注释或者mysql的comment
关于标签的使用:是键盘1左边的按键``,然后里面是
key value(例子: `key1:"value1"` `key2:"value2"`)
(取标签的时候key肯定是已知的,通过协议定制的)
结构体标签在json中的运用
发现json解析出来的结果就是我们刚才标签里面设置的内容
13. goroutine
创建goroutine(直接 go 一个方法就可以了,和进程线程一样,当main goroutine结束的时候,子goroutine也结束)
在一个 go routine 里面提前退出
14. channel(专门用来提供goroutine直接相互通信的)(类似之前的IPC通信)
1. channel的定义
2. channal有缓冲与无缓冲同步问题
(make(chan int,3)这里的3如果没有就是没有缓冲)
对于下面的打印顺序我们是无法保证的,但是“子go程结束”一定是“num=0”之后
3. channal的关闭特点
这里没有对channal进行关闭,导致goroutine执行完之后报错了
4. channal和range
这个代码就是,range尝试去c中读数据,c中如果有数据,就等待c的结果,有了就给data,然后进行一次for循环,等待 goroutine close 之后,和退出for循环
5. channal与select(相当于多路转接)
几个case,就是监听几个channal
15. Go Modules模式
GOPATH工作模式有一些弊端,下载使用Go Modules模式
Go Modules模式基础环境说明(go 1.11以上才可以用)
go mod 命令
go env -w GO111MODULE=on(设置之后go mod 模式就会生效)
(之前导包需要手动下载,现在有go mod之后只需要在代码中写上import的github路径,就会自动在github上面帮我们下载这个包,GPPOXY就代表我们从哪个第三方托管平台去下载)
go env -w GOPROXY=https://goproxy.cn,direct(博主设置的七牛云的/export GOSUMDB=sum.golang.org)(多了个direct是指:当当前配置的地方拉取不到需要的包,就会指示go重定向到源地址去抓取)
GoModules 初始化项目
(如果是用原来的方法导入,就需要cd $GOPATH/src ,到指定位置 go get .../name)
首先在需要go mod 的模块进行初始化
go mod init github.com/sder/moudles_test(后面跟的这个名称就是后面其他人使用的时候 import导入的名称)(生成了go.mod就说明初始化成功了,然后就可以正常写代码了)
package main
import (
"fmt"
"github.com/aceld/zinx/ziface"
"github.com/aceld/zinx/znet"
)
// PingRouter MsgId=1
type PingRouter struct {
znet.BaseRouter
}
//Ping Handle MsgId=1
func (r *PingRouter) Handle(request ziface.IRequest) {
//read client data
fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
}
func main() {
//1 Create a server service
s := znet.NewServer()
//2 configure routing
s.AddRouter(1, &PingRouter{})
//3 start service
s.Serve()
}
就像博主这样,导入了两个包,里面有很多这两个包的方法,直接运行是不可以的,然后我们现在只需要:
把需要用到的,也就是代码里面 import 的
go get github.com/aceld/zinx/ziface
go get github.com/aceld/zinx/znet
导入之后我们的gomod就多了我们需要的包以及版本信息(indirect表示间接依赖,就是说只用了需要包的一部分,并不是需要用到整个包),同时还多了一个go.sum文件
对于go.sum里面,h1:加上hash值代表hash了整个包,验证整个包的完整性
go.mod加上hash,表示的是这个mod文件的hash值
而我们现在的库是给我们下载到了 $GOPATH/pkg/mod
这个文件夹里面就有当前的模块名
现在这种方式就可以在任何地方不用一定把当前项目放在 $GOPATH/src 路径中,现在只需要把go mod 加上 ,指定使用哪个包,然后 go get 包就可以了
修改项目mudule指定版本的依赖关系
16. Go项目——及时通信系统
main.go
package main
func main(){
server := NewServer("127.0.0.1",8080)
server.Start()
}
user.go
package main
import (
"net"
"strings"
)
type User struct{
Name string
Addr string
C chan string //用来接收广播消息
conn net.Conn //socket通信的连接
server *Server//当前用户所属的server
}
//创建一个用户的API
func NewUser(conn net.Conn,server *Server) *User{
userAddr := conn.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: conn,
server: server,
}
//启动当前user channal消息的goroutine
go user.ListenMessage()
return user
}
//用户上线的业务
func (this *User) Online(){
//用户上线,将用户加入到onlineMap中
this.server.mapLock.Lock()
this.server.OnLineMap[this.Name] = this
this.server.mapLock.Unlock()
//广播用户上线消息
this.server.BroadCast(this, "已上线")
}
//用户下线的业务
func (this *User) Offline(){
//用户下线,将用户从onlineMap中删除
this.server.mapLock.Lock()
delete(this.server.OnLineMap, this.Name)
this.server.mapLock.Unlock()
//广播用户下线消息
this.server.BroadCast(this, "已下线")
}
//给当前User对应的客户端发送消息
func (this *User) SendMsg(msg string){
this.conn.Write([]byte(msg))
}
//用户发送消息的业务
func (this *User) DoMessage(msg string){
if msg == "who"{
//查询当前在线用户
this.server.mapLock.Lock()
for _, user := range this.server.OnLineMap{
onlineMsg := "[" + user.Addr + "]" + user.Name + ": 在线...\n"
this.SendMsg(onlineMsg)
}
this.server.mapLock.Unlock()
}else if len(msg) > 7 && msg[:7] == "rename|"{//rename|张三
//消息格式:rename|张三
newName := strings.Split(msg, "|")[1]
//判断name是否存在
_, ok := this.server.OnLineMap[newName]
if ok{
this.SendMsg("当前用户名被使用\n")
}else{
this.server.mapLock.Lock()
delete(this.server.OnLineMap, this.Name)
this.server.OnLineMap[newName] = this
this.server.mapLock.Unlock()
this.Name = newName
this.SendMsg("您已经更新用户名:" + this.Name + "\n")
}
}else if len(msg) > 4 && msg[:3] == "to|"{
//消息格式:to|张三|消息内容
remoteName := strings.Split(msg, "|")[1]
if remoteName == ""{
this.SendMsg("消息格式不正确,请使用\"to|张三|消息内容\"格式\n")
return
}
//根据用户名得到对方User对象
remoteUser, ok := this.server.OnLineMap[remoteName]
if !ok{
this.SendMsg("该用户名不存在\n")
return
}
//获取消息内容,通过对方的User对象将消息内容发送过去
content := strings.Split(msg, "|")[2]
if content == ""{
this.SendMsg("无消息内容,请重发\n")
return
}
remoteUser.SendMsg(this.Name + "对您说:" + content + "\n")
}else{
this.server.BroadCast(this, msg)
}
}
//监听当前User channal的方法,一旦有消息,就直接发送给对端客户端
func (user *User) ListenMessage(){
for{
msg := <- user.C
user.conn.Write([]byte(msg + "\n"))
}
}
server.go
package main
import(
"fmt"
"net"
"sync"
"time"
)
type Server struct{
Ip string
Port int
//在线用户列表
OnLineMap map[string]*User
mapLock sync.RWMutex
//消息广播的channel
Message chan string
}
//创建一个server的接口
func NewServer(ip string, port int) *Server{
server := &Server{
Ip: ip,
Port: port,
OnLineMap: make(map[string]*User),
Message: make(chan string),
}
return server
}
//监听Message广播消息channal的goroutine,一旦有消息就发送给全部的在线User
func (this *Server)ListenMessage(){
for{
msg := <-this.Message
//将msg发送给全部的在线User
this.mapLock.Lock()
for _,cli := range this.OnLineMap{
cli.C <- msg
}
this.mapLock.Unlock()
}
}
//广播用户上线消息的方法
func (this *Server) BroadCast(user *User, msg string){
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
this.Message <- sendMsg
}
func (this *Server) Handler(conn net.Conn){
//fmt.Println("connect success")
user := NewUser(conn,this)
user.Online()
//监听用户是否活跃的chananl
isLive := make(chan bool)
//接受客户端发送的消息
go func(){
buf := make([]byte, 4096)
for{
n, err := conn.Read(buf)
if n == 0{
user.Offline()
return
}
if err != nil && n == 0{
fmt.Println("conn read error", err)
return
}
//提取用户的消息,去除'\n'
msg := string(buf[:n-1])
//用户针对msg进行消息处理
user.DoMessage(msg)
//用户的任意消息,代表当前用户是活跃的
isLive<-true
}
}()
//当前handler阻塞
for{
select {
case<-isLive:
//当前用户是活跃的,应该重置定时器
//不做任何事情,为了激活select,更新下面的定时器
case <-time.After(time.Second*5):
//用户已经超时
//当前的User应该强制关闭
user.SendMsg("你被踢了")
//销毁用的资源
close(user.C)
//关闭连接
conn.Close()
//退出当前的Handler
return//runtime.Goexit()
}
}
}
//启动服务器的方法
func (this *Server) Start(){
//socket listen
listener,err := net.Listen("tcp",fmt.Sprintf("%s:%d",this.Ip,this.Port))
if err != nil{
fmt.Println("listen error", err)
return
}
//Close listener socket
defer listener.Close()
//启动监听的goutine
go this.ListenMessage()
//accept
for{
conn, err := listener.Accept()
if err != nil{
fmt.Println("accept error", err)
continue
}
//do handler
go this.Handler(conn)
}
}
client.go
package main
import (
"fmt"
"net"
"flag"
"io"
"os"
)
type Client struct{
ServerIp string
ServerPort int
Name string
conn net.Conn
flag int//当前client的模式
}
//创建客户端
func NewClient(serverIp string,serverPort int) *Client{
//创建客户端对象
client := &Client{
ServerIp: serverIp,
ServerPort:serverPort,
flag:999,
}
//链接server
conn,err := net.Dial("tcp",fmt.Sprintf("%s:%d",serverIp,serverPort))
if err!=nil{
fmt.Println("net Dial error:",err)
return nil
}
client.conn =conn
//返回对象
return client
}
//处理server回应的消息,直接显示在标准输出
func (client *Client) DealResponse(){
//一旦client.conn有数据,就直接copy到stdout标准输出上,永久阻塞监听
io.Copy(os.Stdout,client.conn)
//等价于
//for{
// buf := make([]byte,4096)
// n,err := client.conn.Read(buf)
// fmt.Println(string(buf[:n]))
//}
}
func (client *Client) menu()bool{
var flag int
fmt.Println("1.公聊模式")
fmt.Println("2.私聊模式")
fmt.Println("3.更改用户名")
fmt.Println("0.退出")
fmt.Println("请输入指令:")
fmt.Scanln(&flag)
if flag >= 0 && flag <= 3{
client.flag = flag
return true
}else{
fmt.Println(">>>请输入正确的指令")
return false
}
}
//查询在线用户
func (client *Client) SelectUsers(){
sendMsg := "who\n"
_,err := client.conn.Write([]byte(sendMsg))
if err!=nil{
fmt.Println("conn Write error:",err)
return
}
}
//私聊模式
func (client *Client) PrivateChat(){
client.SelectUsers()
fmt.Println(">>>请输入聊天对象[用户名],exit退出:")
var remoteName string
fmt.Scanln(&remoteName)
for remoteName != "exit"{
fmt.Println(">>>请输入消息内容,exit退出:")
var chatMsg string
fmt.Scanln(&chatMsg)
for chatMsg != "exit"{
//消息不为空
if len(chatMsg) != 0{
//消息不为空
sendMsg := "to|" + remoteName + "|" + chatMsg + "\n"
_,err := client.conn.Write([]byte(sendMsg))
if err!=nil{
fmt.Println("conn Write error:",err)
break
}
}
chatMsg = ""
fmt.Println(">>>请输入消息内容,exit退出:")
fmt.Scanln(&chatMsg)
}
client.SelectUsers()
fmt.Println(">>>请输入聊天对象[用户名],exit退出:")
fmt.Scanln(&remoteName)
}
}
//共聊模式
func (client *Client) PublicChat(){
//提示用户输入消息
var chatMsg string
fmt.Println(">>>请输入聊天内容,exit退出")
fmt.Scanln(&chatMsg)
for chatMsg != "exit"{
//发送给服务器
if len(chatMsg) != 0{
_,err := client.conn.Write([]byte(chatMsg + "\n"))
if err!=nil{
fmt.Println("conn Write error:",err)
break
}
}
chatMsg = ""
fmt.Println(">>>请输入聊天内容,exit退出")
fmt.Scanln(&chatMsg)
}
}
//客户端版的更新用户名
func (client *Client) UpdateName()bool{
fmt.Println(">>>请输入用户名:")
fmt.Scanln(&client.Name)
sendMsg := "rename|" + client.Name + "\n"
_,err := client.conn.Write([]byte(sendMsg))
if err!=nil{
fmt.Println("conn Write error:",err)
return false
}
return true
}
func (client *Client) Run(){
for client.flag != 0{
//判断当前客户端的模式如果非法输入就一直死循环
for client.menu() != true{
}
switch client.flag{
case 1:
//公聊模式
fmt.Println(">>>公聊模式")
client.PublicChat()
break
case 2:
//私聊模式
fmt.Println(">>>私聊模式")
client.PrivateChat()
break
case 3:
//更改用户名
fmt.Println(">>>更改用户名")
client.UpdateName()
break
}
}
}
var serverIp string
var serverPort int
//./client -ip 127.0.0.1 -port 8080
//init操作就是把这两个形参绑定到flag这个包中
func init(){
//flag库运行我们绑定命令行参数 第二个参数就是上面的 -ip 第三个是默认值
//第四个是用户输入 ./client -h(help)的时候的提升
flag.StringVar(&serverIp,"ip","127.0.0.1","设置服务器IP地址(默认为127.0.0.1)")
flag.IntVar(&serverPort,"port",8080,"设置服务器端口(默认是8080)")
}
func main(){
//命令行解析
flag.Parse()
client := NewClient(serverIp,serverPort)
if client == nil{
fmt.Println(">>>连接服务器失败....")
return
}
//单独开启一个goroutine处理server的回执消息
go client.DealResponse()
fmt.Println(">>>连接服务器成功...")
//启动客户端的业务
client.Run()
}
本文根据小破站作者 刘丹冰Aceld 8小时专职Go 所著
在此感谢该博主讲的很棒!
最后的最后,创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖