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,用于记录在线的用户
- 编写user.go,编写User结构体并实现对user.channel的监听
- 修改server.go,新增OnlineMap和Message属性,在处理的客户端上线的Handler中连接建立成功之后将用户添加到OnlineMap;并新增广播消息方法
- 在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