栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

Golang 实战演练

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Golang 实战演练

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

练习
    • 使用Go Module初始化项目(练习)
    • 项目案例——即时通信系统
      • 一 构建基础Server
      • 二 用户上线功能
      • 三 用户消息广播机制
      • 四 用户业务层封装
      • 五 在线用户查询
      • 六 修改用户名
      • 七 超时强踢功能
      • 八 私聊功能
      • 九 客户端实现

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/ziface,go 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文件中的完整性

另外,刚刚下载的包存到哪里了呢?这里:$GOPATHpkgmodgithub.comaceld

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

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

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

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


项目案例——即时通信系统 一 构建基础Server

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

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并发 了解一下~

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:GoPATHsrcGolangStudy14-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
链接建立成功!


二 用户上线功能

首先需要增加一个用户类

 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
}

//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乱码的解决办法


三 用户消息广播机制 四 用户业务层封装 五 在线用户查询 六 修改用户名 七 超时强踢功能 八 私聊功能 九 客户端实现
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945529.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号