目录

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("服务器端返回了未知的消息类型")
		}
本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
chxii5 小时前
2.2goweb解析http请求信息
go
小刀飘逸13 小时前
部署go项目到linux服务器(简易版)
后端·go
我是前端小学生16 小时前
Go 语言中的 Channel 全面解析
go
IT杨秀才16 小时前
Go语言单元测试指南
后端·单元测试·go
Piper蛋窝21 小时前
Go 1.1 相比 Go1.0 有哪些值得注意的改动?
go
洛卡卡了2 天前
Go + Gin 优化动态定时任务系统:互斥控制、异常捕获与任务热更新
后端·go
洛卡卡了2 天前
Go + Gin 实现动态定时任务系统:从静态注册到动态调度与日志记录
后端·go
你怎么知道我是队长2 天前
Go语言类型捕获及内存大小判断
go
楽码2 天前
只需一文!深入理解闭包的实现
后端·go·编程语言
豆浆Whisky2 天前
深入剖析Go Channel:从底层原理到高阶避坑指南|Go语言进阶(5)
后端·go