关闭页面特效

Golang实现即时通讯系统

1|0即时通讯系统


1|11.基础server构建


  1. 创建一个Server的结构体,结构体应该包含服务端的IP和端口
  2. 写一个创建Server的方法
  3. 创建一个启动Server函数
  4. 创建一个业务链接函数

server.go

package main import ( "fmt" "net" ) type Server struct{ Ip string Port int } //创建Server对象 func NewServer(ip string,port int)*Server{ server:=&Server{ Ip:ip, Port:port, } return server } //启动Server服务函数 func(this *Server)Start(){ listener,err:=net.Listen("tcp",fmt.Sprintf("%s:%d",this.Ip,this.Port)) if err!=nil{ fmt.Plrintln("Accept Error:",err) return } for{ conn,err:=listener.Accept() if err!=nil{ fmt.Println("Listener Accept err:",err) continue } go this.Handler(conn) } //Handler函数 业务函数 func (this *Server)Handler(conn net.Conn){ fmt.Println("业务链接成功!") } }

main.go

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

1|22.用户上线及广播功能


  • user.go
    1. 创建User结构体
    2. 创建User对象
    3. 监听User对应channel消息
  • server.go
    1. Server结构体新增在线用户表和Message管道属性
    2. 在处理客户端上线的Handler函数中根据端口创建用户并添加到在线用户表中
    3. Handler函数中添加广播消息函数,广播用户上线的消息
    4. 添加广播消息的函数
    5. 添加广播消息channel方法
    6. 用一个goroutine单独监听Message管道

user.go

package main import "net" type User struct{ Name string Addr string C chan string conn net.Conn } //创建User对象 func NewUser(conn net.Conn)*User{ userAddr:=conn.RemoteAddr().String() user:=&User{ Name:userAddr, Addr:userAddr, C :make(chan string), conn:conn } //监听当前user的channel消息 go user.ListenMessage() return user } //监听当前用户channel管道消息函数,一旦有消息,就直接发送给对端客户端 func (this *User)ListenMessage(){ for { msg:=<-this.C this.conn.Write([]byte(msg+"\n")) } }

server.go

package main type Server struct { Ip string Port int Message chan string OnlineMap map[string]*User mapLock sync.RWMutex } //创建一个Server对象 func NewServer(ip string ,port int)*Server{ server:=&Server{ Ip:ip, Port:port, Message:make(chan string), OnlineMap:make(map[string]*User) } return server } //创建启动函数 func (this *Server)Start(){ listener,err:=net.Listen("tcp",fmt.Sprintf("%s:%d",this.IP,this.Port)) if err!=nil{ fmt.Println("net.Listen err:",err) return } defer listener.Close() go this.ListenMessager() for { conn,err:=listener.Accept() if err!=nil{ fmt.Println("Listenner accept err:",err) continue } go this.Handler(conn) } } //创建Handler函数 func (this *Server)Handler(conn net.Conn){ user:=NewUser(conn) this.mapLock.Lock() this.OnlineMap[user.Name]=user this.mapLock.Unlock() //广播当前用户上线的消息 this.BroadCast(user,"已上线") //阻塞当前handler select{} } //监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User func (this *Server)ListenMessager(){ msg:=<-this.Message this.mapLock.Lock() for _,cli:=range OnlineMap{ cli.C<-msg } this.mapLock.Unlock() } //广播函数 func (this *Server)BroadCast(user *User,msg string){ sendMsg:="["+user.Addr+"]"+user.name+":"+msg this.Message<-sendMsg }

1|33.用户消息广播机制


  • server.go
    1. 完善handle处理业务⽅法,启动 ⼀个针对当前客户端的读gorutine

server.go

package main type Server struct { Ip string Port int OnlineMap map[string]*User Message chan string mapLock sync.RWMutex } func NewServer(ip string ,port int)*Server{ server:=&Sever{ Ip:ip, Port:port, OlineMap:make(map[string]*User), Message:make(chan string), } return server } func(this *Sever)Handler(conn net.Conn){ user:=NewUser(conn) this.mapLock.Lock() OlineMap[user.Name]=user this.mapLock.Unlock() this.BroadCast(user,"已上线") go func(){ buf:=make([]byte,4096) for{ n,err:=conn.Read(buf) if n==0{ this.BroadCast(user,"已下线") return } if err!=nil&&err!=io.EOF{ fmt.Println("Conn Read Error:",err) return } msg:=string(buf[:n-1]) this.BroadCast(user,msg) } }() select{} } func (this *Server)Start(){ listener,err:=net.Listen() if err!=nil{ fmt.Println(net.Listen err:",err) } defer listener.Close() go this.ListenMessager() for{ conn,err:=listener.Accept() if err!=nil{ fmt.Println("Listener Accept err:",err) continue } go this.Handler(conn) } } func (this *Server)ListenMessager(){ msg:=<-this.Message this.mapLock.Lock() for _,cli:=range OnlineMap{ cli.C<-msg } this.mapLock.Unlock() } func(this *Server)BroadCast(user *User,msg string){ sendMsg:="["+user.Addr+"]"+user.Name+":"+msg this.Message<-sendMsg }

1|44.用户业务层封装


  • user.go
    1. 新增和server关联
    2. 新增online方法
    3. 新增offline方法
    4. 新增 doMessage方法
  • server.go
    1. 将之前的user业务进行替换

user.go

package main import "net" type User struct { Name string Addr string C chan string conn net.Conn server *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, } return user } func (this *User) Online() { this.server.mapLock.Lock() this.server.OnlineMap[this.Name] = this this.server.mapLock.Unlock() this.server.BroadCast(this, "已上线") } func (this *User) Offline() { this.server.mapLock.Lock() 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) } func (this *User) LitenMessage() { for { msg := <-this.C this.conn.Write([]byte(msg + "\n")) } }

server.go

package main import ( "fmt" "io" "net" "sync" ) type Server struct { Ip string Port int Message chan string OnlineMap map[string]*User mapLock sync.RWMutex } func NewServer(ip string, port int) *Server { server := &Server{ Ip: ip, Port: port, Message: make(chan string), OnlineMap: make(map[string]*User), } return server } func (this *Server) Handler(conn net.Conn) { user := NewUser(conn, this) user.Online() 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 } msg := string(buf[:n-1]) user.DoMessage(msg) } }() select {} } func (this *Server) Start() { listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } defer listener.Close() go this.ListenMessage() for { conn, err := listener.Accept() if err != nil { fmt.Println("listener accept err:", err) continue } go this.Handler(conn) } } func (this *Server) ListenMessage() { for { msg := <-this.Message this.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 + ":" + msg this.Message <- sendMsg }

1|55.查询在线用户


  • user.go
    1. 输入消息格式: “who”
    2. 新增SendMsg方法向对象客户端广播消息
    3. 在DoMessage()方法中,加上对“who”指令的处理,返回在线用户消息

user.go

package main import "net" type User struct { Name string Addr string C chan string conn net.Conn server *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, } return user } func (this *User) Online() { this.server.mapLock.Lock() this.server.OnlineMap[this.Name] = this this.server.mapLock.Unlock() this.server.BroadCast(this, "已上线") } func (this *User) Offline() { this.server.mapLock.Lock() delete(this.server.OnlineMap, this.Name) this.server.mapLock.Unlock() this.server.BroadCast(this, "已下线") } 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 _, cli := range (this.server.OnlineMap) { onlineMsg := "[" + cli.Addr + "]" + cli.Name + "在线...\n" this.SendMsg(onlineMsg) } this.server.mapLock.Unlock() } else { this.server.BroadCast(this, msg) } } func (this *User) LitenMessage() { for { msg := <-this.C this.conn.Write([]byte(msg + "\n")) } }

1|66.修改用户名


  • user.go
    1. 消息格式“rename|张三”
    2. 在DoMessage()⽅法中,加上对“rename|张三”指令的处理,返回在线⽤户信息
package main import ( "net" "strings" ) type User struct { Name string Addr string C chan string conn net.Conn server *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, } return user } func (this *User) Online() { this.server.mapLock.Lock() this.server.OnlineMap[this.Name] = this this.server.mapLock.Unlock() this.server.BroadCast(this, "已上线") } func (this *User) Offline() { this.server.mapLock.Lock() delete(this.server.OnlineMap, this.Name) this.server.mapLock.Unlock() this.server.BroadCast(this, "已下线") } 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 _, cli := range (this.server.OnlineMap) { onlineMsg := "[" + cli.Addr + "]" + cli.Name + "在线...\n" this.SendMsg(onlineMsg) } this.server.mapLock.Unlock() }else if len(msg)>7&&msg[:7]=="rename|" { newName:=strings.Split(msg,"|")[1] _,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]=this this.server.mapLock.Unlock() this.Name=newName this.SendMsg("您已经更新用户名:"+this.Name+"\n") } } else { this.server.BroadCast(this, msg) } } func (this *User) LitenMessage() { for { msg := <-this.C this.conn.Write([]byte(msg + "\n")) } }

1|77.超时强踢功能


  • server.go
    1. ⽤户的任意消息表示⽤户为活跃,⻓时间不发消息认为超时,就要强制关闭⽤户连接。
    2. 在⽤户的Hander() goroutine 中,添加⽤户活跃channel,⼀旦有消息,就向该channel发送数据
    3. 在⽤户的Hander() goroutine 中,添加定时器功能,超时则强踢

server.go

package main import ( "fmt" "io" "net" "sync" "time" ) type Server struct { Ip string Port int //在线用户的列表 OnlineMap map[string]*User mapLock sync.RWMutex //消息广播的channel Message 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发送给全部的在线User this.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 + ":" + msg this.Message <- sendMsg } func (this *Server) Handler(conn net.Conn) { //...当前链接的业务 //fmt.Println("链接建立成功") user := NewUser(conn, this) user.Online() //监听用户是否活跃的channel isLive := 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 * 10): //已经超时 //将当前的User强制的关闭 user.SendMsg("你被踢了") //销毁用的资源 close(user.C) //关闭连接 conn.Close() //退出当前Handler return //runtime.Goexit() } } } //启动服务器的接口 func (this *Server) Start() { //socket listen listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.Ip, this.Port)) if err != nil { fmt.Println("net.Listen err:", err) return } //close listen socket defer listener.Close() //启动监听Message的goroutine go this.ListenMessager() for { //accept conn, err := listener.Accept() if err != nil { fmt.Println("listener accept err:", err) continue } //do handler go this.Handler(conn) } }

1|88.私聊功能


  • user.go
    1. 消息格式: “To|张三|你好啊,我是。。”
    2. 在DoMessage()⽅法中,加上对“to|张三|你好啊,我是...”指令的处理,返回在线⽤户信息

user.go

package main type User struct{ Name string Addr string C chan string conn net.Conn server *Server } func NewUser(conn net.Conn,server *Server){ userAddr :=conn.RemoteAddr().String() user:=&User{ Name:userAddr, Addr:userAddr, C:make (chan string), conn:conn, server:server, } go user.ListenMessage() return user } func (this *User)Online(){ this.server.MapLock.Lock() this.server.OnlineMap[this.Name]=this this.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, "下线") } 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] = this this.server.mapLock.Unlock() this.Name = newName this.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) } } func (this *User)ListenMessage(){ for { msg:=<-this.C this.conn.Write([byte(msg+"\n")]) } }

1|99.客户端实现(建立连接)


  • Client.go
    1. 创建client结构体,结构体包括ServerIp,ServerPort,Name和conn
    2. 创建NewClient方法
package main import ( "fmt" "net" ) type Client struct { ServerIp string ServerPort int Name string conn net.Conn } func NewClient(serverIp string ,ServerPort int)*Client{ client:=&Client{ ServerIp:serverIp, ServerPort :serverPort, } conn,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 } func main(){ client:=NewClient("127.0.0.1",8888) if client==nil{ fmt.Println(">>>>Server Connect Failed") }else{ fmt.Println(">>>>Server Connect Success") } select{} }

1|1010.客户端实现(命令行解析)


  • client.go
    1. 添加init函数,使用flag

client.go

package main type Client struct{ ServerIp string ServerPort int Name string conn net.Conn flag int } func NewClient(serverIp string,serverPort int)*Client{ client:=&Client{ ServerIp :serverIp, ServerPort:serverPort, flag:999, } conn,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 } var serverIp string var serverPort int 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 } fmt.Println(">>>>>链接服务器成功...") }

1|1111.客户端实现(菜单显示)


  • client.go
    1. 新增menu函数

clien.go

package main type Client struct { ServerIp string ServerPort int Name string conn net.Conn flag int } func NewClient(serverIp string ,serverPort int)*Client{ client:=Client{ ServerIp:serverIp, ServerPort :serverPort, flag:999, } conn,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 } var serverIp string var serverPort int 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 } fmt.Println(">>>>>链接服务器成功...") }

1|1212.客户端实现(查修在线用户功能、私聊功能、公聊功能、更新用户名实现)


  • client.go
    1. 添加DealResponse函数将server返回的信息打印输出
    2. 添加SelectUser函数,给server发送消息“who”,server返回在线用户列表
    3. 添加PrivateChat函数
    4. 添加PublicChat函数
    5. 添加UpdateName函数
    6. 添加Run函数,使用case语句让用户选择使用模式

client.go

package main import ( "flag" "fmt" "io" "net" "os" ) type Client struct { ServerIp string ServerPort int Name string conn net.Conn flag int //当前Client的模式 } func NewClient(serverIp string, serverPort int) *Client { client := &Client{ ServerIp: serverIp, ServerPort: serverPort, flag: 999, } // 链接server conn, 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 } func (client *Client) DealResponse() { io.Copy(os.Stdout, client.conn) } func (client *Client) menu() bool { var flag int fmt.Println("1.公聊模式") fmt.Println("2.私聊模式") fmt.Println("3.更新用户名") fmt.Println("0.退出") fmt.Scanln(&flag) if flag >= 0 && flag <= 3 { client.flag = flag return true } else { fmt.Println(">>>>请输入合法范围内的数字<<<<<") return false } } // 查询在线用户 func (client *Client) SelectUser() { 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 string var chatMsg string client.SelectUser() 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.SelectUser() fmt.Println(">>>>请输入聊天对象[用户名],exit退出:") fmt.Scanln(&remoteName) } } // 公聊模式 func (client *Client) PublicChat() { var chatMsg string fmt.Println("请输入消息内容,exit退出:") fmt.Scanln(&chatMsg) for chatMsg != "exit" { if len(chatMsg) != 0 { _, err := client.conn.Write([]byte(chatMsg + "\n")) if err != nil { fmt.Println("conn write err:", err) break } } fmt.Println(">>>>>>请输入消息内容,exit退出:") fmt.Scanln(&chatMsg) } } // 更新用户名 func (client *Client) UpdateName() bool { fmt.Println(">>>>请输入新的用户名,exit退出:") fmt.Scanln(&client.Name) sendMsg := "rename|" + client.Name + "\n" if client.Name != "exit" { _, err := client.conn.Write([]byte(sendMsg)) if err != nil { fmt.Println("conn write err:", err) return false } } return true } var serverIp string var serverPort int func (client *Client) Run() { for client.flag != 0 { for client.menu() != true { } switch client.flag { case 1: client.PublicChat() break case 2: client.PrivateChat() break case 3: client.UpdateName() break } } } 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 } go client.DealResponse() fmt.Println(">>>>>链接服务器成功...") client.Run() }

__EOF__

作  者WeberBon
出  处https://www.cnblogs.com/Weber-security/p/17254960.html
关于博主:185猛男
版权声明:署名 - 非商业性使用 - 禁止演绎,协议普通文本 | 协议法律文本
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!

posted @   Weber·Bon  阅读(155)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
0
0
关注
跳至底部
点击右上角即可分享
微信分享提示