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

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

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

go env -w GO111MODULE=on
mkdir -p wening/modules_testgo.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()
}
go get .../...go get github.com/aceld/zinx/zifacego get github.com/aceld/zinx/znetgo.sumgo.modgo run main.go
go.mod
require github.com/aceld/zinx v1.0.0 // indirect
github.com/aceld/zinxv1.0.0// indirect
go.sumh1+hashxxx/go.mod h1+hashgo.mod
$GOPATH\pkg\mod\github.com\aceld
$GOPATH\pkg\mod\

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

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

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

一 构建基础Server

server.gomain.goserver.gomain.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() //解锁
	}
}


<-chanchan<- chanchan <- valuechan

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

三 用户消息广播机制

四 用户业务层封装

五 在线用户查询

六 修改用户名

七 超时强踢功能

八 私聊功能

九 客户端实现