Go语言学习笔记【9】 实现聊天系统[三](B站视频)

【声明】

非完全原创,主要思路来自于B站视频。如果有侵权,请联系我,可以立即删除掉。

一、其他功能的实现思路

1、实现群发、私发消息

为了方便,可以将群发、私聊消息的结构体共用,主要需要包含的字段应当有:发送用户id、用户状态、发送的消息、接收用户id。前面已经定义了notify结构体(包含用户id、用户状态),此时定义ChatMsg可以继承自notify。在服务端,通过接收用户id来区分群发还是私聊,接收用户id为0时表示群聊,其他值表示私聊。
注意:
(1)为了减轻服务端的压力,客户端在私聊消息前,需要先根据好友在线列表来检测用户是否在线,不在线则不发送
(2)服务端在解析时,群发是直接将消息发送给所有在线用户;私聊则根据用户id单独发给指定id的用户

2、设置当前用户状态

在工具包中定义几种特定的状态
0: USER_OFFLINE, 1: USER_BUSY, 2: USER_STUDY, 3: USER_WORKING, 9: USER_ONLINE
用户设置状态后,发送notify结构体的消息给服务端;服务端被动响应,将当前用户的最新状态群发给所有在线用户(包括当前用户),同时需要返回操作结果给该用户
其他用户收到该用户状态变化的通知消息之后,如果其维护的好友列表中没有该用户,则发送提醒用户xxx已上线,将其添加到在线好友列表中;如果该用户是下线,则发送提醒用户xxx已下线,并将其从在线好友列表中删除;其他情况则直接更新在线好友列表中该用户的状态

3、注销当前用户

使用该功能后,当前用户通知服务器自己即将注销,服务器收到消息后:
(1)调用redis函数将用户从数据库中删除
(2)将其从服务端的在线用户列表中删除
(3)将该用户下线的状态群发给其他在线用户
(4)返回注销的结果给客户端

二、整体的实现代码

1、工具包

1.1、结构体/变量/常量定义:utils/msg_def.go

package utils

const (
	ClientLoginMsg     = "LoginMsg"
	ServerReturnMsg    = "LoginReturn"
	UsrRegisterMsg     = "UsrRegister"
	ClientNotifyMsg    = "NotifyMsg"
	ClientGroupChatMsg = "GroupChatMsg"
	ClientPrivateChat  = "PrivateChatMsg"
	UsrLogoutMsg       = "UsrLogout"
)

const (
	USER_ONLINE  = "online"
	USER_OFFLINE = "offline"
	USER_BUSY    = "busy"
	USER_STUDY   = "study"
	USER_WORKING = "working"
)

var (
	GetUsrStatusByNo = map[int]string{
   0: USER_OFFLINE, 1: USER_BUSY, 2: USER_STUDY,
		3: USER_WORKING, 9: USER_ONLINE}
)

var (
	UsrNotExist     = LoginReturn{
   403, "user not exist", nil}
	UsrAlreadyExist = LoginReturn{
   402, "user already exist", nil}
	PwdNotMatch     = LoginReturn{
   401, "password not match", nil}
	HandleSuccess   = LoginReturn{
   200, "handle success", nil}
	RegisterSuccess = LoginReturn{
   201, "register success", nil}
	LoginSuccess    = LoginReturn{
   202, "login success", nil}
	LogoutSuccess   = LoginReturn{
   203, "logout success", nil}
)

type Message struct {
   
	MsgType string
	MsgData string
}

type UserInfoMsg struct {
   
	UsrId  int
	UsrPwd string
}

type LoginReturn struct {
   
	ErrCode   int
	ErrInfo   string
	OnlineIds []int
}

type NotifyMsg struct {
   
	UsrId     int
	UsrStatus string
}

type ChatMsg struct {
   
	NotifyMsg
	Content  string
	DstUsrId int
}

1.2、发送消息的序列化/接收消息的反序列:utils/msg_utils.go

package utils

import (
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
)

func ReadMsg(con *net.Conn) (msg *Message, err error) {
   
	//1. 读取前4个字节,即数据长度
	buf := make([]byte, 8096)
	_, err = (*con).Read(buf[:4])
	if err != nil {
   
		fmt.Printf("[%s] receive message length from [%s] failed, err = %v\n", (*con).LocalAddr(), (*con).RemoteAddr(), err)
		return
	}
	pkgLens := binary.BigEndian.Uint32(buf[:4])

	//2. 再读pkgLens个字节到buf中
	lens, err := (*con).Read(buf[:pkgLens])
	if lens != int(pkgLens) || err != nil {
   
		fmt.Printf("[%s] receive message data from [%s] failed, err = %v\n", (*con).LocalAddr(), (*con).RemoteAddr(), err)
		return
	}

	msg = &Message{
   }
	//3. 读到的数据反序列化为Message结构体
	err = json.Unmarshal(buf[:pkgLens], &msg)
	if err != nil {
   
		fmt.Printf("[%s] received message unmarshal failed, err = %v\n", (*con).LocalAddr(), err)
	}
	return
}

func SendMsg(con *net.Conn, buf []byte, msgType string) (err error) {
   
	//1. 根据服务端返回消息/客户端登录消息(如LoginReturn、LoginMsg)序列化的切片来创建消息Message
	var msg Message
	msg.MsgData = string(buf)
	msg.MsgType = msgType
	data, err := json.Marshal(&msg)
	if err != nil {
   
		fmt.Printf("[%s] message data unmarshal failed, err = %v\n", (*con).LocalAddr(), err)
		return
	}

	//2. 将Message序列化后的数据长度、内容发送给客户端
	//2.1 发送数据长度
	bytebuf := make([]byte, 4)
	binary.BigEndian.PutUint32(bytebuf, uint32(len(data)))
	lens, err := (*con).Write(bytebuf)
	//fmt.Printf("[%s] send message length to [%s] ", con.LocalAddr(), con.RemoteAddr())
	if lens != 4 || err != nil {
   
		fmt.Printf("failed, len = %d, err = %v\n", lens, err)
		return
	}
	//fmt.Printf("successful, len = %d, content = %+v\n", lens, bytebuf)
	//2.2 发送数据内容
	lens, err = (*con).Write(data)
	//fmt.Printf("[%s] send message data to [%s] ", con.LocalAddr(), con.RemoteAddr())
	if err != nil {
   
		fmt.Println("failed, len = ", lens, "err = ", err)
		return
	}
	//fmt.Printf("sucessful, len = %d, content = %+v\n", lens, string(data))
	return
}

2、客户端

2.1、主函数和一级目录:client/main/client.go

package main

import (
	"Test0/IMS/client/proc"
	"Test0/IMS/utils"
	"fmt"
)

func main() {
   
	//定义全局变量接收用户的序号选择、用户ID、密码
	var key, id int
	var pwd string
	for {
   
		fmt.Println("-------------欢迎来到简易及时通讯系统-------------")
		fmt.Println("\t\t 1. 用户登录")
		fmt.Println("\t\t 2. 用户注册")
		fmt.Println("\t\t 3. 退出系统")
		fmt.Printf("\t\t请选择(1~3): ")
		fmt.Scanln(&key)
		switch key {
   
		case 1:
			fmt.Printf("请输入用户ID: ")
			fmt.Scanln(&id)
			fmt.Printf("请输入用户密码: ")
			fmt.Scanln(&pwd)
			err := proc.Login_or_Register(id, pwd, utils.ClientLoginMsg)
			if err != nil {
   
				fmt.Printf("%s", err.Error())
				if err.Error() == utils.UsrNotExist.ErrInfo {
   
					fmt.Println(", 请先注册")
				} else if err.Error() == utils.PwdNotMatch.ErrInfo {
   
					fmt.Println(", 请先重新输入")
				} else {
   
					fmt.Println()
				}
			} 
		case 2:
			fmt.Printf("请输入用户ID: ")
			fmt.Scanln(&id)
			fmt.Printf("请输入用户密码: ")
			fmt.Scanln(&pwd)
			err := proc.Login_or_Register(id, pwd, utils.UsrRegisterMsg)
			if err != nil {
   
				fmt.Println(err.Error())
			} else {
   
				fmt.Println("register success")
			}
		case 3:
		default:
			fmt.Println("序号输入有误,请重新输入!")
		}
		if key == 1 || key == 2 || key == 3 {
   
			break
		}
	}
}

2.2、客户端的好友列表及方法:client/online/friend_online.go

package online

import (
	"Test0/IMS/utils"
	"errors"
	"fmt"
)

var clientOnlineList map[int]*utils.NotifyMsg

func InitOnlineList() {
   
	if clientOnlineList == nil {
   
		clientOnlineList = make(map[int]*utils.NotifyMsg, 10)
	}
}

func UpdateUsrStatus(notify *utils.NotifyMsg) {
   
	InitOnlineList()
	if notify.UsrStatus != utils.USER_OFFLINE {
   
		clientOnlineList[notify.UsrId] = notify
	} else {
   
		delete(clientOnlineList, notify.UsrId)
	}
}

func ShowAllClientOnlineUsr() {
   
	fmt.Println("当前在线的用户列表:")
	for id, usr := range clientOnlineList {
   
		fmt.Println("用户id: ", id, "状态: ", usr.UsrStatus)
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言学习笔记.pdf》是一本关于Go语言学习学习笔记,内容丰富且简洁明了。本书从基础知识开始,逐步介绍了Go语言的语法、特性和常用库函数等。在学习笔记中,作者通过实际的示例和练习帮助读者理解Go语言的概念和用法。 第一章介绍了Go语言的起源和发展,为读者提供了对Go语言背景的整体了解。第二章讲解了Go语言的基本语法,例如变量声明、循环和条件语句等。通过大量的代码示例,读者能够更好地理解Go语言的语法和结构。 接下来的章节重点介绍了Go语言的并发编程和高级特性。第章详细介绍了Go语言中的goroutine和channel,这是Go语言并发编程的核心机制。作者通过生动的示例代码和实际应用案例,向读者展示了如何使用goroutine和channel实现并发编程。 第四章和第五章分别介绍了Go语言中的面向对象编程和函数式编程。通过深入讲解Go语言中的结构体、接口和函数,读者能够更好地应用这些特性进行代码设计和开发。 最后几章则介绍了Go语言中常用的库函数和工具。例如,第六章介绍了Go语言中用于网络编程的net包和http包。读者可以学习到如何使用这些库函数构建基于网络的应用程序。 总的来说,《Go语言学习笔记.pdf》是一本非常实用的Go语言学习资料。通过阅读这本书,读者能够系统学习和理解Go语言的基本概念和高级特性,为之后的Go语言开发打下坚实的基础。无论是初学者还是有一定编程经验的开发者,都能从中获得丰富的知识和经验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值