用户登录后,可以得到当前在线用户列表
示意图如下:
package processBlockimport ("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
}
loginResMes.Code = 200//这里用户已经登录成功,把登录成功的用户放入userManager中//将登录成功的用户id赋给thisthis.UserId = loginMes.UserIduserManager.AddOnlineUser(this)//将当前在线用户的id放入loginResMes.UsersId中//遍历userManager.onlineUsersfor id, _ := range userManager.onlineUsers {loginResMes.UsersId = append(loginResMes.UsersId, id)}fmt.Println("登录成功", user)
if loginResMes.Code == 200 {// fmt.Println("登录成功") //显示当前在线用户列表:loginResMes.UsersIdfmt.Println("当前在线用户列表:")for _, v := range loginResMes.UsersId {//如果要求不显示自己在线,使用continueif v == userId {continue}fmt.Printf("用户id:%v\n", v)}fmt.Println()//这里需要启动一个协程,保持和服务器通讯,如果服务器有数据,则推送给客户端,客户端接收后显示在终端go ServerProcessMes(conn)//1.显示登录成功的菜单[循环显示]for {ShowMenu()}}
type LoginResMes struct {Code int `json:"code"` //返回状态码: 200 登录成功, 500 用户未注册UsersId []int //增加一个字段:保存用户id的切片,用户返回给客户端Error string `json:"error"` //返回错误信息
}
思路1:
1.当有一个用户上线后,服务器就马上把维护的onlieUsers map整体推送
思路2:
1.服务器有自己的策略,每隔一定的时间,把维护的onlineUsers map整体推送
思路3:
1.当一个用户A上线,服务器就把A用户的上线信息,推送给所有在线的用户
2.客户端也需要维护一个map,map中记录了他的好友(目前就是所有人)map[int]User
3.客户端和服务器的通讯通道,要依赖serverProcessMes协程
//编写通知所有在线用户的方法
//userId要通知其他在线用户:我上线了
func (this *UserProcess) NotifyOthersOnlineUser(userId int) {//遍历onlineUsers,然后一个一个地发送NotifyUserStatusMesfor id, up := range userManager.onlineUsers {//过滤自己if id == userId {continue}//开始通知[单独写一个方法]up.NotifyMeOnline(userId)}
}func (this *UserProcess) NotifyMeOnline(userId int) {//组装NotifyUserStatusMesvar mes message.Messagemes.Type = message.NotifyUserStatusMesTypevar notifyUserStatusMes message.NotifyUserStatusMesnotifyUserStatusMes.UserId = userIdnotifyUserStatusMes.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
}
loginResMes.Code = 200//这里用户已经登录成功,把登录成功的用户放入userManager中//将登录成功的用户id赋给thisthis.UserId = loginMes.UserIduserManager.AddOnlineUser(this)//通知其他在线用户,我上线了this.NotifyOthersOnlineUser(loginMes.UserId)//将当前在线用户的id放入loginResMes.UsersId中//遍历userManager.onlineUsersfor id, _ := range userManager.onlineUsers {loginResMes.UsersId = append(loginResMes.UsersId, id)}fmt.Println("登录成功", user)
//定义消息类型
const (LoginMesType = "LoginMes"LoginResMesType = "LoginResMes"RegisterMesType = "RegisterMes"RegisterResMesType = "RegisterResMes"NotifyUserStatusMesType = "NotifyUserStatusMes"
)//定义几个用户状态常量
const (UserOnline = iotaUserOfflineUserBusyStatus
)
//为了配合服务端推送用户状态变化的消息
type NotifyUserStatusMes struct {UserId int `json:"userId"` // 用户idUserStatus int `json:"userStatus"` // 用户状态
}
package processBlockimport ("fmt""go_code/chatroom/common/message"
)//客户端要维护的map
var onlineUsers map[int]*message.User = make(map[int]*message.User, 10)//在客户端显示当前在线用户
func outputOnlineUser() {//遍历onlineUsersfmt.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.UserStatusonlineUsers[notifyUserStatusMes.UserId] = useroutputOnlineUser()
}
//显示登录成功后的界面
func ShowMenu() {fmt.Println("--------恭喜xxx登录成功--------")fmt.Println("--------1.显示在线用户列表--------")fmt.Println("--------2.发送消息--------")fmt.Println("--------3.信息列表--------")fmt.Println("--------4.退出系统--------")fmt.Println("-------请选择(1~4):----")var key intfmt.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.取出NotifyUserStatusMesvar notifyUserStatusMes message.NotifyUserStatusMesjson.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes)//2.把这个用户的消息,保存到客户维护的map[int]User中updateUserStatus(¬ifyUserStatusMes)//处理}// fmt.Printf("mes=%v\n", mes) }
}
完成客户端可以发送消息的思路
1.新增一个消息结构体SmMes
2.新增一个model CurUser
3.在smsProcess.go增加相应的方法 SendGroupMes,发送一个群聊的消息
4.在服务器端接收到SmsMes消息
5.在server/process/smsProcess.go文件增加群发消息的方法
6.在客户端还要增加去处理服务器端转发的群发消息
示意图
(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 modelimport("net""go_code/chatroom/common/message"
)//该结构体目的:维护当前连接
//在客户端很多地方会使用到CurUser,所以将其作为一个全局的,放在userManager.go中统一管理type CurUser struct {Conn net.Connmessage.User
}
(3).client/processBlock/smsProcess.go增加方法
package processBlockimport ("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.创建一个mesvar mes message.Messagemes.Type = message.SmsMesType//2.创建smsMes实例var smsMes message.SmsMessmsMes.Content = contentsmsMes.UserId = curUser.UserIdsmsMes.UserStatus = curUser.UserStatus//3.序列化smsMesdata, 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.再次序列化mesdata, 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结构体
//初始化CurUsercurUser.Conn = conncurUser.UserId = userIdcurUser.UserStatus = message.UserOnline// fmt.Println("登录成功") //显示当前在线用户列表:loginResMes.UsersIdfmt.Println("当前在线用户列表:")for _, v := range loginResMes.UsersId {//如果要求不显示自己在线,使用continueif 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 intvar 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("输入错误,请重新输入")}
}
完成客户端可以发送消息的思路
1.新增一个消息结构体SmMes
2.新增一个model CurUser
3.在smsProcess.go增加相应的方法 SendGroupMes,发送一个群聊的消息
4.在服务器端接收到SmsMes消息
5.在server/process/smsProcess.go文件增加群发消息的方法
6.在客户端还要增加去处理服务器端转发的群发消息SmsMes
(1).server/processBlock/smsProcess.go新增方法
package processBlockimport("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.SmsMeserr := 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实例,发送datatf := &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 : //处理登录消息//创建一个UserProcessup := &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 processBlockimport("fmt""encoding/json""go_code/chatroom/common/message"
)//该文件目前是为了处理输出消息相关逻辑func outputGroupMes(mes *message.Message) { //这个地方mes一定是smsMes//显示//1.反序列化mesvar smsMes message.SmsMeserr := 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.取出NotifyUserStatusMesvar notifyUserStatusMes message.NotifyUserStatusMesjson.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介入,用户登录,注册