2.经典项目-海量用户即使通讯系统

1.实现功能-完成注册用户

完成用户注册的步骤(客户端)

1.将User移动到common/message文件夹下

2.在message中新增注册用户的结构体

const (
	LoginMesType       = "LoginMes"
	LoginResMesType    = "LoginResMes"
	RegisterMesType    = "RegisterMes"
	RegisterResMesType = "RegisterResMes"
)

type RegisterMes struct {
	User User `json:"user"` //类型就是User机构体
}
type RegisterResMes struct {
	Code  int    `json:"code"`  //返回状态码 400表示该用户已存在 200表示注册成功
	Error string `json:"error"` //返回错误信息
}

3.在client/process/userProcess.go中添加注册函数

func (this *UserProcess) Register(userId int, userPwd, userName string) (err error) {
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err = ", err)
		return
	}
	defer conn.Close()

	var mes = message.Message{}
	mes.Type = message.RegisterMesType
	registerMes := message.RegisterMes{}
	registerMes.User.UserId = userId
	registerMes.User.UserPwd = userPwd
	registerMes.User.UserName = userName

	data, err := json.Marshal(registerMes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}

	mes.Data = string(data)

	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}

	tf := &utils.Transfer{
		Conn: conn,
	}
	err = tf.WritePkg(data)
	if err != nil {
		fmt.Println("注册发送信息错误 err = ", err)
		return
	}

	//处理服务器端返回的消息
	mes, err = tf.ReadPkg() //mes就是 RegisterResMes
	if err != nil {
		fmt.Println("utils.ReadPkg(conn) err = ", err)
		return
	}

	var registerResMes message.RegisterResMes
	err = json.Unmarshal([]byte(mes.Data), &registerResMes)
	if registerResMes.Code == 200 {
		fmt.Println("注册成功,请重新登录")
	} else {
		fmt.Println(registerResMes.Error)
	}
	return
}

4.在client/main/main.go中修改注册相关代码

		case 2:
			fmt.Println("注册用户")
			fmt.Println("请输入用户id:")
			fmt.Scanf("%d\n", &userId)
			fmt.Println("请输入用户密码:")
			fmt.Scanf("%s\n", &userPwd)
			fmt.Println("请输入用户名称:")
			fmt.Scanf("%s", &userName)
			up := &process2.UserProcess{}
			up.Register(userId, userPwd, userName)

完成用户注册的步骤(服务器端)

1.在model/userDao.go中添加

func (this *UserDao) Register(user *message.User) (err error) {
	conn := this.pool.Get()
	defer conn.Close()
	_, err = this.getUserById(conn, user.UserId)
	if err == nil {
		err = ERROR_USER_EXISTS
		return
	}

	//账户不存在,则可以正常注册
	data, err := json.Marshal(user) //序列化
	if err != nil {
		return
	}

	//入库
	_, err = conn.Do("HSet", "users", user.UserId, string(data))
	if err != nil {
		fmt.Println("保存注册用户错误 err = ", err)
		return
	}
	return
}

2.在process/userProcess.go中添加

func (this *UserProcess) ServerProcessRegister(mes *message.Message) (err error) {
	var registerMes message.RegisterMes
	err = json.Unmarshal([]byte(mes.Data), &registerMes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}

	var resMes message.Message
	resMes.Type = message.RegisterResMesType

	var registerResMes message.RegisterResMes

	err = model.MyUserDao.Register(&registerMes.User)

	if err != nil {
		if err == model.ERROR_USER_EXISTS {
			registerResMes.Code = 505
			registerResMes.Error = err.Error()
		} else {
			registerResMes.Code = 506
			registerResMes.Error = "注册发生未知错误"
		}
	} else {
		registerResMes.Code = 200
		fmt.Println("用户注册成功")
	}

	data, err := json.Marshal(registerResMes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}
	resMes.Data = string(data)

	data, err = json.Marshal(resMes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}

	// 发送data,将其封装到writePkg函数
	//因为使用分层模式(mvc)需要先创建一个Transfer实例,然后读取
	tf := &utils.Transfer{
		Conn: this.Conn,
	}
	err = tf.WritePkg(data)
	return
}

3.修改main/processor.go

func (this *Processor) ServerProcessMes(mes *message.Message) (err error) {
	switch mes.Type {
	case message.LoginMesType:
		//处理登录
		//创建UserPorcess实例
		up := &process2.UserProcess{
			Conn: this.Conn,
		}
		err = up.ServerProcessLogin(mes)
	case message.RegisterMesType:
		up := &process2.UserProcess{
			Conn: this.Conn,
		}
		err = up.ServerProcessRegister(mes)
	default:
		fmt.Println("消息类型不存在,无法处理...")
	}
	return
}

2.实现功能-完成登录时能返回当前在线用户

1.在服务器端维护一个onlineUsers map[int] *UserProcess

2.创建一个新的文件userMgr.go,完成功能对onlineUsers的增删改查

3.在LoginResMess增加一个字段Users []int //保存在线用户id

4.当用户登录后可以显示当前在线用户列表

代码实现

新增server/process/userMgr.go

package process

import "fmt"

// 因为UserMgr实例在服务器端有且仅有一个,在很多机房会用到
// 因此将其定义为全局变量
var (
	userMgr *UserMgr
)

type UserMgr struct {
	onlineUsers map[int]*UserProcess
}

// 完成对UserMgr初始化工作
func init() {
	userMgr = &UserMgr{
		onlineUsers: make(map[int]*UserProcess, 1024),
	}
}

// 完成对onlineUsers添加
func (this *UserMgr) AddOnlineUser(up *UserProcess) {
	this.onlineUsers[up.UserId] = up
}

// 删除
func (this *UserMgr) DelOnlineUser(userId int) {
	delete(this.onlineUsers, userId)
}

// 返回当前所有在线用户
func (this *UserMgr) GetAllOnlineUser() map[int]*UserProcess {
	return this.onlineUsers
}

// 根据id返回对应的值
func (this *UserMgr) GetOnlineUserById(userId int) (up *UserProcess, err error) {
	//从map取出一个值,带检测方式
	up, ok := this.onlineUsers[userId]
	if !ok { //说明,要找的用户当前不在线
		err = fmt.Errorf("用户%d 不在线", userId)
		return
	}
	return
}

修改server/process/userProcess.go

type UserProcess struct {
	Conn net.Conn
	//增加一个字段,表示该Conn是哪个用户
	UserId int
}

	} else {
		loginResMes.Code = 200
		//将登录成功的用户的userId赋给this
		this.UserId = loginMes.UserId
		//将登录成功的用户放入userMgr中
		userMgr.AddOnlineUser(this)
		//将当前在线用户的id放到loginResMes.UserIds中
		for id, _ := range userMgr.onlineUsers {
			loginResMes.UserIds = append(loginResMes.UserIds, id)
		}
		fmt.Println(user.UserName, "账户登录成功")

修改message.go

type LoginResMes struct {
	Code    int    `json:"code"`    //返回状态码 500 表示该用户未注册 200表示登录成功
	UserIds []int  `json:"userIds"` //增加字段保存userid的切片
	Error   string `json:"error"`   //返回错误信息
}

修改client/process/userProcess.go

	if loginResMes.Code == 200 {
		//可以显示当前在线用户列表
		fmt.Println("当前在线用户列表如下:")
		for _, v := range loginResMes.UserIds {
			//不显示自己
			if v == userId {
				continue
			}
			fmt.Println("用户id:\t", v)
		}
		fmt.Print("\n\n")

优化:当一个新的用户上线后,其他已经登录的用户也能获取最新在线用户列表

思路:

  1. 当用户A上线,服务器九八A用户的上线信息推给所有在线的用户
  2. 客户端也需要维护一个map,map中记录了他的好友(目前就是所有人)map[int]User
  3. 客户端和服务器的通讯通道,要依赖serverProcessMes协程

代码实现

在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
	Status int `json:"status"` //用户状态
}

修改user.go

// 定义一个用户的结构体
type User struct {
	//确定字段信息
	//为了序列化和反序列化成功,需保证用户信息的json字符串的key 和结构体的字段对应的tag名字一致
	UserId     int    `json:"userId"`
	UserPwd    string `json:"userPwd"`
	UserName   string `json:"userName"`
	UserStatus int    `json:"userStatus"` //用户在线状态
}

修改server/process/userProcess.go

	} else {
		loginResMes.Code = 200
		//将登录成功的用户的userId赋给this
		this.UserId = loginMes.UserId
		//将登录成功的用户放入userMgr中
		userMgr.AddOnlineUser(this)
		//通知其他在线用户
		this.NotifyOtherOnlineUser(this.UserId)
		//将当前在线用户的id放到loginResMes.UserIds中
		for id, _ := range userMgr.onlineUsers {
			loginResMes.UserIds = append(loginResMes.UserIds, id)
		}
		fmt.Println(user.UserName, "账户登录成功")
	}

在server/process/userProcess.go中增加

// 编写通知所有在线的用户的方法
func (this *UserProcess) NotifyOtherOnlineUser(userId int) {
	//遍历onlineUsers 然后一个个发送NotifyUserStatusMes
	for id, up := range userMgr.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.Status = message.UserOnline

	//将notifyUserStatusMes序列化
	data, err := json.Marshal(notifyUserStatusMes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}
	//将序列化后的notifyUserStatusMes复制给mes.Data
	mes.Data = string(data)

	//对mes再次序列化,准备发送
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}

	//发送,Transfer实例
	tf := &utils.Transfer{
		Conn: this.Conn,
	}

	err = tf.WritePkg(data)
	if err != nil {
		fmt.Println("NotifyMeOnline err = ", err)
		return
	}
}

修改client/process/server.go

		case 1:
			fmt.Println("显示在线用户列表")
			outpuOnlineUser()

// 和服务器保持通讯
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), &notifyUserStatusMes)
			//2. 把这个用户信息,状态保存到客户map[int]User中
			updateUserStatus(&notifyUserStatusMes)
		default:
			fmt.Println("服务器端返回了未知的消息类型")
		}
		//fmt.Printf("mes = %v\n", mes)
	}
}

新增client/process/userMgt.go

package process

import (
	"fmt"
	"project/common/message"
)

// 客户端维护的map
var onlineUsers map[int]*message.User = make(map[int]*message.User, 10)

// 在客户端显示当前在线的用户
func outpuOnlineUser() {
	//遍历onlilneUsers
	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.Status
	onlineUsers[notifyUserStatusMes.UserId] = user
	outpuOnlineUser()
}

修改client/process/userProcess.go

	if loginResMes.Code == 200 {
		//可以显示当前在线用户列表
		fmt.Println("当前在线用户列表如下:")
		for _, v := range loginResMes.UserIds {
			//不显示自己
			if v == userId {
				continue
			}
			fmt.Println("用户id:\t", v)
			//完成客户端的onlineUsers初始化
			user := &message.User{
				UserId:     v,
				UserStatus: message.UserOnline,
			}
			onlineUsers[v] = user
		}
		fmt.Print("\n\n")

3.实现功能-完成登录用户群聊

3.1 完成客户端发送消息

思路

1.新增一个消息结构体smsMes...

2.新增一个model/CurUser

3.在smsProcess.go增加相应的方法SendGroupMes发送一个群聊消息

代码实现

在message中新增

const (
	LoginMesType            = "LoginMes"
	LoginResMesType         = "LoginResMes"
	RegisterMesType         = "RegisterMes"
	RegisterResMesType      = "RegisterResMes"
	NotifyUserStatusMesType = "NotifyUserStatusMes"
	SmsMesType              = "SmsMes"
)

// 增加一个SmsMes发送消息
type SmsMes struct {
	Content string `json:"content"` //消息内容
	User           //匿名结构体,集继承
}

新增client/model/curUser.go

package model

import (
	"net"
	"project/common/message"
)

// 在客户端很多地方要用到,需声明为全局
type CurUser struct {
	Conn net.Conn
	message.User
}

在client/process/userMgr.go中新增

var CurUser model.CurUser //在用户登录成功后,完成对CurUser初始化

在client/process/userProcess.go修改

	if loginResMes.Code == 200 {
		//初始化CurUser
		CurUser.Conn = conn
		CurUser.UserId = userId
		CurUser.UserStatus = message.UserOnline

在client/process/smsProcess.go新增

package process

import (
	"encoding/json"
	"fmt"
	"project/common/message"
	"project/common/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 err = ", err.Error())
		return
	}
	mes.Data = string(data)
	//4.对mes再次序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("SendGroupMes json.Marshal err = ", err.Error())
		return
	}

	//5.将mes发送给服务器
	tf := &utils.Transfer{
		Conn: CurUser.Conn,
	}

	//6.发送
	err = tf.WritePkg(data)
	if err != nil {
		fmt.Println("sendGroupMes err = ", err.Error())
		return
	}
	return
}

修改client/process/server.go

		var key int
		var content string
		//用到SmsProcess实例较为频繁,因此定义在外部
		smsProcess := &SmsProcess{}
		fmt.Scanf("%d\n", &key)
		switch key {
		case 1:
			fmt.Println("显示在线用户列表")
			outpuOnlineUser()
		case 2:
			fmt.Println("你想对大家说什么:")
			fmt.Scanf("%s\n", &content)
			smsProcess.SendGroupMes(content)
3.2 服务器接收群发消息,并发送消息(发送者除外)

思路

1.在服务器端接收到SmsMes消息

2.在server/process/smsProcess.go文件增加群发消息的方法

3.在客户端还要增加去处理服务器转发的群发消息

代码实现

在server/process/smsProcess.go中添加

package process

import (
	"encoding/json"
	"fmt"
	"net"
	"project/common/message"
	"project/common/utils"
)

type SmsProcess struct {
}

func (this *SmsProcess) SendGroupMes(mes *message.Message) {
	var smsMes message.SmsMes
	err := json.Unmarshal([]byte(mes.Data), &smsMes)
	if err != nil {
		fmt.Println("json.Unmarshal err = ", err)
		return
	}

	data, err := json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err = ", err)
		return
	}
	for id, up := range userMgr.onlineUsers {
		//过滤自己
		if id == smsMes.UserId {
			continue
		}
		this.SendMesToEachOnlineUser(data, up.Conn)
	}

}

func (this *SmsProcess) SendMesToEachOnlineUser(data []byte, conn net.Conn) {
	tf := &utils.Transfer{
		Conn: conn,
	}
	err := tf.WritePkg(data)
	if err != nil {
		fmt.Println("群发消息失败")
		return
	}
}

修改server/main/processor.go

func (this *Processor) ServerProcessMes(mes *message.Message) (err error) {
	switch mes.Type {
	case message.LoginMesType:
		//处理登录
		//创建UserPorcess实例
		up := &process2.UserProcess{
			Conn: this.Conn,
		}
		err = up.ServerProcessLogin(mes)
	case message.RegisterMesType:
		up := &process2.UserProcess{
			Conn: this.Conn,
		}
		err = up.ServerProcessRegister(mes)
	case message.SmsMesType:
		smsProcess := &process2.SmsProcess{}
		smsProcess.SendGroupMes(mes)
	default:
		fmt.Println("消息类型不存在,无法处理...")
	}
	return
}

新增client/process/smsMgr.go

package process

import (
	"encoding/json"
	"fmt"
	"project/common/message"
)

func outputGroupMes(mes *message.Message) {
	var smsMes message.SmsMes
	err := json.Unmarshal([]byte(mes.Data), &smsMes)
	if err != nil {
		fmt.Println("json.Unmarshal err = ", err.Error())
		return
	}

	//显示信息
	info := fmt.Sprintf("用户id:\t%d 对大家说:\t%s", smsMes.UserId, smsMes.Content)
	fmt.Println(info)
	fmt.Println()
}

修改client/process/server.go

		//如果读到消息,进入下一步处理逻辑
		switch mes.Type {
		case message.NotifyUserStatusMesType: //有人上线
			//处理
			//1. 取出NotifyUserStatusMes
			var notifyUserStatusMes message.NotifyUserStatusMes
			json.Unmarshal([]byte(mes.Data), &notifyUserStatusMes)
			//2. 把这个用户信息,状态保存到客户map[int]User中
			updateUserStatus(&notifyUserStatusMes)
		case message.SmsMesType: //有人群发消息
			outputGroupMes(&mes)
		default:
			fmt.Println("服务器端返回了未知的消息类型")
		}
相关推荐
蒙娜丽宁2 天前
Go语言错误处理详解
ios·golang·go·xcode·go1.19
qq_172805592 天前
GO Govaluate
开发语言·后端·golang·go
littleschemer3 天前
Go缓存系统
缓存·go·cache·bigcache
程序者王大川4 天前
【GO开发】MacOS上搭建GO的基础环境-Hello World
开发语言·后端·macos·golang·go
Grassto4 天前
Gitlab 中几种不同的认证机制(Access Tokens,SSH Keys,Deploy Tokens,Deploy Keys)
go·ssh·gitlab·ci
高兴的才哥5 天前
kubevpn 教程
kubernetes·go·开发工具·telepresence·bridge to k8s
少林码僧5 天前
sqlx1.3.4版本的问题
go
蒙娜丽宁6 天前
Go语言结构体和元组全面解析
开发语言·后端·golang·go
蒙娜丽宁6 天前
深入解析Go语言的类型方法、接口与反射
java·开发语言·golang·go
三里清风_6 天前
Docker概述
运维·docker·容器·go