当前位置: 首页 > news >正文

2级a做爰片免费网站/西安网站定制开发

2级a做爰片免费网站,西安网站定制开发,武汉做网站的培训机构,创建网站向导和模板文章目录 二十三、海量用户即时通讯系统1、项目开发前技术准备2.实现功能-显示客户端登录菜单3.实现功能-完成用户登录-1.完成客户端可以该长度值发送消息长度,服务器端可以正常接收到-2.完成客户端可以发送消息,服务器端可以接收到消息并根据客户端发送…

文章目录

    • 二十三、海量用户即时通讯系统
      • 1、项目开发前技术准备
      • 2.实现功能-显示客户端登录菜单
      • 3.实现功能-完成用户登录
        • -1.完成客户端可以该长度值发送消息长度,服务器端可以正常接收到
        • -2.完成客户端可以发送消息,服务器端可以接收到消息并根据客户端发送的消息判断用户的合法性并返回相应的消息
        • -3.能够完成登录,并提示信息
        • -4.程序结构的改进
          • 1)画出程序框架图
          • 2)步骤
            • server层后端项目结构图
            • client层后端项目结构图
        • -5.应用redis
          • 1)在Redis手动添加测试用户,并画图+说明注意(后面通过程序注册用户)
          • 2)如输入的用户名密码正确在Redis中存在则登录,否则退出系统,并给出相应的提示信息
          • 3)代码实现
            • (1)先编写了server/model/user.go
            • (2)先编写了server/model/error.go
            • (3)编写了server/model/userDao.go
            • (4)编写了server/main.redis.go
            • (5)编写了server/process/userProcess.go改进登录方式以及错误类型
            • (6)改进server/main/main.go(加了一个初始化redis连接池的函数)
      • 4.完成用户注册操作
        • 1)要求
        • 2)具体代码
          • (1)common/message/user.go(从server/model下复制过来的。记住要复制而不是剪切还要改包名)
          • (2)common/message/message.go增加了关于注册消息的代码
          • (3)server/process/userProcess(增加了一个方法)
          • (4)server/model/userDao(增加了一个Register方法对数据库进行添加的操作)
          • (5)在client/main/main.go进行了调用操作
          • (6)client/process/userProcess.go(添加一个Register的方法)
      • 5.实现功能-完成登录时能返回当前在线用户
        • 3)代码实现
          • (1)编写了server/process/userMgr.go
          • (2)server/process/userProcess.go(在login成功的地方加入代码)
        • 4)当一个新的用户上线后,其他已经登录的用户也能获取最新在新用户列表
      • 6.完成登录可以完成群聊操作
        • -1.步骤1 :
          • 1)思路分析
          • 2)代码实现
        • -2.步骤2.
          • 1)思路分析
          • 2)代码实现
          • 3)拓展功能要求

二十三、海量用户即时通讯系统

项目展示
在这里插入图片描述
开始此项目之前请确保安装好redis,golang
源码下载:https://github.com/BeAlrightc/go-study.git

1、项目开发前技术准备

项目要保存用户信息和信息数据,因此我们需要学习数据(redis或者mysql),这里我们选择redis

2.实现功能-显示客户端登录菜单

在这里插入图片描述

代码编写

clien包下的main.go

package main
import ("fmt""os"
)//定义两个变量,一个表示用户的id,一个表示用户的密码
var userId int
var userPwd stringfunc main() {//接收用户的选择var key int//判断是否还继续显示菜单var loop = truefor loop{fmt.Println("-----------欢迎登录多人聊天系统------")fmt.Println("\t\t\t 1 登录聊天室")fmt.Println("\t\t\t 2 注册用户")fmt.Println("\t\t\t 3 退出系统")fmt.Println("\t\t\t 请选择 1-3:")fmt.Scanf("%d\n",&key)switch key {case 1 :fmt.Println("登录聊天室")loop=falsecase 2 :fmt.Println("注册用户")	loop=falsecase 3 :fmt.Println("退出系统")	//loop=falseos.Exit(0)default:fmt.Println("输入有误,请输入1-3")	}}//根据用户的输入,显示新的提示信息if key ==1 {//说明用户要登录了fmt.Println("请输入用户的id")fmt.Scanf("%d\n",&userId)fmt.Println("请输入用户的密码")fmt.Scanf("%s\n",&userPwd)//先把登录函数,写到另外一个文件,先写login.goerr := login(userId,userPwd)if err != nil {fmt.Println("登录失败")}else {fmt.Println("登录成功")}}else if key ==2 {fmt.Println("进行用户注册的逻辑....")}}

clien包下的login.go

package main
import ("fmt"
)
//写一个函数,完成登录操作
func login(userId int,userPwd string) (err error) {//下一个就要开始定协议fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)return nil
}

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

要求:完成指定用户的验证,用户id=100,密码pwd=123456可以登录,其他用户不能登录

理解从client到server中的程序执行流程,如图所示【Message组成的示意图。并发送一个message的流程介绍】

在这里插入图片描述

-1.完成客户端可以该长度值发送消息长度,服务器端可以正常接收到

分析思路

1)先确定消息Message的格式

2)发送消息示意图

在这里插入图片描述

代码展示

sever

main.go

package main
import ("fmt""net"
)//处理和客户端的通讯
func process(conn net.Conn){//这里需要延时关闭defer conn.Close()//循环地读客户端发送的信息for {buf := make([]byte,8096)fmt.Println("读取客户端发送的数据...")n, err :=conn.Read(buf[:4])if n != 4 || err !=nil {fmt.Println("conn.Read err=",err)return}fmt.Println("独到的buf=",buf[:4])}}
func main() {//提示信息fmt.Println("服务器在8889端口监听....")listen, err := net.Listen("tcp","0.0.0.0:8889")defer listen.Close()if err != nil {fmt.Println("net.Listen err=",err)return} //一旦监听成功,就等待客户端来连接服务器for {fmt.Println("等待客户端来连接服务器")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=",err)} //一旦连接成功,则则启动一个协程和客户端保持通讯。。go process(conn)}
}

common层的message

message.go

package messageconst (LoginMesType   = "LoginMes"LoginResMesType  = "LoginResMes"
)type Message struct {Type string `json:"type"`//消息的类型Data string `json:"data"`//消息的数据
}//定义两个消息。。后面需要再添加
type LoginMes struct {UserId int `json:"userId"`//用户IdUserPwd string `json:"userPwd"`//用户密码UserName string `json:"userName"`//用户名
}type LoginResMes struct {Code int `json:"code"`//返回状态码 500表示该用户未注册 200表示登录成功Error string `json:"error"`//返回错误信息
}

client层

login.go

package main
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message"
)
//写一个函数,完成登录操作
func login(userId int,userPwd string) (err error) {//下一个就要开始定协议// fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)// return nil//1.连接到服务器端conn, err :=net.Dial("tcp","localhost:8889")if err != nil {fmt.Println("net.Dial err=",err)return}//延时关闭defer conn.Close()//2.准备通过conn发送消息给服务器var mes message.Messagemes.Type = message.LoginMesType //3.创建一个LoginMes 结构体var loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd //4.将loginMes序列化data, err :=json.Marshal(loginMes)if err != nil {fmt.Println("json.Mashal err=",err)return}//5.将data赋给了mes.Data字段mes.Data = string(data)//6.将mes进行序列化data, err =json.Marshal(mes)if err != nil {fmt.Println("json.Mashal err=",err)return}//7.到这个时候,data就是我们要发送的消息//7.1先把data的长度发送给服务器//先获取data的长度->转成一个表示长度的byte切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //将该、长度转成了byte类型是数据//发送长度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}fmt.Printf("客户端发送数据的消息长度=%d 内容是=%s",len(data),string(data))return}

main.go

package main
import ("fmt""os"
)//定义两个变量,一个表示用户的id,一个表示用户的密码
var userId int
var userPwd stringfunc main() {//接收用户的选择var key int//判断是否还继续显示菜单var loop = truefor loop{fmt.Println("-----------欢迎登录多人聊天系统------")fmt.Println("\t\t\t 1 登录聊天室")fmt.Println("\t\t\t 2 注册用户")fmt.Println("\t\t\t 3 退出系统")fmt.Println("\t\t\t 请选择 1-3:")fmt.Scanf("%d\n",&key)switch key {case 1 :fmt.Println("登录聊天室")loop=falsecase 2 :fmt.Println("注册用户")	loop=falsecase 3 :fmt.Println("退出系统")	//loop=falseos.Exit(0)default:fmt.Println("输入有误,请输入1-3")	}}//根据用户的输入,显示新的提示信息if key ==1 {//说明用户要登录了fmt.Println("请输入用户的id")fmt.Scanf("%d\n",&userId)fmt.Println("请输入用户的密码")fmt.Scanf("%s\n",&userPwd)//先把登录函数,写到另外一个文件,先写login.goerr := login(userId,userPwd)if err != nil {fmt.Println("登录失败")}else {fmt.Println("登录成功")}}else if key ==2 {fmt.Println("进行用户注册的逻辑....")}}
-2.完成客户端可以发送消息,服务器端可以接收到消息并根据客户端发送的消息判断用户的合法性并返回相应的消息

思路分析

1)让客户端发送消息本身

2)服务器端接收到消息,然后反序列化成对应的消息结构体

3)服务器端根据反序列化的消息,判断是否登录用户是合法,返回LoginReMes

4)客户端解析返回的LoginReMes,显示对应界面

5)这里我们需要做一些函数的封装

cient/login.go在结尾添加这些coding

//发送消息本身_, err = conn.Write(data)if err !=nil {fmt.Println("connWrite(data) fail ",err)return}//休眠20秒time.Sleep(10 * time.Second)fmt.Println("休眠了20秒..")//这里还需要处理服务器端返回的消息return

在server/main.go中我们做了以下改动

将读数据的过程封装了一个函数

package main
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message"//"errors""io"
)
func readPkg(conn net.Conn)(mes message.Message,err error){buf := make([]byte,8096)fmt.Println("读取客户端发送的数据...")//conn.Read()只有在conn没有被关闭的情况下,才会阻塞//如果客户端关闭conn则,就不会阻塞_, err =conn.Read(buf[:4]) //read出buf中的数据if err !=nil {//fmt.Println("conn.Read err=",err)//err = errors.New("read pkg header error")return}//根据buf[:4]转成uint32类型var pkgLen uint32pkgLen=binary.BigEndian.Uint32(buf[0:4])//根据pkgLen读取消息内容n, err :=conn.Read(buf[:pkgLen])if n != int(pkgLen) || err !=nil {//err = errors.New("read pkg body error")return}//把pkgLen 反序列化成 -->message.Message//技术就是一层窗户纸json.Unmarshal(buf[:pkgLen],&mes)if err != nil {fmt.Println("json.Unmarshal err=",err)return}return
}//处理和客户端的通讯
func process(conn net.Conn) {//这里需要延时关闭defer conn.Close()//循环地读客户端发送的信息for {//这里我们将读取数据包,直接封装成一个函数readPkg(),返回Message,Errmes, err :=readPkg(conn)if err != nil {if err == io.EOF {fmt.Println("客户端退出,服务器端也退出...")return}else {fmt.Println("readpkg err=",err)}return}fmt.Println("mes=",mes)}
}
//main函数下的则没有改变
func main() {//提示信息fmt.Println("服务器在8889端口监听....")listen, err := net.Listen("tcp","0.0.0.0:8889")defer listen.Close()if err != nil {fmt.Println("net.Listen err=",err)return} //一旦监听成功,就等待客户端来连接服务器for {fmt.Println("等待客户端来连接服务器")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=",err)} //一旦连接成功,则则启动一个协程和客户端保持通讯。。go process(conn)}
}
-3.能够完成登录,并提示信息

server/main.go

添加了发送信息给客户端的代码
func writePkg(conn net.Conn,data []byte)(err error) {//先发送一个长度给对方var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //将该、长度转成了byte类型是数据//发送长度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}//发送data本身n, err = conn.Write(data)if n != int(pkgLen) || err !=nil {fmt.Println("connWrite(data) fail ",err)return}return}//编写一个函数serverProcessLogin函数,专门处理登录请求
func serverProcessLogin(conn net.Conn,mes *message.Message)(err error){//核心代码//1.先从mes中取出mes.Data,并直接反序列化成LoginMesvar loginMes message.LoginMeserr =json.Unmarshal([]byte(mes.Data),&loginMes)if err != nil {fmt.Println("json.Unmarshal fail err=",err)return}//1.先声明一个resMesvar resMes message.MessageresMes.Type=message.LoginResMesType//2.再声明一个LoginResMesvar loginResMes message.LoginResMes//如果用户的id=100,密码=123456认为合法,否则不合法if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {//合法loginResMes.Code = 200} else {//不合法loginResMes.Code = 500  //500状态码表示用户不存在loginResMes.Error = "该用户不存在,请注册再使用。。。"}//3.将loginResMes 序列化data, err := json.Marshal(loginResMes)if err != nil {fmt.Println("Marshal fail err=",err)}//4.将data赋值给resMesresMes.Data = string(data) //5.对resMes进行序列化,准备发送data, err = json.Marshal(resMes)if err != nil {fmt.Println("Marshal fail err=",err)return}//6.发送data 我们将其封装为writePkgerr = writePkg(conn,data)return}//编写一个ServerProcessMes函数
//功能 :根据客户端发送的消息种类不同,决定调用哪个函数处理
func serverProcessMes(conn net.Conn,mes *message.Message)(err error) {switch mes.Type {case message.LoginMesType ://处理登录的逻辑err = serverProcessLogin(conn,mes)case message.RegisterMesType ://处理注册default :fmt.Println("消息类型不存在,无法处理...")}return
}在process进行了改定
//处理和客户端的通讯
func process(conn net.Conn) {//这里需要延时关闭defer conn.Close()//循环地读客户端发送的信息for {//这里我们将读取数据包,直接封装成一个函数readPkg(),返回Message,Errmes, err :=readPkg(conn)if err != nil {if err == io.EOF {fmt.Println("客户端退出,服务器端也退出...")return}else {fmt.Println("readpkg err=",err)}return}//增加了这段代码进行调用这个函数err = serverProcessMes(conn,&mes)if err != nil {return}}
}

client/utils(增加了一个utils.go用于read的write的操作)

package main
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message"
)func readPkg(conn net.Conn)(mes message.Message,err error){buf := make([]byte,8096)fmt.Println("读取客户端发送的数据...")//conn.Read()只有在conn没有被关闭的情况下,才会阻塞//如果客户端关闭conn则,就不会阻塞_, err =conn.Read(buf[:4]) //先读取之前发送的数据长度if err !=nil {//fmt.Println("conn.Read err=",err)//err = errors.New("read pkg header error")return}//根据buf[:4]转成uint32类型var pkgLen uint32pkgLen=binary.BigEndian.Uint32(buf[0:4])//根据pkgLen(data数据的长度)读取消息内容n, err :=conn.Read(buf[:pkgLen])if n != int(pkgLen) || err !=nil {//err = errors.New("read pkg body error")return}//把pkgLen 反序列化成 -->message.Message//技术就是一层窗户纸json.Unmarshal(buf[:pkgLen],&mes)if err != nil {fmt.Println("json.Unmarshal err=",err)  //json的反序列化失败!return}return
}func writePkg(conn net.Conn,data []byte)(err error) {//先发送一个长度给对方var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //将该、长度转成了byte类型是数据//发送长度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}//发送data本身n, err = conn.Write(data)if n != int(pkgLen) || err !=nil {fmt.Println("connWrite(data) fail ",err)return}return}

client/login.go

//在末尾加入了如下的代码
//这里还需要处理服务器端返回的消息mes, err = readPkg(conn) //mes 就是if err != nil {fmt.Println("readPkg(conn) err=",err)return}//将mes的Data部分反序列化为LoginResMesvar loginResMes message.LoginResMeserr = json.Unmarshal([]byte(mes.Data),&loginResMes)if loginResMes.Code == 200 {fmt.Println("登录成功")}else if loginResMes.Code == 500 {fmt.Println(loginResMes.Error)}return}
-4.程序结构的改进

说明:前面的程序虽然完成了功能,但是没有结构,系统的可读性、拓展性和维护性都不好,因此需要对程序的结构进行改进

1)画出程序框架图

在这里插入图片描述

2)步骤

(1)先把分析出来的文件,创建好,然后放到相应的文件夹中

server层后端项目结构图

在这里插入图片描述

(2)现在根据各个文件完成的任务和作用不同,将main.go的代码剥离到对应的文件即可

(3)先修改了utils.go

package utilsimport ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message")//将这些方法关联到结构体当中type Transfer struct {//分析应该有哪些字段Conn net.ConnBuf [8096]byte  //这是传输时使用缓冲}func (this *Transfer) ReadPkg()(mes message.Message,err error){fmt.Println("读取客户端发送的数据...")//conn.Read()只有在conn没有被关闭的情况下,才会阻塞//如果客户端关闭conn则,就不会阻塞_, err =this.Conn.Read(this.Buf[:4]) //先读取之前发送的数据长度if err !=nil {//fmt.Println("conn.Read err=",err)//err = errors.New("read pkg header error")return}//根据buf[:4]转成uint32类型var pkgLen uint32pkgLen=binary.BigEndian.Uint32(this.Buf[0:4])//根据pkgLen(data数据的长度)读取消息内容n, err :=this.Conn.Read(this.Buf[:pkgLen])if n != int(pkgLen) || err !=nil {//err = errors.New("read pkg body error")return}//把pkgLen 反序列化成 -->message.Message//技术就是一层窗户纸json.Unmarshal(this.Buf[:pkgLen],&mes)if err != nil {fmt.Println("json.Unmarshal err=",err)  //json的反序列化失败!return}return
}func (this *Transfer) WritePkg(data []byte)(err error) {//先发送一个长度给对方var pkgLen uint32pkgLen = uint32(len(data))binary.BigEndian.PutUint32(this.Buf[0:4],pkgLen) //将该、长度转成了byte类型是数据//发送长度n, err := this.Conn.Write(this.Buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(this.Buf) fail ",err)return}//发送data本身n, err = this.Conn.Write(data)if n != int(pkgLen) || err !=nil {fmt.Println("connWrite(data) fail ",err)return}return}

(4)修改了process2/userProcess.go

package process2
import ("fmt""net""encoding/json""go_code/chatroom/common/message""go_code/chatroom/server/utils"
)type UserProcess struct {//字段Conn net.Conn
}//编写一个函数serverProcessLogin函数,专门处理登录请求
func (this *UserProcess) ServerProcessLogin(mes *message.Message)(err error){//核心代码//1.先从mes中取出mes.Data,并直接反序列化成LoginMesvar loginMes message.LoginMeserr =json.Unmarshal([]byte(mes.Data),&loginMes)if err != nil {fmt.Println("json.Unmarshal fail err=",err)return}//1.先声明一个resMesvar resMes message.MessageresMes.Type=message.LoginResMesType//2.再声明一个LoginResMesvar loginResMes message.LoginResMes//如果用户的id=100,密码=123456认为合法,否则不合法if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {//合法loginResMes.Code = 200} else {//不合法loginResMes.Code = 500  //500状态码表示用户不存在loginResMes.Error = "该用户不存在,请注册再使用。。。"}//3.将loginResMes 序列化data, err := json.Marshal(loginResMes)if err != nil {fmt.Println("Marshal fail err=",err)}//4.将data赋值给resMesresMes.Data = string(data) //5.对resMes进行序列化,准备发送data, err = json.Marshal(resMes)if err != nil {fmt.Println("Marshal fail err=",err)return}//6.发送data 我们将其封装为writePkg//因为使用了分层模式(mvc),我们先创建一个Transfer实例,然后读取tf := &utils.Transfer{Conn : this.Conn,}err = tf.WritePkg(data)return
}

(5)修改了main/processor.go

package main
import ("fmt""net""go_code/chatroom/common/message""go_code/chatroom/server/utils""go_code/chatroom/server/process""io"
)//先创建一个Processor的结构体
type Processor struct {Conn net.Conn
}//编写一个ServerProcessMes函数
//功能 :根据客户端发送的消息种类不同,决定调用哪个函数处理
func (this *Processor) serverProcessMes(mes *message.Message)(err error) {switch mes.Type {case message.LoginMesType ://处理登录的逻辑//创建一个UserProcess实例up := &process2.UserProcess{Conn : this.Conn,}err = up.ServerProcessLogin(mes)case message.RegisterMesType ://处理注册default :fmt.Println("消息类型不存在,无法处理...")}return
}func (this *Processor) process2()(err error){//循环地读客户端发送的信息for {//这里我们将读取数据包,直接封装成一个函数readPkg(),返回Message,Err//创建一个Transfer实例完成读包任务tf := &utils.Transfer{Conn : this.Conn,}mes, err :=tf.ReadPkg()if err != nil {if err == io.EOF {fmt.Println("客户端退出,服务器端也退出...")return err}else {fmt.Println("readpkg err=",err)}return err}err = this.serverProcessMes(&mes)if err != nil {return err}}
}

修改了main/main.go

package main
import ("fmt""net"
)//处理和客户端的通讯
func process(conn net.Conn) {//这里需要延时关闭defer conn.Close()//这里调用总控,创建一个processor实例processor := &Processor{Conn : conn,}err := processor.process2()if err != nil {fmt.Println("客户端和服务器通讯的协程错误=err",err)return }
}
func main() {//提示信息fmt.Println("服务器[新的结构]在8889端口监听....")listen, err := net.Listen("tcp","0.0.0.0:8889")defer listen.Close()if err != nil {fmt.Println("net.Listen err=",err)return} //一旦监听成功,就等待客户端来连接服务器for {fmt.Println("等待客户端来连接服务器")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=",err)} //一旦连接成功,则则启动一个协程和客户端保持通讯。。go process(conn)}
}

修改客户端。先画出程序的框架图,再写代码

client层后端项目结构图

在这里插入图片描述

(2)先把各个文件放到对应的文件夹[包]

在这里插入图片描述

(3)将server/utils.go拷贝到client/utils/utils.go

(4)创建了client/process/userProcess.go

package process
import ("fmt""net""encoding/json""encoding/binary""go_code/chatroom/common/message""go_code/chatroom/client/utils"
)
type UserProcess struct {//暂时不需要字段
}
//给关联一个用户登录的方法
//写一个函数,完成登录操作
func (this *UserProcess) Login(userId int,userPwd string) (err error) {//下一个就要开始定协议// fmt.Printf("userId = %d userPwd = %s\n",userId,userPwd)// return nil//1.连接到服务器端conn, err :=net.Dial("tcp","localhost:8889")if err != nil {fmt.Println("net.Dial err=",err)return}//延时关闭defer conn.Close()//2.准备通过conn发送消息给服务器var mes message.Messagemes.Type = message.LoginMesType //3.创建一个LoginMes 结构体var loginMes message.LoginMesloginMes.UserId = userIdloginMes.UserPwd = userPwd //4.将loginMes序列化data, err :=json.Marshal(loginMes)if err != nil {fmt.Println("json.Mashal err=",err)return}//5.将data赋给了mes.Data字段mes.Data = string(data)//6.将mes进行序列化data, err =json.Marshal(mes)if err != nil {fmt.Println("json.Mashal err=",err)return}//7.到这个时候,data就是我们要发送的消息//7.1先把data的长度发送给服务器//先获取data的长度->转成一个表示长度的byte切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //将该、长度转成了byte类型是数据//发送长度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}//fmt.Printf("客户端发送数据的消息长度=%d 内容是=%s",len(data),string(data))//发送消息本身_, err = conn.Write(data)if err !=nil {fmt.Println("connWrite(data) fail ",err)return}//休眠20秒// time.Sleep(10 * time.Second)// fmt.Println("休眠了20秒..")//这里还需要处理服务器端返回的消息//创建一个Transfer实例tf := &utils.Transfer{Conn : conn,}mes, err = tf.ReadPkg() //mes 就是if err != nil {fmt.Println("readPkg(conn) err=",err)return}//将mes的Data部分反序列化为LoginResMesvar loginResMes message.LoginResMeserr = json.Unmarshal([]byte(mes.Data),&loginResMes)if loginResMes.Code == 200 {//fmt.Println("登录成功")//这里我们还需要再客户端启动一个协程//该协程保持和服务器端的通讯,如果服务器有数据推送给客户端//则可以接受并显示在客户端的终端go serverProcessMes(conn)//1.显示登录成功后的菜单[循环显示]for {ShowMenu()}}else if loginResMes.Code == 500 {fmt.Println(loginResMes.Error)}return
}

说明:该文件就是在原来login.go做了一个改进,封装到userProcess结构体

(5)创建了server/process/server.go

package process
import ("fmt""os""go_code/chatroom/client/utils""net"
)//显示登录后的界面..
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("显示用户在线列表")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.Printf("客户端正在等待读取服务器发送的消息")mes, err:=tf.ReadPkg()if err != nil {fmt.Println("tf.ReadPkg err=",err)return}//如果读取到消息,又是下一步处理逻辑fmt.Printf("mes=%v",mes)}}

(6)client/main/main.go

package main
import ("fmt""os""go_code/chatroom/client/process"
)//定义两个变量,一个表示用户的id,一个表示用户的密码
var userId int
var userPwd stringfunc main() {//接收用户的选择var key int//判断是否还继续显示菜单// loop = truefor true{fmt.Println("-----------欢迎登录多人聊天系统------")fmt.Println("\t\t\t 1 登录聊天室")fmt.Println("\t\t\t 2 注册用户")fmt.Println("\t\t\t 3 退出系统")fmt.Println("\t\t\t 请选择 1-3:")fmt.Scanf("%d\n",&key)switch key {case 1 :fmt.Println("登录聊天室")fmt.Println("请输入用户的id")fmt.Scanf("%d\n",&userId)fmt.Println("请输入用户的密码")fmt.Scanf("%s\n",&userPwd)//完成登录//1.创建一个UserProcess的实例up :=&process.UserProcess{}up.Login(userId,userPwd)//loop=falsecase 2 :fmt.Println("注册用户")	//loop=falsecase 3 :fmt.Println("退出系统")	//loop=falseos.Exit(0)default:fmt.Println("输入有误,请输入1-3")	}}
}
-5.应用redis
1)在Redis手动添加测试用户,并画图+说明注意(后面通过程序注册用户)

在这里插入图片描述

手动直接在redis增加一个用户信息

在这里插入图片描述

2)如输入的用户名密码正确在Redis中存在则登录,否则退出系统,并给出相应的提示信息
  • 1.用户不存在,你也可以重新注册,再登录
  • 2.你的密码不正确
3)代码实现
(1)先编写了server/model/user.go
package model//定义一个用户的结构体
type User struct {//确定字段信息//为了序列化和反序列化成功//用户信息的json字符串与结构体字段对应的Tag名字一致UserId int `json:"userId"`UserPwd string `json:"userPwd"`UserName string `json:"userName"`
}
(2)先编写了server/model/error.go
package model
import ("errors"
)//根据业务逻辑的需要,自定义一些错误var (ERROR_USER_NOTEXIST = errors.New("用户不存在。。")ERROR_USER_EXIST = errors.New("用户已存在。。")ERROR_USER_PWD = errors.New("密码错误"))
(3)编写了server/model/userDao.go
package model
import ("fmt""github.com/garyburd/redigo/redis""encoding/json"
)//我们在服务器启动后,就初始化一个UserDao实例
//把它做成全局的变量,在需要和redis操作时,就直接使用即可
var (MyUserDao *UserDao
)
//定义一个UserDao结构体
//完成对User 结构体的各种操作type UserDao struct {pool *redis.Pool 
}//使用工厂模式创建一个UserDao实例
func NewUserDao(pool *redis.Pool) (userDao *UserDao){userDao = &UserDao{pool:pool,}return
}//写方法,应该提供哪个方法呢
//1,根据用户id返回一个User实例+err
func (this *UserDao) getUserById(conn redis.Conn,id int) (user *User,err error) {//通过给定的id去redis去查询用户res,err := redis.String(conn.Do("HGet","users",id))if err != nil {//错误if err == redis.ErrNil {//表示在users中没有找到对应的iderr= ERROR_USER_NOTEXIST}return}user = &User{}//这里我们需要反序列化成一个User实例err = json.Unmarshal([]byte(res),user)if err != nil {fmt.Println("json.Unmarshal Err=",err)return}return}//完成登录的校验 Login
//1.Login 完成对用户的验证
//2.如果用户的id和pwd都正确,则返回一个User实例
//3.如果用户的id和pwd有错误,则返回对应的错误信息func (this *UserDao)Login(userId int,userPwd string)(user *User,err error){//先从UserDao链接池中取出一根连接conn := this.pool.Get()defer conn.Close()user,err = this.getUserById(conn,userId)if err != nil {return}//这时证明用户是获取到了if user.UserPwd != userPwd {err = ERROR_USER_PWDreturn}return
}
(4)编写了server/main.redis.go
package main
import ("github.com/garyburd/redigo/redis""time")//定义一个全局的pool
var pool *redis.Poolfunc initPool(address string,maxIdle,maxActive int,idleTimeout time.Duration) {pool = &redis.Pool{MaxIdle: maxIdle, //最大空闲连接数MaxActive: maxActive,//表示和数据库的最大连接数,0表示没有限制IdleTimeout: idleTimeout,//最大空闲时间Dial:func()(redis.Conn,error){//初始化连接的代码。连接哪个ipreturn redis.Dial("tcp",address)},}
}
(5)编写了server/process/userProcess.go改进登录方式以及错误类型
//我们需要到redis数据库去完成验证//1.使用model.MyUserDao到redis去验证user, err := model.MyUserDao.Login(loginMes.UserId,loginMes.UserPwd)if err != nil {if err ==model.ERROR_USER_NOTEXIST {loginResMes.Code = 500loginResMes.Error = err.Error()}else if err ==model.ERROR_USER_PWD {loginResMes.Code = 403loginResMes.Error = err.Error()}else {loginResMes.Code = 505loginResMes.Error = "服务器内部错误..."}//这里我们先测试成功,然后再返回具体的错误信息}else{loginResMes.Code = 200fmt.Println(user,"登录成功")}
(6)改进server/main/main.go(加了一个初始化redis连接池的函数)
func init(){//当服务器启动时,我们就去初始化我们的redis的连接池initPool("localhost:6379",16,0,300 * time.Second)initUserDao()
}//这里我们编写一个函数完成对UserDao的初始化任务
func initUserDao() {//这里的pool本身就是一个全局的变量//这里需要注意一个初始化的顺序问题//initPool,在initUserDaomodel.MyUserDao =model.NewUserDao(pool)
}

4.完成用户注册操作

1)要求

完成注册功能,将用户信息录入到Redis中

思路分析,并完成代码

思路分析的示意图

2)具体代码
(1)common/message/user.go(从server/model下复制过来的。记住要复制而不是剪切还要改包名)
package message// User 定义一个用户的结构体
type User struct {//确定字段信息//为了序列化和反序列化成功//用户信息的json字符串与结构体字段对应的Tag名字一致UserId   int    `json:"userId"`UserPwd  string `json:"userPwd"`UserName string `json:"userName"`
}
(2)common/message/message.go增加了关于注册消息的代码
type RegisterMes struct {User User `json:"user"` //类型就是User结构体}
type RegisterResMes struct {Code int `json:"code"` //返回状态码400表示该用户已经占用 200表示登录注册成功Error string `json` //返回错误信息
}
(3)server/process/userProcess(增加了一个方法)
func (this *UserProcess) ServerProcessRegister(mes *message.Message) (err error){//1.先从mes中取出mes.Data,并直接反序列化成RegisterMesvar registerMes message.RegisterMeserr = json.Unmarshal([]byte(mes.Data), &registerMes)if err != nil {fmt.Println("json.Unmarshal fail err=", err)return}//1.先声明一个resMesvar resMes message.MessageresMes.Type = message.RegisterResMesType//2.再声明一个RegisterMesvar registerResMes message.RegisterResMes//我们需要到redis数据库去完成注册//1.使用model.MyUserDao到redis去注册err= model.MyUserDao.Register(&registerMes.User)if err !=nil {if err == model.ERROR_USER_EXISTS {registerResMes.Code = 505registerResMes.Error = model.ERROR_USER_EXISTS.Error()} else {registerResMes.Code = 506registerResMes.Error = "注册时发生未知错误"}} else {registerResMes.Code = 200}//3.将loginResMes 序列化data, err := json.Marshal(registerResMes)if err != nil {fmt.Println("Marshal fail err=", err)}//4.将data赋值给resMesresMes.Data = string(data)//5.对resMes进行序列化,准备发送data, err = json.Marshal(resMes)if err != nil {fmt.Println("Marshal fail err=", err)return}//6.发送data 我们将其封装为writePkg//因为使用了分层模式(mvc),我们先创建一个Transfer实例,然后读取tf := &utils.Transfer{Conn: this.Conn,}err = tf.WritePkg(data)return}
(4)server/model/userDao(增加了一个Register方法对数据库进行添加的操作)
func (this *UserDao)Register(user *message.User)(err error){//先从UserDao链接池中取出一根连接conn := this.pool.Get()defer conn.Close()_,err = this.getUserById(conn,user.UserId)if err == nil {err = ERROR_USER_EXISTSreturn}//这时说明id在redis还没有,则可以完成注册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}
(5)在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\n",&userName)//2.调用UserProcess,完成注册的请求up :=&process.UserProcess{}up.Register(userId,userPwd,userName)
(6)client/process/userProcess.go(添加一个Register的方法)
func (this *UserProcess) Register(userId int,userPwd string,userName string)(err error){//1.连接到服务器端conn, err :=net.Dial("tcp","localhost:8889")if err != nil {fmt.Println("net.Dial err=",err)return}//延时关闭defer conn.Close()//2.准备通过conn发送消息给服务器var mes message.Messagemes.Type = message.RegisterMesType//3.创建一个RegisterMes 结构体var registerMes message.RegisterMesregisterMes.User.UserId = userIdregisterMes.User.UserPwd = userPwd registerMes.User.UserName = userName//4.将registerMes序列化data, err :=json.Marshal(registerMes)if err != nil {fmt.Println("json.Mashal err=",err)return}//5.将data赋给了mes.Data字段mes.Data = string(data)//6.将mes进行序列化data, err =json.Marshal(mes)if err != nil {fmt.Println("json.Mashal err=",err)return}//7.到这个时候,data就是我们要发送的消息//7.1先把data的长度发送给服务器//先获取data的长度->转成一个表示长度的byte切片var pkgLen uint32pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4],pkgLen) //将该、长度转成了byte类型是数据//发送长度n, err := conn.Write(buf[:4])if n != 4 || err !=nil {fmt.Println("connWrite(buf) fail ",err)return}fmt.Printf("客户端发送数据的消息长度=%d 内容是=%s",len(data),string(data))//发送消息本身_, err = conn.Write(data)if err !=nil {fmt.Println("connWrite(data) fail ",err)return}//创建一个Transfer实例tf := &utils.Transfer{Conn : conn,}//发送data给服务器端err = tf.WritePkg(data)if err != nil {fmt.Println("注册发送信息错误 err=",err)}mes, err = tf.ReadPkg() //mes 就是RegisterResMesif err != nil {fmt.Println("readPkg(conn) err=",err)return}//将mes的Data部分反序列化为RegisterResMesvar registerResMes message.RegisterResMeserr = json.Unmarshal([]byte(mes.Data),&registerResMes)if registerResMes.Code == 200 {fmt.Println("注册成功,你重新登录一把")os.Exit(0)}else {fmt.Println(registerResMes.Error)os.Exit(0)}return}

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

1)用户登陆后,可以得到当前在线用户列表 思路分析、示意图代码实现

用户登陆后,可以得到当前在线用户列表

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

(2)创建一个新的文件userMgr.go,完成功能,对onlineUsers这个map进行增删改查

(3)在loginResMes增加一个字段 User []int 将在线的用户ID返回

(4)当用户登陆后,可以显示当前在线用户列表

2)示意图

在这里插入图片描述

3)代码实现
(1)编写了server/process/userMgr.go
package process
import ("fmt"
)
//因为UserMge实例在服务其中有且只有一个
//因为在很多的地方,都会使用,因此,我们
//将其定义为全局变量
var (userMgr *UserMgr
)
type UserMgr struct {onlineUsers map[int]*UserProcess
}//完成对userMge的初始化工作
func init() {userMgr = &UserMgr{onlineUsers : make(map[int]*UserProcess,1024),}
}//完成对onlineUsers的添加
func (this *UserMgr) AddOnlinesUser(up *UserProcess) {this.onlineUsers[up.UserId] = up
}//删除
func (this *UserMgr) DeleteOnlinesUser(userId int ) {delete(this.onlineUsers,userId)
}//返回当前所有在线的用户
func (this *UserMgr)GetAllUsers() 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("用户id不存在",userId)return} return
}
(2)server/process/userProcess.go(在login成功的地方加入代码)
} else {loginResMes.Code = 200//这里,因为用户登录成功,我们就把登录成功的用户放入到userMgr中//将登录成功的用户的userId赋给thisthis.UserId = loginMes.UserIduserMgr.AddOnlinesUser(this)//将当前在线用户的id放入到loginResMes.UsersId//遍历userMgr.onlineUsersfor id, _ := range userMgr.onlineUsers{loginResMes.UsersId = append(loginResMes.UsersId,id)}fmt.Println(user, "登录成功")}

(3)client

/process/userProcess.go(在login成功的地方加入代码)

//现在可以显示当前在线的列表 遍历loginResMes.UsersIdfmt.Println("当前在线用户列表如下")for _, v := range loginResMes.UsersId {//如果我们要求不显示自己在线,下面我们增加一个代码if v == userId {continue}fmt.Println("用户id:\t",v)}fmt.Println("\n\n")
4)当一个新的用户上线后,其他已经登录的用户也能获取最新在新用户列表

思路1:

当有一个用户上线后,服务其就马上把维护的onlineUser map整体推送

思路2:

服务其有自己的策略,每隔一段时间,把维护的onlineUsers map整体推送

思路3:

(1)当一个用户上线后,服务器就把A用户的上线信息推送给所有在线用户即可

(2)客户端也要维护一个map,map中记录了他的好友(目前就是所有人)map[int]User

(3)客户端和服务器的通讯通道要依赖于serverProcess协程

代码实现

(1)在server/process/userMgr.go

package process
import ("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\t",id)}
}//编写一个方法,处理返回的NotifyUserStatusMes
func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) {//适当的优化user,ok :=onlineUsers[notifyUserStatusMes.UserId]if !ok { //原来没有user = &message.User{UserId : notifyUserStatusMes.UserId,}	}user.UserStatus = string(notifyUserStatusMes.Status)onlineUsers[notifyUserStatusMes.UserId] = useroutputOnlineUser()
}

(2)server/process/userProcess.go

//这里我们编写通知所有在线用户的方法
//这个id要通知其他的在线用户,我上线
func (this *UserProcess) NotifyOthersOnlineUser(userId int) {//遍历 onlineUsers ,然后一个一个的发送 NotifyUserStatusMesfor id, up := range userMgr.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.Status = message.UserOnline//将notifyUserStatusMes序列化data, err := json.Marshal(notifyUserStatusMes)if err != nil {fmt.Println("json.Marshal err",err)return}//将序列化后的notifyUserStatusMes赋值给mes.Datames.Data = string(data)//对message再次序列化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("NotifyMeOline err=",err)return}}下面调用
//通知其他的用户我上线了this.NotifyOthersOnlineUser(loginMes.UserId)

(3)common/message/message.go

//为了配合服务器端推送用户状态变化类型
type NotifyUserStatusMes struct {UserId int `json:"userId"` //用户idStatus int `json:"status"` //用户的状态
}

(4)客户端client/process/userMgr.go

package process
import ("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\t",id)}
}//编写一个方法,处理返回的NotifyUserStatusMes
func updateUserStatus(notifyUserStatusMes *message.NotifyUserStatusMes) {//适当的优化user,ok :=onlineUsers[notifyUserStatusMes.UserId]if !ok { //原来没有user = &message.User{UserId : notifyUserStatusMes.UserId,}	}user.UserStatus = string(notifyUserStatusMes.Status)onlineUsers[notifyUserStatusMes.UserId] = useroutputOnlineUser()
}

(5)client/main/server.go

case 1://fmt.Println("显示用户在线列表")outputOnlineUser()case 2://如果读取到消息,又是下一步处理逻辑switch mes.Type {case message.NotifyUserStatusMesType : //有人上线了//1.取出 NotifyUserStatusMesvar notifyUserStatusMes message.NotifyUserStatusMesjson.Unmarshal([]byte(mes.Data),&notifyUserStatusMes)//2.把这个用户的信息,状态保存在客户端的map[int]User中updateUserStatus(&notifyUserStatusMes)//处理default :fmt.Println("服务其端返回了未知的消息类型")	}

6.完成登录可以完成群聊操作

-1.步骤1 :

当一个用户上线后,可以将群聊消息发给服务器。服务器可以接收到

1)思路分析

在这里插入图片描述

(1)新增一个消息结构体

(2)新增一个model CurUser

(3)在smsProcess增加相应的方法 SendGroupMes,

2)代码实现

(1)common/message/message.go

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

(2)client/model/curUser.go

package model
import ("net""go_code/chatroom/common/message"
)//因为在客户端,我们很多地方会使用到curUser,我们将其作为一个全局的
type CurUser struct {Conn net.Connmessage.User
}

(3)client/process/smsProcess.go

package process
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.创建一个Mesvar mes message.Messagemes.Type = message.SmsMesType//2.创建一个SmsMes 实例var smsMes message.SmsMessmsMes.Content = content //内容smsMes.UserId = CurUser.UserIdsmsMes.UserStatus = CurUser.UserStatus//3.序列化smsMesdata, 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(" json.Marshal err=",err.Error())return}//5.将mes发送给服务器tf := &utils.Transfer{Conn : CurUser.Conn,}//6.发送err = tf.WritePkg(data)if err != nil {fmt.Println("SendGroupsMes err=",err.Error())return}return
}

(4)测试

在这里插入图片描述

-2.步骤2.

服务器可以将接收到的消息,群发给所有在线用户(发送者除外)

1)思路分析

在这里插入图片描述

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

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

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

2)代码实现

(1)server/main/processor.go[在server中调用转发消息的方法]

//处理注册up := &process2.UserProcess{Conn : this.Conn,}err = up.ServerProcessRegister(mes)case message.SmsMesType ://创建一个SmsProcess实例完成转发群聊消息。	smsProcess := &process2.SmsProcess{}smsProcess.SendGroupMes(mes)

(2)client/process/smsMes.go

package process
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.创建一个Mesvar mes message.Messagemes.Type = message.SmsMesType//2.创建一个SmsMes 实例var smsMes message.SmsMessmsMes.Content = content //内容smsMes.UserId = CurUser.UserIdsmsMes.UserStatus = CurUser.UserStatus//3.序列化smsMesdata, 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(" json.Marshal err=",err.Error())return}//5.将mes发送给服务器tf := &utils.Transfer{Conn : CurUser.Conn,}//6.发送err = tf.WritePkg(data)if err != nil {fmt.Println("SendGroupsMes err=",err.Error())return}return
}

(3)client/process/smsMgr.go

package process
import ("fmt""encoding/json""go_code/chatroom/common/message")func outputGroupMes(mes *message.Message) {//这个地方一定是SmsMes//显示即可//1.反序列化mes.Datavar 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()
}

(4)client/process/server.go

case message.SmsMesType : //有人群发消息了outputGroupMes(&mes)
3)拓展功能要求

1.可以实现私聊(点对点聊天)

2.如果一个登录用户离线,就把这个人从在线列表中去掉

3.实现离线留言,在群聊时,如果某个用户没有在线,当登录后,可以接受到离线的消息

相关文章:

基于go语言开发的海量用户及时通讯系统

文章目录 二十三、海量用户即时通讯系统1、项目开发前技术准备2.实现功能-显示客户端登录菜单3.实现功能-完成用户登录-1.完成客户端可以该长度值发送消息长度,服务器端可以正常接收到-2.完成客户端可以发送消息,服务器端可以接收到消息并根据客户端发送…...

19.Oracle 中count(1) 、count(*) 和count(列名) 函数的区别

count(1) and count(字段) 两者的主要区别是 count(1) 会统计表中的所有的记录数,包含字段为null 的记录。count(字段) 会统计该字段在表中出现的次数,忽略字段为null 的情况。 即不统计字段为null 的记录。 count(*) 和 count(1)和count(列名)区别 …...

C 库函数 - time()

描述 C 库函数 time_t time(time_t *seconds) 返回自纪元 Epoch(1970-01-01 00:00:00 UTC)起经过的时间,以秒为单位。如果 seconds 不为空,则返回值也存储在变量 seconds 中。 声明 下面是 time() 函数的声明。 time_t time(t…...

基于Python数据可视化的网易云音乐歌单分析系统

目录 《Python数据分析初探》项目报告 基于Python数据可视化的网易云音乐歌单分析系统一、项目简介(一)项目背景(二)项目过程 二、项目设计流程图(一)基于Python数据可视化的网易云音乐歌单分析系统的整体…...

Jenkins----基于 CentOS 或 Docker 安装部署Jenkins并完成基础配置

查看原文 文章目录 基于 CentOS7 系统部署 Jenkins 环境基于 Docker 安装部署 Jenkins环境配置 Jenkins 中文模式配置用户名密码形式的 Jenkins 凭据配置 ssh 私钥形式的 Jenkins 凭据配置 Jenkins 执行任务的节点 基于 CentOS7 系统部署 Jenkins 环境 (1&#xff…...

flume系列之:监控flume agent channel的填充百分比

flume系列之:监控flume agent channel的填充百分比 一、监控效果二、获取flume agent三、飞书告警四、获取每个flume agent channel的填充百分比一、监控效果 二、获取flume agent def getKafkaFlumeAgent():# 腾讯云10.130.112.60zk = KazooClient(hosts...

信息安全和网络安全的区别

信息安全与网络安全都属于安全领域,但它们的范围和重点不同。 信息安全主要关注数据的保护,包括对敏感数据进行加密、防止数据丢失或泄露等措施。信息安全通常与数据存储、传输和处理相关。 而网络安全更侧重于保护计算机系统和网络免受攻击、病毒、蠕…...

【开源项目】WPF 扩展 -- 多画面视频渲染组件

目录 1、项目介绍 2、组件集成 2.1 下载地址 2.2 添加依赖 3、使用示例 3.1 启动动画 3.2 视频渲染 3.3 效果展示 4、项目地址 1、项目介绍 Com.Gitusme.Net.Extensiones.Wpf 是一款 Wpf 扩展组件。基于.Net Core 3.1 开发,当前是第一个发布版本 1.0.0&am…...

risc-v system instruction

ECALL ecall 指令以前叫做 scall,用于执行环境的变更,它会根据当前所处模式触发不同的执行环境切换异常, 用来执行需要更高权限才能执行的功能;简单来说,ecall 指令将权限提升到内核模式并将程序跳转到指定的地址。操作系统内核和应用程序其实…...

08 v-text指令

概述 v-text指令主要是用来渲染文本内容,和双大括号的效果基本一致,所以使用场景非常少。 一般情况下,我们都会使用双大括号语法去渲染文本内容,而不是使用v-text指令。 基本用法 我们创建src/components/Demo08.vue&#xff…...

vite基本知识

vite的了解与使用 基本知识 开发时,并不对代码打包,而实直接采用ESM的方式运行项目一 项目部署时,再对项目进行打包 核心原理 其核心原理是利用浏览器现在已经支持ES6的import,碰见import就会发送一个HTTP请求去加载文件 使…...

考研真题c语言

【2016年山西大学考研真题】输入10个学生三门课的成绩,用函数实现:找出最高的分数所对应的学号和成绩。 1. 定义一个结构体 Student 来表示每个学生,包括学号和三门课的成绩。 c typedef struct { int studentID; int score1; i…...

neuq-acm预备队训练week 9 P8604 [蓝桥杯 2013 国 C] 危险系数

题目背景 抗日战争时期,冀中平原的地道战曾发挥重要作用。 题目限制 题目描述 地道的多个站点间有通道连接,形成了庞大的网络。但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系。 我们来定义一个危险系数 DF…...

【BIG_FG_CSDN】*VMware17pro*Linux*Redhit6网络管理(个人向——学习笔记)

物理机中的网络 查看物理网络的方法 “网络连接”—>单点选中网络的选项-->菜单栏中“查看此连接状态”-->“详细信息” “网络连接”中的VM网卡 在主机上对应的有VMware Network Adapter VMnet1和VMware Network Adapter VMnet8两块虚拟网卡,它们分别…...

Nginx location+Nginx rewrite(重写)(新版)

Nginx locationNginx rewrite(重写) Nginx locationNginx rewrite(重写)一、location1、常用的Nginx 正则表达式2、location的类型3、location 的匹配规则4、location 优先级5、location 示例说明5.1只修改网页路径5.2修改nginx配置文件和网页路径5.3一般前缀5.4正则匹配5.5前缀…...

uniapp实现地图电子围栏功能

该功能使用uniapp中内置组件map实现 效果图预览&#xff1a; 实现过程&#xff1a; 1.文档&#xff1a; 2.代码&#xff1a; <template><view><map :style"width: 100%; height:screenHeight" :latitude"latitude" :longitude"longit…...

LeetCode第376场周赛

文章目录 1.Find Missing and Repeated Values2.Divide Array Into Arrays With Max Difference3.Minimum Cost to Make Array Equalindromic 1.Find Missing and Repeated Values 直接暴力过 class Solution { public:vector<int> findMissingAndRepeatedValues(vecto…...

数据仓库与数据挖掘小结

更加详细的只找得到pdf版本 填空10分 判断并改错10分 计算8分 综合20分 客观题 填空10分 判断并改错10分--错的要改 mooc中的--尤其考试题 名词解释12分 4个&#xff0c;每个3分 经常碰到的专业术语 简答题40分 5个&#xff0c;每道8分 综合 画roc曲线 …...

ensp创建配置环境,实现全网互访

文章目录 创建配置环境&#xff0c;实现全网互访配置步骤接入层交换机&#xff08;sw4、sw5&#xff09;划分vlan汇聚层交换机&#xff08;sw2、sw3&#xff09;配置ip地址作为vlan网关、与sw1 ip地址直连核心层交换机&#xff08;sw1&#xff09;配置ip地址与汇聚层交换机&…...

智能优化算法应用:基于JAYA算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于JAYA算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于JAYA算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.JAYA算法4.实验参数设定5.算法结果6.参考文献7.MA…...

ripro后台登录后转圈和图标不显示的原因及解决方法

最近&#xff0c;好多小伙伴使用ripro主题的小伙伴们都发现&#xff0c;登录后台后&#xff0c;进入主题设置就转圈&#xff0c;等待老半天后好不容易显示页面了&#xff0c;却发现图标不显示了&#xff0c;都统一显示为方框。 这是因为后台的js、css这类静态资源托管用的是js…...

android 源码编译android 12

一、python安装 python2 sudo apt-get install python python3 sudo apt-get install python3 二、repo管理多个git repo因为Android源码由多个git组成&#xff0c;故安装repo利于管理git工程. repo安装步骤 a.第一步, 新建一个空白文件夹保存repo引导文件,并包含你的路径…...

CSS第二天导读

1&#xff0c;Emmet语法 Emmet语法的前身是Zen coding&#xff0c;它使用缩写&#xff0c;来提高html / css 的编写速度&#xff0c;Vscode内部已经集成该语法 1.1&#xff0c;快速生成HTML结构语法 1.想要快速生成多个相同标签&#xff0c;加上*就可以了&#xff0c;比如 d…...

scroll-behavior属性使用方法

定义和用法&#xff1a; scroll-behavior 属性规定当用户单击可滚动框中的链接时&#xff0c;是否平滑地&#xff08;具动画效果&#xff09;滚动位置&#xff0c;而不是直线跳转。 <style>element{/* 核心代码 */scroll-behavior: smooth;} </style> 属性值&am…...

Python Django 连接 PostgreSQL 操作实例

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python Django 连接 PostgreSQL 操作实例&#xff0c;全文3500字&#xff0c;阅读大约10分钟 在Web开发中&#xff0c;使用Django连接到PostgreSQL数据库是一种常见的选择。…...

5.实现简化版raft协议完成选举

1.设计 前面已经完成了netty的集成&#xff0c;接下来就是借助netty完成选举就行了。 针对选举&#xff0c;我们用到了VotRequestMessage、VotRespMessage、当节点下线时NodeOfflineMessage、NodeOnlineMessage、NodeOnlineRespMessage 1.1 节点详细的交互 1.2 对所有消息的…...

服装管理系统 简单实现

服装管理系统 项目使用jsp servletmysql实现&#xff1b; 登陆注册 首页 首页显示服装信息 服装管理 1添加服装 2修改服装 3分页查询服装 4导出服装信息 5 导入服装信息 代码结构截图 百度网盘 链接&#xff1a;https://pan.baidu.com/s/1zfLHGMnrYd-JtnhzS5elYQ 提取码…...

深度学习项目实战:垃圾分类系统

简介&#xff1a; 今天开启深度学习另一板块。就是计算机视觉方向&#xff0c;这里主要讨论图像分类任务–垃圾分类系统。其实这个项目早在19年的时候&#xff0c;我就写好了一个版本了。之前使用的是python搭建深度学习网络&#xff0c;然后前后端交互的采用的是java spring …...

C#浅拷贝和深拷贝数据

目录 一、浅拷贝 二、深拷贝 一、浅拷贝 就是把原来的数据&#xff0c;复制一份&#xff0c;但是2份数据是共享地址的&#xff0c;修改第一份数据或者修改第二份数据&#xff0c;都会一起改变&#xff0c;这可能不是我们程序中需要的场景。 下面我们演示一下&#xff0c;首…...

【JVM】4.运行时数据区(程序计数器、虚拟机栈)

文章目录 4.JVM的运行时数据区4.1 程序计数器4.2 Java虚拟机栈4.3 虚拟机栈内存溢出 4.JVM的运行时数据区 4.1 程序计数器 程序计数器&#xff08;PC&#xff09;会记录着下一行字节码指令的地址。执行完当前指令后&#xff0c;PC刷新&#xff0c;JVM的执行引擎根据程序计数器…...