Go完整即时通讯项目及Go的生态介绍

news/2024/10/31 3:18:07/

Go完整即时通讯项目

项目架构:
在这里插入图片描述

1 编写基本服务端-Server

server.go

package mainimport ("fmt""net"
)// 定义服务端
type Server struct {ip   stringport int
}// 创建一个Server
func NewServer(ip string, port int) *Server {return &Server{ip:   ip,port: port,}
}// 定义Server方法
func (this *Server) Start() {//根据ip+port监听socket套接字 tcp表明类型【socket listen】listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))if err != nil {fmt.Println("net.Listen err=", err)return}//关闭套接字,避免资源浪费defer listen.Close()for {conn, err := listen.Accept()if err != nil {fmt.Println("listen accept err=", err)continue}//处理连接请求【具体业务:处理客户端请求】//开启协程处理,避免占用主线程【server一直要监听ip+port】go this.Handler(conn)}
}// 处理连接请求
func (this *Server) Handler(conn net.Conn) {//当前连接的业务fmt.Printf("连接建立成功...")
}

通过在main.go中启动一个server并配合telnet命令检测代码是否正确

  • telnet:可以模拟连接的建立
  • telnet 127.0.0.1 8082

main.go:

package mainfunc main() {//创建一个serverserver := NewServer("127.0.0.1", 8082)//启动server【监听】server.Start()
}
//打包代码为exe
go build -o intime.exe .\main.go .\server.go

在这里插入图片描述

2 实现用户上线广播机制【用户上线功能】

架构图:Server端存储一个OnlineMap,用于记录在线的用户

在这里插入图片描述

  1. 编写user.go,编写User结构体并实现对user.channel的监听
  2. 修改server.go,新增OnlineMap和Message属性,在处理的客户端上线的Handler中连接建立成功之后将用户添加到OnlineMap;并新增广播消息方法
  3. 在server.go中新增监听广播消息channel的方法,同时用一个goroutine单独监听message
//构建代码 生成intime.exe文件
go build -o intime.exe .\main.go .\server.go .\user.go 

3 用户消息广播机制

修改server.go,完善一个handle处理业务方法,启动一个专门针对当前用户的goroutine

server.go:

package mainimport ("fmt""io""net""sync"
)// 定义服务端
type Server struct {ip   stringport int//定义一个map,用于存储在线用户OnlineMap map[string]*User//定义map锁,保证存储map时候的数据正确,避免并发读取mapLock sync.RWMutex//消息广播的channel[当该chan中有数据时,直接广播给所有在线用户]Message chan string
}// 创建一个Server
func NewServer(ip string, port int) *Server {return &Server{ip:        ip,port:      port,OnlineMap: make(map[string]*User),Message:   make(chan string),}
}// 定义Server方法
func (this *Server) Start() {//根据ip+port监听socket套接字 tcp表明类型【socket listen】listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))if err != nil {fmt.Println("net.Listen err=", err)return}//关闭套接字,避免资源浪费defer listen.Close()//启动监听Message的goroutinego this.ListenMessage()for {conn, err := listen.Accept()if err != nil {fmt.Println("listen accept err=", err)continue}//处理连接请求【具体业务:处理客户端请求】//开启协程处理,避免占用主线程【server一直要监听ip+port】go this.Handler(conn)}
}// 处理连接请求
func (this *Server) Handler(conn net.Conn) {//当前连接的业务[用户上线之后,发送广播通知]//fmt.Printf("连接建立成功...")//1. 创建user,并将user添加到OnlineMap中,此处操作map(可能涉及并发,因此加锁保证安全性)user := NewUser(conn)this.mapLock.Lock()this.OnlineMap[user.Name] = userthis.mapLock.Unlock()//2. 将该用户上线消息进行广播this.Broadcast(user, "已上线")//3. 接收用户发送的消息,并广播go func() {buf := make([]byte, 4096)//接收用户信息n, err := conn.Read(buf)if n == 0 {this.Broadcast(user, "下线")return}if err != nil && err != io.EOF {fmt.Println("Conn read err:", err)return}//提取用户消息(去除"\n")msg := string(buf[:n-1])//广播消息this.Broadcast(user, msg)}()//4. 阻塞当前handlerselect {}
}// 广播消息
func (this *Server) Broadcast(user *User, message string) {sendMsg := "[" + user.Addr + "] " + user.Name + ":" + message//将消息写入广播chan中this.Message <- sendMsg
}// 监听Message广播消息channel的goroutine,一旦有消息就广播发送给所有在线用户
func (this *Server) ListenMessage() {for {msg := <-this.Message//遍历OnlineMap,将消息广播给所有用户[设置并发操作map,加锁]this.mapLock.Lock()for _, cli := range this.OnlineMap {//将广播消息写入用户的channel,等待用户监听读取cli.C <- msg}this.mapLock.Unlock()}
}

4 用户业务层封装

修改user.go,新增对应方法:

  • user中新增一个Server属性
  • Online
  • Offline
  • DoMessage等

替换之前server.go中涉及到user的代码

user.go:

package mainimport ("net""unicode/utf8"
)type User struct {Name stringAddr string//管道用于接收服务端的消息C chan string//与服务器端的连接conn net.Conn//对应连接的Serverserver *Server
}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就应该监听自己chan管道中的消息,如果有就取出go user.ListenMessage()return user
}// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {for {msg := <-this.C//从user管道中读取消息,发送给user客户端对中文进行处理//msgByte := []byte(msg)//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)//if err != nil {//	fmt.Println("simplifiedchinese decoder err=", err)//}runes := []rune(msg + "\n")bytes := make([]byte, len(runes)*4)for _, v := range runes {buf := make([]byte, 4)size := utf8.EncodeRune(buf, v)bytes = append(bytes, buf[:size]...)}//this.conn.Write([]byte(msg + "\n"))this.conn.Write(bytes)}
}// Online:用户上线方法
func (this *User) Online() {//用户上线,将用户添加到OnlineMap中this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.server.mapLock.Unlock()//广播用户上线信息this.server.Broadcast(this, "已上线")
}// 用户下线业务
func (this *User) Offline() {this.server.mapLock.Lock()//根据key删除对应值delete(this.server.OnlineMap, this.Name)this.server.mapLock.Unlock()this.server.Broadcast(this, "下线")
}// 用户处理消息的业务
func (this *User) DoMessage(msg string) {this.server.Broadcast(this, msg)
}

server.go:

package mainimport ("fmt""io""net""sync"
)// 定义服务端
type Server struct {ip   stringport int//定义一个map,用于存储在线用户OnlineMap map[string]*User//定义map锁,保证存储map时候的数据正确,避免并发读取mapLock sync.RWMutex//消息广播的channel[当该chan中有数据时,直接广播给所有在线用户]Message chan string
}// 创建一个Server
func NewServer(ip string, port int) *Server {return &Server{ip:        ip,port:      port,OnlineMap: make(map[string]*User),Message:   make(chan string),}
}// 定义Server方法
func (this *Server) Start() {//根据ip+port监听socket套接字 tcp表明类型【socket listen】listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))if err != nil {fmt.Println("net.Listen err=", err)return}//关闭套接字,避免资源浪费defer listen.Close()//启动监听Message的goroutinego this.ListenMessage()for {conn, err := listen.Accept()if err != nil {fmt.Println("listen accept err=", err)continue}//处理连接请求【具体业务:处理客户端请求】//开启协程处理,避免占用主线程【server一直要监听ip+port】go this.Handler(conn)}
}// 处理连接请求
func (this *Server) Handler(conn net.Conn) {//当前连接的业务[用户上线之后,发送广播通知]//fmt.Printf("连接建立成功...")// 创建user,并将user添加到OnlineMap中,此处操作map(可能涉及并发,因此加锁保证安全性)user := NewUser(conn, this)user.Online()// 接收用户发送的消息,并广播go func() {buf := make([]byte, 4096)//接收用户信息n, err := conn.Read(buf)if n == 0 {//用户下线user.Offline()return}if err != nil && err != io.EOF {fmt.Println("Conn read err:", err)return}//提取用户消息(去除"\n")msg := string(buf[:n-1])//用户针对msg进行消息处理user.DoMessage(msg)}()//4. 阻塞当前handlerselect {}
}// 广播消息
func (this *Server) Broadcast(user *User, message string) {sendMsg := "[" + user.Addr + "] " + user.Name + ":" + message//将消息写入广播chan中this.Message <- sendMsg
}// 监听Message广播消息channel的goroutine,一旦有消息就广播发送给所有在线用户
func (this *Server) ListenMessage() {for {msg := <-this.Message//遍历OnlineMap,将消息广播给所有用户[设置并发操作map,加锁]this.mapLock.Lock()for _, cli := range this.OnlineMap {//将广播消息写入用户的channel,等待用户监听读取cli.C <- msg}this.mapLock.Unlock()}
}

5 用户查询功能

实现,用户在终端输入who,查看当前在线用户(修改user.go)

  • 添加SendMsg():给客户端发送消息
  • 新增判断“who”命令逻辑
package mainimport ("net""unicode/utf8"
)type User struct {Name stringAddr string//管道用于接收服务端的消息C chan string//与服务器端的连接conn net.Conn//对应连接的Serverserver *Server
}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就应该监听自己chan管道中的消息,如果有就取出go user.ListenMessage()return user
}// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {for {msg := <-this.C//从user管道中读取消息,发送给user客户端对中文进行处理//msgByte := []byte(msg)//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)//if err != nil {//	fmt.Println("simplifiedchinese decoder err=", err)//}runes := []rune(msg + "\n")bytes := make([]byte, len(runes)*4)for _, v := range runes {buf := make([]byte, 4)size := utf8.EncodeRune(buf, v)bytes = append(bytes, buf[:size]...)}//this.conn.Write([]byte(msg + "\n"))this.conn.Write(bytes)}
}// Online:用户上线方法
func (this *User) Online() {//用户上线,将用户添加到OnlineMap中this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.server.mapLock.Unlock()//广播用户上线信息this.server.Broadcast(this, "已上线")
}// 用户下线业务
func (this *User) Offline() {this.server.mapLock.Lock()//根据key删除对应值delete(this.server.OnlineMap, this.Name)this.server.mapLock.Unlock()this.server.Broadcast(this, "下线")
}// 用户处理消息的业务
func (this *User) DoMessage(msg string) {//添加who命令逻辑:查询当前在线用户if msg == "who" {this.server.mapLock.Lock()for _, user := range this.server.OnlineMap {onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\n"this.SendMsg(onlineMsg)}}this.server.Broadcast(this, msg)
}// 给当前user的客户端发送消息
func (this *User) SendMsg(msg string) {this.conn.Write([]byte(msg))
}

6 修改用户名

定义命令rename|zhangsan:将当前用户名修改为张三

  • 修改user.go:在DoMessage()方法中判断命令是否为rename

user.go:

package mainimport ("net""strings""unicode/utf8"
)type User struct {Name stringAddr string//管道用于接收服务端的消息C chan string//与服务器端的连接conn net.Conn//对应连接的Serverserver *Server
}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就应该监听自己chan管道中的消息,如果有就取出go user.ListenMessage()return user
}// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {for {msg := <-this.C//从user管道中读取消息,发送给user客户端对中文进行处理//msgByte := []byte(msg)//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)//if err != nil {//	fmt.Println("simplifiedchinese decoder err=", err)//}runes := []rune(msg + "\n")bytes := make([]byte, len(runes)*4)for _, v := range runes {buf := make([]byte, 4)size := utf8.EncodeRune(buf, v)bytes = append(bytes, buf[:size]...)}//this.conn.Write([]byte(msg + "\n"))this.conn.Write(bytes)}
}// Online:用户上线方法
func (this *User) Online() {//用户上线,将用户添加到OnlineMap中this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.server.mapLock.Unlock()//广播用户上线信息this.server.Broadcast(this, "is online")
}// 用户下线业务
func (this *User) Offline() {this.server.mapLock.Lock()//根据key删除对应值delete(this.server.OnlineMap, this.Name)this.server.mapLock.Unlock()this.server.Broadcast(this, "is offline")
}// 用户处理消息的业务
func (this *User) DoMessage(msg string) {//添加who命令逻辑:查询当前在线用户if msg == "who" {this.server.mapLock.Lock()for _, user := range this.server.OnlineMap {onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\n"this.SendMsg(onlineMsg)}} else if len(msg) > 7 && msg[:7] == "rename|" {newName := strings.Split(msg, "|")[1]//判断要修改的name是否已经被占用_, ok := this.server.OnlineMap[newName]if ok {this.SendMsg("the name is already exists...")} else {this.server.mapLock.Lock()delete(this.server.OnlineMap, this.Name)this.server.OnlineMap[newName] = thisthis.server.mapLock.Unlock()this.Name = newName //更新页面当前用户this.SendMsg("update name success:" + this.Name + "\n")}} else {this.server.Broadcast(this, msg)}
}// 给当前user的客户端发送消息
func (this *User) SendMsg(msg string) {this.conn.Write([]byte(msg))
}

7 超时强踢功能

如果某个用户长时间不发消息,不活跃,达到一定时间则断开连接,达到强踢效果

  • 修改server.go:
    ①在用户的Hander() goroutine中,添加用户活跃channel,一旦有消息就向该channel发送数据
    ②在用户的Hander()goroutine中,添加定时器功能,超时则强踢

server.go:

package mainimport ("fmt""io""net""sync""time"
)// 定义服务端
type Server struct {ip   stringport int//定义一个map,用于存储在线用户OnlineMap map[string]*User//定义map锁,保证存储map时候的数据正确,避免并发读取mapLock sync.RWMutex//消息广播的channel[当该chan中有数据时,直接广播给所有在线用户]Message chan string
}// 创建一个Server
func NewServer(ip string, port int) *Server {return &Server{ip:        ip,port:      port,OnlineMap: make(map[string]*User),Message:   make(chan string),}
}// 定义Server方法
func (this *Server) Start() {//根据ip+port监听socket套接字 tcp表明类型【socket listen】listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))if err != nil {fmt.Println("net.Listen err=", err)return}//关闭套接字,避免资源浪费defer listen.Close()//启动监听Message的goroutinego this.ListenMessage()for {conn, err := listen.Accept()if err != nil {fmt.Println("listen accept err=", err)continue}//处理连接请求【具体业务:处理客户端请求】//开启协程处理,避免占用主线程【server一直要监听ip+port】go this.Handler(conn)}
}// 处理连接请求
func (this *Server) Handler(conn net.Conn) {//当前连接的业务[用户上线之后,发送广播通知]//fmt.Printf("连接建立成功...")// 创建user,并将user添加到OnlineMap中,此处操作map(可能涉及并发,因此加锁保证安全性)user := NewUser(conn, this)user.Online()//监听用户是否活跃的channelisLive := make(chan bool)// 接收用户发送的消息,并广播go func() {buf := make([]byte, 4096)//接收用户信息n, err := conn.Read(buf)if n == 0 {//用户下线user.Offline()return}if err != nil && err != io.EOF {fmt.Println("Conn read err:", err)return}//提取用户消息(去除"\n")msg := string(buf[:n-1])//用户针对msg进行消息处理user.DoMessage(msg)//用户的任意消息代表用户当前是一个活跃的isLive <- true}()//4. 当前handler阻塞【超时强制踢出】for {select {case <-isLive://当前用户是活跃的,应该重置定时器//不做任何事,为了激活select,更新下面的定时器case <-time.After(time.Second * 10)://已经超时,将当前的User强制关闭user.SendMsg("you have been offline")//销毁用的资源,关闭channelclose(user.C)//关闭连接conn.Close()//退出当前Handler[runtime.Goexit()]return}}
}// 广播消息
func (this *Server) Broadcast(user *User, message string) {sendMsg := "[" + user.Addr + "] " + user.Name + ":" + message//将消息写入广播chan中this.Message <- sendMsg
}// 监听Message广播消息channel的goroutine,一旦有消息就广播发送给所有在线用户
func (this *Server) ListenMessage() {for {msg := <-this.Message//遍历OnlineMap,将消息广播给所有用户[设置并发操作map,加锁]this.mapLock.Lock()for _, cli := range this.OnlineMap {//将广播消息写入用户的channel,等待用户监听读取cli.C <- msg}this.mapLock.Unlock()}
}

8 私聊功能

消息格式:to|zhangsan|hello, how are you

  • 修改user.go的DoMessage()逻辑,新增私聊消息判断

user.go:

package mainimport ("net""strings""unicode/utf8"
)type User struct {Name stringAddr string//管道用于接收服务端的消息C chan string//与服务器端的连接conn net.Conn//对应连接的Serverserver *Server
}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就应该监听自己chan管道中的消息,如果有就取出go user.ListenMessage()return user
}// 监听当前user 的channel,一旦有消息,就直接发送给对应的客户端
func (this *User) ListenMessage() {for {msg := <-this.C//从user管道中读取消息,发送给user客户端对中文进行处理//msgByte := []byte(msg)//bytes, err := simplifiedchinese.GB18030.NewDecoder().Bytes(msgByte)//if err != nil {//	fmt.Println("simplifiedchinese decoder err=", err)//}runes := []rune(msg + "\n")bytes := make([]byte, len(runes)*4)for _, v := range runes {buf := make([]byte, 4)size := utf8.EncodeRune(buf, v)bytes = append(bytes, buf[:size]...)}//this.conn.Write([]byte(msg + "\n"))this.conn.Write(bytes)}
}// Online:用户上线方法
func (this *User) Online() {//用户上线,将用户添加到OnlineMap中this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.server.mapLock.Unlock()//广播用户上线信息this.server.Broadcast(this, "is online")
}// 用户下线业务
func (this *User) Offline() {this.server.mapLock.Lock()//根据key删除对应值delete(this.server.OnlineMap, this.Name)this.server.mapLock.Unlock()this.server.Broadcast(this, "is offline")
}// 用户处理消息的业务
func (this *User) DoMessage(msg string) {//添加who命令逻辑:查询当前在线用户if msg == "who" {this.server.mapLock.Lock()for _, user := range this.server.OnlineMap {onlineMsg := "[" + user.Addr + "]" + user.Name + ":" + "online...\n"this.SendMsg(onlineMsg)}} else if len(msg) > 7 && msg[:7] == "rename|" {newName := strings.Split(msg, "|")[1]//判断要修改的name是否已经被占用_, ok := this.server.OnlineMap[newName]if ok {this.SendMsg("the name is already exists...")} else {this.server.mapLock.Lock()delete(this.server.OnlineMap, this.Name)this.server.OnlineMap[newName] = thisthis.server.mapLock.Unlock()this.Name = newName //更新页面当前用户this.SendMsg("update name success:" + this.Name + "\n")}} else if len(msg) > 4 && msg[:3] == "to|" {//如果是私聊命令 消息格式: to|zhangsan|msg content//1. 获取对方用户名remoteName := strings.Split(msg, "|")[1]if remoteName == "" {this.SendMsg("the msg format is incorrect, please use the 'to|zhangsan|msg content' to send a msg\n")return}//2. 根据用户名,得到对方的user对象remoteUser, ok := this.server.OnlineMap[remoteName]if !ok {this.SendMsg("the user is not exist")return}//3. 获取消息内容,通过对方的User对象将消息内容发送过去content := strings.Split(msg, "|")[2]if content == "" {this.SendMsg("please do not send a empty msg\n")return}remoteUser.SendMsg(this.Name + "is speak to you:" + content)} else {this.server.Broadcast(this, msg)}
}// 给当前user的客户端发送消息
func (this *User) SendMsg(msg string) {this.conn.Write([]byte(msg))
}

9 客户端实现(过程省略)

9.1 客户端类型定义与链接

9.2 解析命令行

9.3 菜单显示

9.4 更新用户名客户端实现

9.5 公聊模式

9.6 私聊模式

10 最终代码

①main.go

package mainfunc main() {server := NewServer("127.0.0.1", 8888)server.Start()
}

②server.go

package mainimport ("fmt""io""net""sync""time"
)type Server struct {Ip   stringPort int//在线用户的列表OnlineMap map[string]*UsermapLock   sync.RWMutex//消息广播的channelMessage 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广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (this *Server) ListenMessager() {for {msg := <-this.Message//将msg发送给全部的在线Userthis.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 + ":" + msgthis.Message <- sendMsg
}func (this *Server) Handler(conn net.Conn) {//...当前链接的业务//fmt.Println("链接建立成功")user := NewUser(conn, this)user.Online()//监听用户是否活跃的channelisLive := make(chan bool)//接受客户端发送的消息go func() {buf := make([]byte, 4096)for {n, err := conn.Read(buf)if n == 0 {user.Offline()return}if err != nil && err != io.EOF {fmt.Println("Conn Read err:", 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 * 300)://已经超时//将当前的User强制的关闭user.SendMsg("你被踢了")//销毁用的资源close(user.C)//关闭连接conn.Close()//退出当前Handlerreturn //runtime.Goexit()}}
}//启动服务器的接口
func (this *Server) Start() {//socket listenlistener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port))if err != nil {fmt.Println("net.Listen err:", err)return}//close listen socketdefer listener.Close()//启动监听Message的goroutinego this.ListenMessager()for {//acceptconn, err := listener.Accept()if err != nil {fmt.Println("listener accept err:", err)continue}//do handlergo this.Handler(conn)}
}

③user.go

package mainimport ("net""strings"
)type User struct {Name stringAddr stringC    chan stringconn net.Connserver *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 channel消息的goroutinego user.ListenMessage()return user
}//用户的上线业务
func (this *User) Online() {//用户上线,将用户加入到onlineMap中this.server.mapLock.Lock()this.server.OnlineMap[this.Name] = thisthis.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|张三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] = thisthis.server.mapLock.Unlock()this.Name = newNamethis.SendMsg("您已经更新用户名:" + this.Name + "\n")}} else if len(msg) > 4 && msg[:3] == "to|" {//消息格式:  to|张三|消息内容//1 获取对方的用户名remoteName := strings.Split(msg, "|")[1]if remoteName == "" {this.SendMsg("消息格式不正确,请使用 \"to|张三|你好啊\"格式。\n")return}//2 根据用户名 得到对方User对象remoteUser, ok := this.server.OnlineMap[remoteName]if !ok {this.SendMsg("该用户名不不存在\n")return}//3 获取消息内容,通过对方的User对象将消息内容发送过去content := strings.Split(msg, "|")[2]if content == "" {this.SendMsg("无消息内容,请重发\n")return}remoteUser.SendMsg(this.Name + "对您说:" + content)} else {this.server.BroadCast(this, msg)}
}//监听当前User channel的 方法,一旦有消息,就直接发送给对端客户端
func (this *User) ListenMessage() {for {msg := <-this.Cthis.conn.Write([]byte(msg + "\n"))}
}

④client.go

package mainimport ("flag""fmt""io""net""os"
)type Client struct {ServerIp   stringServerPort intName       stringconn       net.Connflag       int //当前client的模式
}func NewClient(serverIp string, serverPort int) *Client {//创建客户端对象client := &Client{ServerIp:   serverIp,ServerPort: serverPort,flag:       999,}//链接serverconn, 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)
}func (client *Client) menu() bool {var flag intfmt.Println("1.公聊模式")fmt.Println("2.私聊模式")fmt.Println("3.更新用户名")fmt.Println("0.退出")fmt.Scanln(&flag)if flag >= 0 && flag <= 3 {client.flag = flagreturn 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 err:", err)return}
}//私聊模式
func (client *Client) PrivateChat() {var remoteName stringvar chatMsg stringclient.SelectUsers()fmt.Println(">>>>请输入聊天对象[用户名], exit退出:")fmt.Scanln(&remoteName)for remoteName != "exit" {fmt.Println(">>>>请输入消息内容, exit退出:")fmt.Scanln(&chatMsg)for chatMsg != "exit" {//消息不为空则发送if len(chatMsg) != 0 {sendMsg := "to|" + remoteName + "|" + chatMsg + "\n\n"_, err := client.conn.Write([]byte(sendMsg))if err != nil {fmt.Println("conn Write err:", err)break}}chatMsg = ""fmt.Println(">>>>请输入消息内容, exit退出:")fmt.Scanln(&chatMsg)}client.SelectUsers()fmt.Println(">>>>请输入聊天对象[用户名], exit退出:")fmt.Scanln(&remoteName)}
}func (client *Client) PublicChat() {//提示用户输入消息var chatMsg stringfmt.Println(">>>>请输入聊天内容,exit退出.")fmt.Scanln(&chatMsg)for chatMsg != "exit" {//发给服务器//消息不为空则发送if len(chatMsg) != 0 {sendMsg := chatMsg + "\n"_, err := client.conn.Write([]byte(sendMsg))if err != nil {fmt.Println("conn Write err:", 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 err:", err)return false}return true
}func (client *Client) Run() {for client.flag != 0 {for client.menu() != true {}//根据不同的模式处理不同的业务switch client.flag {case 1://公聊模式client.PublicChat()breakcase 2://私聊模式client.PrivateChat()breakcase 3://更新用户名client.UpdateName()break}}
}var serverIp string
var serverPort int//./client -ip 127.0.0.1 -port 8888
func init() {flag.StringVar(&serverIp, "ip", "127.0.0.1", "设置服务器IP地址(默认是127.0.0.1)")flag.IntVar(&serverPort, "port", 8888, "设置服务器端口(默认是8888)")
}func main() {//命令行解析flag.Parse()client := NewClient(serverIp, serverPort)if client == nil {fmt.Println(">>>>> 链接服务器失败...")return}//单独开启一个goroutine去处理server的回执消息go client.DealResponse()fmt.Println(">>>>>链接服务器成功...")//启动客户端的业务client.Run()
}

11 go的全部生态

在这里插入图片描述

参考:

  • 资料地址:https://pan.baidu.com/s/1glckD7XGInHDFQQKCRE66g#list/path=%2F

http://www.ppmy.cn/news/92220.html

相关文章

perl 通过 swig 调用 c++代码

Swig 是一个软件开发工具&#xff0c;可以简化不同语言与 C/C 的交互&#xff08;直接在其它语言的代码中调用 C/C 的代码&#xff09;。   记录一下成功用 perl 调用 c 代码的例子。 环境 操作系统&#xff1a;centos 7.9 perl: version 5.16.3 swig: version 2.0.10 g: v…

操作dom

1-获取元素 通过id、name、className属性获取&#xff0c;通过tagName获取&#xff1b; 通过id获取的是元素列表第一个&#xff1b;通过name属性获取的是NodeList&#xff1b;通过className和tagName获取的是HTMLCollection let son1 document.getElementById("son&quo…

C4D R26 渲染学习笔记(2):渲染流程介绍

往期文章 C4D R26 渲染学习笔记&#xff08;1&#xff09;&#xff1a;C4D版本选择和初始UI框介绍 3D建模流程 大致流程 #mermaid-svg-eE2RXHal49sVZ34l {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-eE2RXHal4…

深度学习框架-Tensorflow2:特点、架构、应用和未来发展趋势

引言 深度学习是一种新兴的技术&#xff0c;已经在许多领域中得到广泛的应用&#xff0c;如计算机视觉、自然语言处理、语音识别等。在深度学习中&#xff0c;深度学习框架扮演着重要的角色。Tensorflow是一种广泛使用的深度学习框架&#xff0c;已经成为深度学习的事实标准。…

PLX31-EIP-MBTCP 以太网/IP到Modbus TCP/IP

PLX31-EIP-MBTCP ProSoft Technology的EtherNet/IP to Modbus TCP/IP通信网关允许在支持EtherNet/IP的控制器或设备与Modbus TCP/IP控制器或设备之间进行高速双向数据传输。 我们的Modbus TCP/IP驱动程序具有多种客户端和服务器功能&#xff0c;可实现更快的数据传输。此外&a…

自动清理 ES 历史数据

一、 背景 随着业务的增长和时间的变化&#xff0c;ES 数据库的存储空间越来越大&#xff0c;存储数据多数为系统监控日志&#xff0c;保存的数据不需要长期保留&#xff0c;多数情况只需要保留几个月ES数据即可&#xff0c;既可以减轻ES服务器的负载和资源使用率&#xff0c;还…

ChatGpt都这么火了,它使用的 BPE 分词算法要不要了解一下?

Byte Pair Encoding&#xff08;BPE&#xff09;是一种文本压缩算法&#xff0c;它通常用于自然语言处理领域中的分词、词汇表构建等任务。BPE 算法的核心思想是通过不断地合并字符或子词来生成词汇表。 在这里&#xff0c;我们将对 BPE 算法进行全面、详细的讲解&#xff0c;…

自动化测试-基础知识—Bash基础

Bash 在 Bash 中&#xff0c;美元符号 $ 可以用于引用变量或者表达式的值。Bash 中的变量并不需要事先声明&#xff0c;而是在第一次赋值时自动创建。基于这个特性&#xff0c;我们可以通过给变量名加上 $ 的方式来引用它的值&#xff0c;比如 $var 表示引用变量 var 的值。 …