1.实现功能-完成登录时能返回当前在线用户
用户登录后,可以得到当前在线用户列表
示意图如下:

讯享网 步骤:
(1).编写了server/processBlock/userManager.go
package processBlock import ( "fmt" ) //因为UserManager实例在服务器中有且只有一个,并且在很多地方都要使用,因此,将其定义为全局变量 var ( userManager *UserManager ) type UserManager struct { onlineUsers map[int]*UserProcess } //完成对UserManager的初始化 func init() { userManager = &UserManager { onlineUsers: make(map[int]*UserProcess, 1024), } } //完成对onlineUsers添加 func (this *UserManager) AddOnlineUser(up *UserProcess) { this.onlineUsers[up.UserId] = up } //完成对onlineUsers删除 func (this *UserManager) DelOnlineUser(up *UserProcess) { delete(this.onlineUsers, up.UserId) } //返回当前所有在线用户 func (this *UserManager) GetAllOnlineUser() map[int]*UserProcess { return this.onlineUsers } //根据id返回对应的值 func (this *UserManager) GetOnlineUserById(userId int) (up *UserProcess, err error) { //如何从map中取出一个值,带检测方式 up, ok := this.onlineUsers[userId] if !ok { // 说明要查找的这个用户,当前不在线 err = fmt.Errorf("用户 %d 不存在", userId) return } return }
讯享网
(2).完善了sercer/processBlock/userProcess.go中的Login()方法
讯享网 loginResMes.Code = 200 //这里用户已经登录成功,把登录成功的用户放入userManager中 //将登录成功的用户id赋给this this.UserId = loginMes.UserId userManager.AddOnlineUser(this) //将当前在线用户的id放入loginResMes.UsersId中 //遍历userManager.onlineUsers for id, _ := range userManager.onlineUsers { loginResMes.UsersId = append(loginResMes.UsersId, id) } fmt.Println("登录成功", user)
(3).完善了client/processBlock/userProcess.go中的Login()方法
if loginResMes.Code == 200 { // fmt.Println("登录成功") //显示当前在线用户列表:loginResMes.UsersId fmt.Println("当前在线用户列表:") for _, v := range loginResMes.UsersId { //如果要求不显示自己在线,使用continue if v == userId { continue } fmt.Printf("用户id:%v\n", v) } fmt.Println() //这里需要启动一个协程,保持和服务器通讯,如果服务器有数据,则推送给客户端,客户端接收后显示在终端 go ServerProcessMes(conn) //1.显示登录成功的菜单[循环显示] for { ShowMenu() } }
(4).完善了common/message/message.go中的LoginResMes ()
讯享网type LoginResMes struct { Code int `json:"code"` //返回状态码: 200 登录成功, 500 用户未注册 UsersId []int //增加一个字段:保存用户id的切片,用户返回给客户端 Error string `json:"error"` //返回错误信息 }
2.当一个新的用户上线后,其它已经登录的用户也能获取最新在线用户列表
思路1:
1.当有一个用户上线后,服务器就马上把维护的onlieUsers map整体推送
思路2:
1.服务器有自己的策略,每隔一定的时间,把维护的onlineUsers map整体推送
思路3:
1.当一个用户A上线,服务器就把A用户的上线信息,推送给所有在线的用户
2.客户端也需要维护一个map,map中记录了他的好友(目前就是所有人)map[int]User
3.客户端和服务器的通讯通道,要依赖serverProcessMes协程
(1).server/processBlock/userProcess.go增加了方法
//编写通知所有在线用户的方法 //userId要通知其他在线用户:我上线了 func (this *UserProcess) NotifyOthersOnlineUser(userId int) { //遍历onlineUsers,然后一个一个地发送NotifyUserStatusMes for id, up := range userManager.onlineUsers { //过滤自己 if id == userId { continue } //开始通知[单独写一个方法] up.NotifyMeOnline(userId) } } func (this *UserProcess) NotifyMeOnline(userId int) { //组装NotifyUserStatusMes var mes message.Message mes.Type = message.NotifyUserStatusMesType var notifyUserStatusMes message.NotifyUserStatusMes notifyUserStatusMes.UserId = userId notifyUserStatusMes.UserStatus = message.UserOnline //将notifyUserStatusMes序列化 data, err := json.Marshal(notifyUserStatusMes) if err != nil { fmt.Println("NotifyMeOnline json marshal fail, err=", err) return } //将序列化后的notifyUserStatusMes赋值给mes.Data mes.Data = string(data) //对mes再次序列化 data, err = json.Marshal(mes) if err != nil { fmt.Println("NotifyMeOnline json marshal fail, err=", err) return } //发送,创建一个Transfer实例 tf := &utils.Transfer { Conn: this.Conn, } err = tf.WritePkg(data) if err != nil { fmt.Println("NotifyMeOnline WritePkg fail, err=", err) return } return }
(2).server/processBlock/userProcess.go Login方法修改了代码
讯享网 loginResMes.Code = 200 //这里用户已经登录成功,把登录成功的用户放入userManager中 //将登录成功的用户id赋给this this.UserId = loginMes.UserId userManager.AddOnlineUser(this) //通知其他在线用户,我上线了 this.NotifyOthersOnlineUser(loginMes.UserId) //将当前在线用户的id放入loginResMes.UsersId中 //遍历userManager.onlineUsers for id, _ := range userManager.onlineUsers { loginResMes.UsersId = append(loginResMes.UsersId, id) } fmt.Println("登录成功", user)
(3).common/message/message.go增加了方法以及常量配置
//定义消息类型 const ( LoginMesType = "LoginMes" LoginResMesType = "LoginResMes" RegisterMesType = "RegisterMes" RegisterResMesType = "RegisterResMes" NotifyUserStatusMesType = "NotifyUserStatusMes" ) //定义几个用户状态常量 const ( UserOnline = iota UserOffline UserBusyStatus ) //为了配合服务端推送用户状态变化的消息 type NotifyUserStatusMes struct { UserId int `json:"userId"` // 用户id UserStatus int `json:"userStatus"` // 用户状态 }
(4).新增文件client/processBlock/userManager.go
讯享网package processBlock import ( "fmt" "go_code/chatroom/common/message" ) //客户端要维护的map var onlineUsers map[int]*message.User = make(map[int]*message.User, 10) //在客户端显示当前在线用户 func outputOnlineUser() { //遍历onlineUsers fmt.Println("当前在线用户列表:") for id, _ := range onlineUsers { fmt.Println("用户id:\t", id) } } //编写一个方法,处理返回的NotifyUserStatusMes func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) { user, ok := onlineUsers[notifyUserStatusMes.UserId] if !ok { // 原来没有 user = &message.User{ UserId: notifyUserStatusMes.UserId, } } user.UserStatus = notifyUserStatusMes.UserStatus onlineUsers[notifyUserStatusMes.UserId] = user outputOnlineUser() }
(5).client/processBlock/server.go修改
//显示登录成功后的界面 func ShowMenu() { fmt.Println("--------恭喜xxx登录成功--------") fmt.Println("--------1.显示在线用户列表--------") fmt.Println("--------2.发送消息--------") fmt.Println("--------3.信息列表--------") fmt.Println("--------4.退出系统--------") fmt.Println("-------请选择(1~4):----") var key int fmt.Scanf("%d\n", &key) switch key { case 1: // fmt.Println("显示在线用户列表") outputOnlineUser() case 2: fmt.Println("发送消息") case 3: fmt.Println("信息列表") case 4: fmt.Println("退出了系统") os.Exit(0) default: fmt.Println("输入错误,请重新输入") } } //和服务端端保持通讯 func ServerProcessMes(conn net.Conn) { //创建一个Transfer实例,让它不停地读取服务器发送的消息 tf := &utils.Transfer { Conn: conn, } for { fmt.Println("客户端正在等待读取服务器发送的消息") mes, err := tf.ReadPkg() if err != nil { fmt.Println("tf.readpkg err =", err) return } //如果读取到消息,则进行下一步逻辑处理 switch mes.Type { case message.NotifyUserStatusMesType: //有人上线了 //1.取出NotifyUserStatusMes var notifyUserStatusMes message.NotifyUserStatusMes json.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes) //2.把这个用户的消息,保存到客户维护的map[int]User中 updateUserStatus(¬ifyUserStatusMes) //处理 } // fmt.Printf("mes=%v\n", mes) } }
3.实现功能-完成登录后用户可以群聊
完成客户端可以发送消息的思路
1.新增一个消息结构体SmMes
2.新增一个model CurUser
3.在smsProcess.go增加相应的方法 SendGroupMes,发送一个群聊的消息
4.在服务器端接收到SmsMes消息
5.在server/process/smsProcess.go文件增加群发消息的方法
6.在客户端还要增加去处理服务器端转发的群发消息
示意图


步骤1:当用户上线后,可以将群聊消息发给服务器,服务器就可以接收到
(1).common/message/message.go增加方法
讯享网//定义消息类型 const ( LoginMesType = "LoginMes" LoginResMesType = "LoginResMes" RegisterMesType = "RegisterMes" RegisterResMesType = "RegisterResMes" NotifyUserStatusMesType = "NotifyUserStatusMes" SmsMesType = "SmsMes" ) //增加一个SmsMes,发送消息 type SmsMes struct { Content string `json:"content"`//内容 User //匿名结构体,继承type User struct }
(2).新建文件client/model/curUser.go
package model import( "net" "go_code/chatroom/common/message" ) //该结构体目的:维护当前连接 //在客户端很多地方会使用到CurUser,所以将其作为一个全局的,放在userManager.go中统一管理 type CurUser struct { Conn net.Conn message.User }
(3).client/processBlock/smsProcess.go增加方法
讯享网package processBlock import ( "fmt" "encoding/json" "go_code/chatroom/common/message" "go_code/chatroom/client/utils" ) type SmsProcess struct { } //发送群聊消息 func (this *SmsProcess) SendGroupMes(content string) (err error) { //1.创建一个mes var mes message.Message mes.Type = message.SmsMesType //2.创建smsMes实例 var smsMes message.SmsMes smsMes.Content = content smsMes.UserId = curUser.UserId smsMes.UserStatus = curUser.UserStatus //3.序列化smsMes data, err := json.Marshal(smsMes) if err != nil { fmt.Println("SendGroupMes json Marshal smsMes fail, err = ", err) return } //4.给mes.Data赋值 mes.Data = string(data) //5.再次序列化mes data, err = json.Marshal(mes) if err != nil { fmt.Println("SendGroupMes json Marshal mes fail, err = ", err) return } //6.将mes发送给服务器 tf := &utils.Transfer{ Conn: curUser.Conn, } err = tf.WritePkg(data) if err != nil { fmt.Println("SendGroupMes transfer writePkg fail, err = ", err) return } return }
(4).client/processBlock/userProcess.go Login()方法初始化CurUser结构体
//初始化CurUser curUser.Conn = conn curUser.UserId = userId curUser.UserStatus = message.UserOnline // fmt.Println("登录成功") //显示当前在线用户列表:loginResMes.UsersId fmt.Println("当前在线用户列表:") for _, v := range loginResMes.UsersId { //如果要求不显示自己在线,使用continue if v == userId { continue } fmt.Printf("用户id:%v\n", v) //完成客户端的onlineUsers完成初始化 user := &message.User { UserId: v, UserStatus: message.UserOnline, } onlineUsers[v] = user } fmt.Println() //这里需要启动一个协程,保持和服务器通讯,如果服务器有数据,则推送给客户端,客户端接收后显示在终端 go ServerProcessMes(conn) //1.显示登录成功的菜单[循环显示] for { ShowMenu() }
(5).client/processBlock/server.go调用群聊方法
讯享网//显示登录成功后的界面 func ShowMenu() { fmt.Println("--------恭喜xxx登录成功--------") fmt.Println("--------1.显示在线用户列表--------") fmt.Println("--------2.发送消息--------") fmt.Println("--------3.信息列表--------") fmt.Println("--------4.退出系统--------") fmt.Println("-------请选择(1~4):----") var key int var content string //有时候总会使用SmsProcess实例,故把该实例定义在switch外面 smsProcess := &SmsProcess{} fmt.Scanf("%d\n", &key) switch key { case 1: // fmt.Println("显示在线用户列表") outputOnlineUser() case 2: fmt.Println("请输入想对大家说的话:") fmt.Scanf("%s\n", &content) smsProcess.SendGroupMes(content) case 3: fmt.Println("信息列表") case 4: fmt.Println("退出了系统") os.Exit(0) default: fmt.Println("输入错误,请重新输入") } }
步骤2:服务器可以将接收到的消息,群发给所有在线用户(发送者除外)
完成客户端可以发送消息的思路
1.新增一个消息结构体SmMes
2.新增一个model CurUser
3.在smsProcess.go增加相应的方法 SendGroupMes,发送一个群聊的消息
4.在服务器端接收到SmsMes消息
5.在server/process/smsProcess.go文件增加群发消息的方法
6.在客户端还要增加去处理服务器端转发的群发消息SmsMes
(1).server/processBlock/smsProcess.go新增方法
package processBlock import( "fmt" "net" "encoding/json" "go_code/chatroom/common/message" "go_code/chatroom/server/utils" ) type SmsProcess struct { } //转发消息 func (this *SmsProcess) SendGroupMes(mes *message.Message) { //遍历服务器端的onlineUsers map[int]*UserProcess //将消息转发出去 //取出mes中的内容 var smsMes message.SmsMes err := json.Unmarshal([]byte(mes.Data), &smsMes) if err != nil { fmt.Println("SendGroupMes json Ummarshal fail, err = ", err) return } data, err := json.Marshal(mes) if err != nil { fmt.Println("SendGroupMes json Marshal fail, err = ", err) return } for id, up := range userManager.onlineUsers { //过滤自己:不需要给自己发送消息 if id == smsMes.UserId { continue } this.SendMesToEachOnlineUser(data, up.Conn) } } func (this *SmsProcess) SendMesToEachOnlineUser(data []byte, conn net.Conn) { //创建一个Transfer实例,发送data tf := &utils.Transfer { Conn : conn, } err := tf.WritePkg(data) if err != nil { fmt.Println("转发消息失败, err = ", err) } return }
(2).server/main/processor.go中调用smsProcess. SendGroupMes()方法
讯享网//编写一个ServerProcessMes函数 //功能:根据客户端发送消息类型不同,决定调用哪个函数来处理 func (this *Processor) serverProcessMes(mes *message.Message) (err error) { switch mes.Type { case message.LoginMesType : //处理登录消息 //创建一个UserProcess up := &processBlock.UserProcess{ Conn: this.Conn, } err = up.ServerProcessLogin(mes) case message.RegisterMesType : //处理注册 up := &processBlock.UserProcess{ Conn: this.Conn, } err = up.ServerProcessRegister(mes) case message.SmsMesType : //穿甲一个SmsProcess实例,完成消息的转发 sms := &processBlock.SmsProcess{} sms.SendGroupMes(mes) default : fmt.Println("消息类型不存在, 无法处理...") } return }
(3).client/processBlock/smsManager.go 新增方法
package processBlock import( "fmt" "encoding/json" "go_code/chatroom/common/message" ) //该文件目前是为了处理输出消息相关逻辑 func outputGroupMes(mes *message.Message) { //这个地方mes一定是smsMes //显示 //1.反序列化mes var smsMes message.SmsMes err := json.Unmarshal([]byte(mes.Data), &smsMes) if err != nil { fmt.Println("outputGroupMes json.Unmarshal fail, err =", err) return } //显示消息 info := fmt.Sprintf("用户:%d\t,对大家说:%s", smsMes.UserId, smsMes.Content) fmt.Println(info) }
(4).client/processBlock/server.go中调用方法 smsManger.outputGroupMes()
讯享网//和服务端端保持通讯 func ServerProcessMes(conn net.Conn) { //创建一个Transfer实例,让它不停地读取服务器发送的消息 tf := &utils.Transfer { Conn: conn, } for { fmt.Println("客户端正在等待读取服务器发送的消息") mes, err := tf.ReadPkg() if err != nil { fmt.Println("tf.readpkg err =", err) return } //如果读取到消息,则进行下一步逻辑处理 switch mes.Type { case message.NotifyUserStatusMesType: //有人上线了 //1.取出NotifyUserStatusMes var notifyUserStatusMes message.NotifyUserStatusMes json.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes) //2.把这个用户的消息,保存到客户维护的map[int]User中 updateUserStatus(¬ifyUserStatusMes) //处理 case message.SmsMesType: //有人群发消息了 outputGroupMes(&mes) default: fmt.Println("") } // fmt.Printf("mes=%v\n", mes) }
[上一节][go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册
[下一节][go学习笔记.第十七章.redis的使用] 1.redis的使用

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/49513.html