Golang 实战演练

🤪 内容来源于b站及其开源github(小破站牛b!)

ps:目前只更新到 二 ,未完待续呀~


使用Go Module初始化项目(练习)

Ⅰ. 开启go module
go env -w GO111MODULE=on
※ Go Module的开关,推荐go v1.11以上版本都设置为on,它将指向未来版本。

Ⅱ. 初始化项目
为了与GOPATH区分,这里不在GOPATH下创建文件夹(即,不要求在GOPATH下创建文件夹)
mkdir -p wening/modules_test
👆创建属于你自己的文件夹
👇进入你的文件夹下,初始化Go Module模块,即创建go.mod文件:
※ 建议跟上当前模块的名称,它决定我们未来导包时的路径名称。

> cd wening/modules_test
> go mod init github.com/wening/modules_test
go: creating new go.mod: module github.com/wening/modules_test

👇可以看一下这个go.mod文件:

module github.com/wening/modules_test

go 1.17

有了这个文件就可以说明Go Module模块触发成功

Ⅲ. 编辑简易代码查看效果
创建main.go,代码仅作示例参考不追究具体(作者给出的):

package main

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()
}
  • 普通导包(使用GOPATH)
    进入到该目录,执行go get .../...
  • Go Module导包
    直接go get github.com/aceld/zinx/zifacego get github.com/aceld/zinx/znet,从网络自动下载包文件。
    此时得到go.sum文件,且go.mod文件中多了一个需要的库。
    现在直接运行go run main.go
    在这里插入图片描述
    运行成功~

来看下go.mod文件中多出来的内容:

require github.com/aceld/zinx v1.0.0 // indirect

github.com/aceld/zinx包,v1.0.0版本号, // indirect表示间接依赖的库

go.sum罗列当前项目直接/间接依赖的所有版本模块,防篡改,保证完整性。只有两种方式:① h1+hash保证当前包的完整性(所有项目文件的校验和生成的hash,不存在可能表示依赖库暂时未启用);② xxx/go.mod h1+hash只能保证go.mod文件中的完整性

另外,刚刚下载的包存到哪里了呢?这里:$GOPATH\pkg\mod\github.com\aceld

这样做的好处就是,方便库包的管理,一般都会下载到$GOPATH\pkg\mod\这里,直接下载,不需要再特地进入某文件夹下去下载。

Ⅳ. 修改项目模块版本的依赖关系
可能的需求场景:版本更新覆盖,想启用老版本;也可用于国外版本镜像转换~

go mod edit -replace=新版本名=老版本名

此时go.mod将多一行replace...


项目案例——即时通信系统

一 构建基础Server

首先创建你的项目文件夹,以及其下的两个文件:server.gomain.go
(经验之谈:这样做的目的是为了模块基本划分,server.go用于完成服务端的基本构件,main.go用于程序主入口)

/*
 * server.go
 * server基本的listen操作 
 */
package main

import (
	"fmt"
	"net"
)

//结构体:ip和端口
type Serer struct {
	Ip   string
	Port int
}

//基本封装:创建一个server的接口
func NewServer(ip string, port int) *Serer {
	//创建基本的server对象(相当于将Server地址赋给server,指针接收)
	server := &Serer{
		Ip:   ip,
		Port: port,
	}
	return server
}

func (this *Serer) Handler(conn net.Conn) {	//为创建好的连接启动一个Handler
	//...当前链接业务
	fmt.Println("链接建立成功!")
}


//启动服务器的接口(首字母大写表示对外开放),启动上面接口的具体方法,分四步
func (this *Serer) Start() {
	//socket listen,传网络类型和服务器地址(一般ip=127.0.0.1:8888,我们需要拼接ip和port,格式化一下)
	listener,err := net.Listen("tcp",fmt.Sprintf("%s:%d",this.Ip,this.Port))
	if err != nil {	//捕获err基本初类型
		fmt.Println("net.Listen err:",err)
		return
	}
	//close listen socket
	defer listener.Close()	//别忘了释放

	for {
		//accept	成功返回连接(之后就可进行基本读写操作)	阻塞循环
		conn, err := listener.Accept()
		if err != nil {	//失败情况
			fmt.Println("listener accept err:",err)
			continue
		}

		//do handler	业务
		go this.Handler(conn)	//当前go层承载业务,异步协程

	}
}

🧐 go并发 了解一下~

/*
 * main.go
 * server入口
 */
package main //都属于main包,就不用import了

func main() {
	server := NewServer("127.0.0.1", 8888) //监听
	server.Start()                         //启动
}

编译:(注意,linux下不用写后缀,windows下要建立.exe文件,不然windows找不到打开方式)

> go build -o server.exe main.go server.go
> ls


    目录: D:\Go\PATH\src\GolangStudy\14-golang-IM-System


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----         2021/11/4     12:57            174 main.go
-a----         2021/11/4     12:58        2397184 server.exe
-a----         2021/11/4     12:55           1412 server.go

启动(为方便叙述,称此客户端为s1):

> ./server.exe

另开一个客户端(称此客户端为s2),使用指令模拟基本tcp客户端(演示为windows下的):

> telnet 127.0.0.1 8888
正在连接127.0.0.1...

(Linux 使用原生指令 nc ,windows 使用 telnet)
windows 开启 telnet 方法:
在这里插入图片描述
此时刚刚的客户端(即s1)显示如下:

> ./server.exe
链接建立成功!


二 用户上线功能

首先需要增加一个用户类

/*
 * user.go
 */
 package main

import "net"

//用户名、地址、与当前用户绑定的channel(每个用户都有一个)、当前用户唯一可以和客户端通信的链接
type User struct {
	Name string
	Addr string
	C    chan string
	conn net.Conn
}

//创建一个用户的API
func NewUser(conn net.Conn) *User {
	userAddr := conn.RemoteAddr().String() //从链接获取地址
	user := &User{
		Name: userAddr, //可以以当前地址作为名称
		Addr: userAddr,
		C:    make(chan string), //看来可以创建一个channel,make分配空间
		conn: conn,
	}

	//启动监听当前user channel消息的goroutine
	go user.ListenMessage()

	return user
}

//每个user都该启动一个go(goroutine),将启动信息发给客户端。
//监听当前User channel的方法,一旦有消息就直接发给客户端
func (user *User) ListenMessage() {
	for {
		msg := <-user.C //永远读取,写到channel中去

		user.conn.Write([]byte(msg + "\n")) //转二进制传输
	}
}

要实现上线提示功能,需要server得知上线,添加进用户表,并广播。server.go的具体改动如下:

//结构体中增加用户表和消息管道
type Server struct {
	Ip   string
	Port int

	//增加map表和管道
	//在线用户列表
	OnLineMap map[string]*User
	mapLock   sync.RWMutex //加一个执行锁
	//消息广播channel
	Message chan string
}
//结构体变更,server接口也要跟着动
func NewServer(ip string, port int) *Server {
	//创建基本的server对象(相当于将Server地址赋给server,指针接收)
	server := &Server{
		Ip:        ip,
		Port:      port,
		OnLineMap: make(map[string]*User), //在线用户列表
		Message:   make(chan string),      //消息广播channel
	}
	return server
}
//既然有了具体业务了,我们把事宜就写到Handler里去
func (this *Server) Handler(conn net.Conn) { //为创建好的连接启动一个Handler
	//...当前链接业务
	fmt.Println("链接建立成功!")

	user := NewUser(conn)
	//用户上线,更新在线用户列表
	this.mapLock.Lock() //上锁
	this.OnLineMap[user.Name] = user
	this.mapLock.Unlock() //解锁
	//广播当前用户上线消息
	this.BroadCast(user, "已上线!")
	//当前handler阻塞(handler暂时不能死,因为那将导致上述go程死亡,其子go程都将死亡)
	select {}
}
//这个业务用到的广播方法
//广播消息的方法,需要知道是谁发起的,以及内容是什么
func (this *Server) BroadCast(user *User, msg string) {
	sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg

	this.Message <- sendMsg
}
/* 向Start()的循环前增加消息监听!!!!!!
	//启动加载 Message 的 goroutine 
	go this.ListenMessage()
 */
//listen启动时即启动的永久go程,监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (this *Server) ListenMessage() {
	for {
		msg := <-this.Message
		//将mag发送给全部在线的user
		this.mapLock.Lock() //加锁
		for _, cli := range this.OnLineMap {
			cli.C <- msg //像每个用户分别发送消息
		}
		this.mapLock.Unlock() //解锁
	}
}



<- 是对chan类型来说的。
chan类型类似于一个数组, 当 <- chan 的时候是对chan中的数据读取; 相反 chan <- value 是对chan赋值。

效果如图:
在这里插入图片描述
针对汉字乱码问题:windows 控制台cmd乱码的解决办法


三 用户消息广播机制

四 用户业务层封装

五 在线用户查询

六 修改用户名

七 超时强踢功能

八 私聊功能

九 客户端实现

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值