企业网站都是静态的吗,邮箱注册申请,wordpress好用的文件管理,成都最新的防疫通告文章目录 二十三、海量用户即时通讯系统1、项目开发前技术准备2.实现功能-显示客户端登录菜单3.实现功能-完成用户登录-1.完成客户端可以该长度值发送消息长度#xff0c;服务器端可以正常接收到-2.完成客户端可以发送消息#xff0c;服务器端可以接收到消息并根据客户端发送… 文章目录 二十三、海量用户即时通讯系统1、项目开发前技术准备2.实现功能-显示客户端登录菜单3.实现功能-完成用户登录-1.完成客户端可以该长度值发送消息长度服务器端可以正常接收到-2.完成客户端可以发送消息服务器端可以接收到消息并根据客户端发送的消息判断用户的合法性并返回相应的消息-3.能够完成登录并提示信息-4.程序结构的改进1画出程序框架图2步骤server层后端项目结构图client层后端项目结构图 -5.应用redis1在Redis手动添加测试用户并画图说明注意后面通过程序注册用户2如输入的用户名密码正确在Redis中存在则登录否则退出系统并给出相应的提示信息3代码实现(1)先编写了server/model/user.go2先编写了server/model/error.go3编写了server/model/userDao.go4编写了server/main.redis.go5编写了server/process/userProcess.go改进登录方式以及错误类型6改进server/main/main.go(加了一个初始化redis连接池的函数) 4.完成用户注册操作1要求2具体代码1common/message/user.go(从server/model下复制过来的。记住要复制而不是剪切还要改包名)2common/message/message.go增加了关于注册消息的代码3server/process/userProcess(增加了一个方法)4server/model/userDao(增加了一个Register方法对数据库进行添加的操作)5在client/main/main.go进行了调用操作(6)client/process/userProcess.go(添加一个Register的方法) 5.实现功能-完成登录时能返回当前在线用户3代码实现1编写了server/process/userMgr.go2server/process/userProcess.go(在login成功的地方加入代码) 4当一个新的用户上线后其他已经登录的用户也能获取最新在新用户列表 6.完成登录可以完成群聊操作-1.步骤1 1思路分析2代码实现 -2.步骤2.1思路分析2代码实现3拓展功能要求 二十三、海量用户即时通讯系统
项目展示 开始此项目之前请确保安装好redisgolang 源码下载https://github.com/BeAlrightc/go-study.git
1、项目开发前技术准备
项目要保存用户信息和信息数据因此我们需要学习数据(redis或者mysql),这里我们选择redis
2.实现功能-显示客户端登录菜单 代码编写
clien包下的main.go
package main
import (fmtos
)//定义两个变量一个表示用户的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(登录聊天室)loopfalsecase 2 :fmt.Println(注册用户) loopfalsecase 3 :fmt.Println(退出系统) //loopfalseos.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.实现功能-完成用户登录
要求完成指定用户的验证用户id100密码pwd123456可以登录其他用户不能登录
理解从client到server中的程序执行流程如图所示【Message组成的示意图。并发送一个message的流程介绍】 -1.完成客户端可以该长度值发送消息长度服务器端可以正常接收到
分析思路
1先确定消息Message的格式
2发送消息示意图 代码展示
sever
main.go
package main
import (fmtnet
)//处理和客户端的通讯
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 LoginMesLoginResMesType 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 (fmtnetencoding/jsonencoding/binarygo_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 (fmtos
)//定义两个变量一个表示用户的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(登录聊天室)loopfalsecase 2 :fmt.Println(注册用户) loopfalsecase 3 :fmt.Println(退出系统) //loopfalseos.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 (fmtnetencoding/jsonencoding/binarygo_code/chatroom/common/message//errorsio
)
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 uint32pkgLenbinary.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.Typemessage.LoginResMesType//2.再声明一个LoginResMesvar loginResMes message.LoginResMes//如果用户的id100,密码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 (fmtnetencoding/jsonencoding/binarygo_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 uint32pkgLenbinary.BigEndian.Uint32(buf[0:4])//根据pkgLendata数据的长度读取消息内容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 (fmtnetencoding/jsonencoding/binarygo_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 uint32pkgLenbinary.BigEndian.Uint32(this.Buf[0:4])//根据pkgLendata数据的长度读取消息内容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 (fmtnetencoding/jsongo_code/chatroom/common/messagego_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.Typemessage.LoginResMesType//2.再声明一个LoginResMesvar loginResMes message.LoginResMes//如果用户的id100,密码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 (fmtnetgo_code/chatroom/common/messagego_code/chatroom/server/utilsgo_code/chatroom/server/processio
)//先创建一个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 (fmtnet
)//处理和客户端的通讯
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 (fmtnetencoding/jsonencoding/binarygo_code/chatroom/common/messagego_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 (fmtosgo_code/chatroom/client/utilsnet
)//显示登录后的界面..
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)}}6client/main/main.go
package main
import (fmtosgo_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)//loopfalsecase 2 :fmt.Println(注册用户) //loopfalsecase 3 :fmt.Println(退出系统) //loopfalseos.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:userIdUserPwd string json:userPwdUserName 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 (fmtgithub.com/garyburd/redigo/redisencoding/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/redistime)//定义一个全局的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具体代码
1common/message/user.go(从server/model下复制过来的。记住要复制而不是剪切还要改包名)
package message// User 定义一个用户的结构体
type User struct {//确定字段信息//为了序列化和反序列化成功//用户信息的json字符串与结构体字段对应的Tag名字一致UserId int json:userIdUserPwd string json:userPwdUserName string json:userName
}2common/message/message.go增加了关于注册消息的代码
type RegisterMes struct {User User json:user //类型就是User结构体}
type RegisterResMes struct {Code int json:code //返回状态码400表示该用户已经占用 200表示登录注册成功Error string json //返回错误信息
}3server/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}4server/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
}2server/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, 登录成功)}
3client
/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 (fmtgo_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()
}2server/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)3common/message/message.go
//为了配合服务器端推送用户状态变化类型
type NotifyUserStatusMes struct {UserId int json:userId //用户idStatus int json:status //用户的状态
}(4)客户端client/process/userMgr.go
package process
import (fmtgo_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()
}5client/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代码实现
1common/message/message.go
//增加一个SmsMes //发送的
type SmsMes struct {Content string json:content //内容User //匿名结构体继承
}
2client/model/curUser.go
package model
import (netgo_code/chatroom/common/message
)//因为在客户端我们很多地方会使用到curUser,我们将其作为一个全局的
type CurUser struct {Conn net.Connmessage.User
}3client/process/smsProcess.go
package process
import (fmtencoding/jsongo_code/chatroom/common/messagego_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代码实现
1server/main/processor.go[在server中调用转发消息的方法]
//处理注册up : process2.UserProcess{Conn : this.Conn,}err up.ServerProcessRegister(mes)case message.SmsMesType ://创建一个SmsProcess实例完成转发群聊消息。 smsProcess : process2.SmsProcess{}smsProcess.SendGroupMes(mes)
2client/process/smsMes.go
package process
import (fmtencoding/jsongo_code/chatroom/common/messagego_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
}3client/process/smsMgr.go
package process
import (fmtencoding/jsongo_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()
}4client/process/server.go
case message.SmsMesType : //有人群发消息了outputGroupMes(mes)3拓展功能要求
1.可以实现私聊点对点聊天
2.如果一个登录用户离线就把这个人从在线列表中去掉
3.实现离线留言在群聊时如果某个用户没有在线当登录后可以接受到离线的消息