🤪 内容来源于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乱码的解决办法