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介入,用户登录,注册