1、用户注册
2、用户登陆
3、显示在线用户列表
4、群聊
5、点对点聊天
6、离线留言
这里选择redis数据库 代码实现client/login.go
package main
import (
"fmt"
"net"
"encoding/binary"
"encoding/json"
"chatroom/common/message"
)
// 写一个函数,完成登陆
func login(userId int, userPwd string) (err error) {
// 下一步就要开始定协议。。。
// fmt.Printf(" userID=%d userPwd=%s", userID, userPwd)
// return nil
//1、连接到feu武器
conn, err := net.Dial("tcp", "localhost:8899")
if err != nil {
fmt.Println("net.Dial err", err)
return
}
//延时关闭
defer conn.Close()
// 2、准备通过conn发送消息给服务器
var mes message.Message
mes.Type = message.LoginMesType
// 3、创建一个LoginMes
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.UserPwd = userPwd
// 4、将loginMes序列化
data, err := json.Marshal(loginMes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//5、将data赋给 mes.Data字段
mes.Data = string(data)
// 6、将mes进行序列化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//7、此时data就是我们要发送的消息
// 7.1 先把data的长度发生给服务器
// 先获取到data的长度->转换成一个表示长度的byte切片
var pkgLen uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// 发送长度
n, err := conn.Write(buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write fail", err)
return
}
// fmt.Printf("客户端,发送消息长度成功=%d 内容是=%s", len(data), string(data))
// 发送消息本身
_, err = conn.Write(data)
if err != nil {
fmt.Println("conn.Write(data) fail", err)
return
}
//休眠20
// time.Sleep(20 * time.Second)
// fmt.Println("休眠了20秒")
// 这里还需要处理服务器端的消息
mes, err = readPkg(conn) //mes就是
if err != nil {
fmt.Println("readPkg err=", err)
return
}
// 将mes的Data部分反序列化成 LoginResMes
var loginResMes message.LoginResMes
err = json.Unmarshal([]byte(mes.Data), &loginResMes)
if loginResMes.Code == 200 {
fmt.Println("用户登陆成功")
} else if loginResMes.Code == 500 {
fmt.Println(loginResMes.Error)
}
return
}
client/main.go
package main
import (
"fmt"
"os"
)
// 定义两个变量,一个表示用户的ID, 一个表示用户密码
var userId int
var userPwd string
func main() {
// 接收用户选择
var key int
// 判断是否还继续显示菜单
var loop = true
for 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 = false
case 2 :
fmt.Println("注册用户")
loop = false
case 3 :
fmt.Println("退出系统")
os.Exit(0)
default :
fmt.Println("你的输入有误,请重新输入")
}
}
// 根据用户的输入,显示新的提示信息
if key ==1 {
// 说明用户要登陆
fmt.Println("请输入用户ID:")
fmt.Scanf("%d\n", &userId)
fmt.Println("请输入用户密码:")
fmt.Scanf("%s\n", &userPwd)
// 先把登陆的函数,写到另一个文件
err := login(userId, userPwd)
if err != nil {
fmt.Println("登陆失败")
} else {
fmt.Println("登陆成功")
}
} else if key ==2 {
fmt.Println("进行用户注册的逻辑。。。。")
}
}
common/message/message.go
package message
const (
LoginMesType = "loginMes"
LoginResMesType = "LoginResMes"
)
type Message struct {
Type string `json:"type"`//消息类型
Data string `json:"data"`//消息的类型
}
// 先定义两个消息
type LoginMes struct {
UserId int `json:"userId"`//用户Id
UserPwd string `json:"userPwd"`//用户密码
UserName string `json:"userName"`//用户名
}
type LoginResMes struct {
Code int `json:"code"`//返回状态码 500表示该用户未注册 200 用户登陆成功
Error string `json:"error"`//返回错误信息
}
server/main.go
package main
import (
"fmt"
"net"
"encoding/binary"
"encoding/json"
"chatroom/common/message"
"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])
if err != nil {
return
}
//根据读到的长度buf[:4] 转成一个uint32类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(buf[0:4])
// 根据pkgLen读取内容
n, err := conn.Read(buf[:pkgLen])
if n != int(pkgLen) || err != nil {
return
}
// 把pkgLen反序列化成 -> message.Message
err = json.Unmarshal(buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarshal fail err=", err)
return
}
return
}
func writePkg(conn net.Conn, data []byte) (err error){
// 先发送一个长度给对方
var pkgLen uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// 发送长度
n, err := conn.Write(buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write fail", err)
return
}
// 发送data本身
n, err = conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write fail", err)
return
}
return
}
// 编写一个函数serverProcessLogin函数,专门处理登陆请求
func serverProcessLogin(conn net.Conn, mes *message.Message) (err error){
// 核心代码。。。
// 1、先从mes中取出mes.Data,并直接反序列化成LoginMes
var loginMes message.LoginMes
err = json.Unmarshal([]byte(mes.Data), &loginMes)
if err != nil {
fmt.Println("json.Unmashal fail err=", err)
return
}
// ① 先声明一个resMes
var resMes message.Message
resMes.Type = message.LoginResMesType
// ② 再声明一个 loginResMes,并完成赋值
var loginResMes message.LoginResMes
//如果用户的id为100,密码为123456,认为合法, 否则 不合法
if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// 合法
loginResMes.Code = 200
} else {
//不合法
loginResMes.Code = 500 //500状态码,表示该用户不存在
loginResMes.Error = "该用户不存在,请注册后再使用"
}
// ③ 将 loginResMes 序列化
data, err := json.Marshal(loginResMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ④ 将data 赋值给resMes
resMes.Data = string(data)
// ⑤ 对resMes 进行序列化,准备发送
data, err = json.Marshal(resMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ⑥ 发送data,将其封装到writePkg函数
err = 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
}
//处理和客户端的通讯
func process(conn net.Conn) {
//这里需要延迟关闭conn
defer conn.Close()
//循环读取读客户端发送的消息
for {
// 这里将读取数据包,直接封装成一个readPkg(),返回Message,err
mes, err := readPkg(conn)
if err != nil {
if err == io.EOF {
fmt.Println("客户端退出,服务器端也正常退出")
return
} else {
fmt.Println("readPkg fail err=", err)
return
}
}
err = serverProcessMes(conn, &mes)
if err != nil {
return
}
}
}
func main() {
//提示信息
fmt.Println("服务器在8899监听。。。。")
listen, err := net.Listen("tcp", "0.0.0.0:8899")
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("liten Accept err=", err)
}
//一旦连接成功,则启动一个协程和客户端保持通讯。
go process(conn)
}
}
client/utils.go 新增文件整体代码
package main
import (
"fmt"
"net"
"encoding/binary"
"encoding/json"
"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 {
return
}
//根据读到的长度buf[:4] 转成一个uint32类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(buf[0:4])
// 根据pkgLen读取内容
n, err := conn.Read(buf[:pkgLen])
if n != int(pkgLen) || err != nil {
return
}
// 把pkgLen反序列化成 -> message.Message
err = json.Unmarshal(buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarshal fail err=", err)
return
}
return
}
func writePkg(conn net.Conn, data []byte) (err error){
// 先发送一个长度给对方
var pkgLen uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// 发送长度
n, err := conn.Write(buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write fail", err)
return
}
// 发送data本身
n, err = conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write fail", err)
return
}
return
}
服务端代码该进
package main
import (
"fmt"
"net"
)
// 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 {
// return
// }
// //根据读到的长度buf[:4] 转成一个uint32类型
// var pkgLen uint32
// pkgLen = binary.BigEndian.Uint32(buf[0:4])
// // 根据pkgLen读取内容
// n, err := conn.Read(buf[:pkgLen])
// if n != int(pkgLen) || err != nil {
// return
// }
// // 把pkgLen反序列化成 -> message.Message
// err = json.Unmarshal(buf[:pkgLen], &mes)
// if err != nil {
// fmt.Println("json.Unmarshal fail err=", err)
// return
// }
// return
// }
// func writePkg(conn net.Conn, data []byte) (err error){
// // 先发送一个长度给对方
// var pkgLen uint32
// pkgLen = uint32(len(data))
// var buf [4]byte
// binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// // 发送长度
// n, err := conn.Write(buf[0:4])
// if n != 4 || err != nil {
// fmt.Println("conn.Write fail", err)
// return
// }
// // 发送data本身
// n, err = conn.Write(data)
// if n != int(pkgLen) || err != nil {
// fmt.Println("conn.Write fail", err)
// return
// }
// return
// }
// 编写一个函数serverProcessLogin函数,专门处理登陆请求
// func serverProcessLogin(conn net.Conn, mes *message.Message) (err error){
// // 核心代码。。。
// // 1、先从mes中取出mes.Data,并直接反序列化成LoginMes
// var loginMes message.LoginMes
// err = json.Unmarshal([]byte(mes.Data), &loginMes)
// if err != nil {
// fmt.Println("json.Unmashal fail err=", err)
// return
// }
// // ① 先声明一个resMes
// var resMes message.Message
// resMes.Type = message.LoginResMesType
// // ② 再声明一个 loginResMes,并完成赋值
// var loginResMes message.LoginResMes
// //如果用户的id为100,密码为123456,认为合法, 否则 不合法
// if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// // 合法
// loginResMes.Code = 200
// } else {
// //不合法
// loginResMes.Code = 500 //500状态码,表示该用户不存在
// loginResMes.Error = "该用户不存在,请注册后再使用"
// }
// // ③ 将 loginResMes 序列化
// data, err := json.Marshal(loginResMes)
// if err != nil {
// fmt.Println("json.Marshal fail", err)
// return
// }
// // ④ 将data 赋值给resMes
// resMes.Data = string(data)
// // ⑤ 对resMes 进行序列化,准备发送
// data, err = json.Marshal(resMes)
// if err != nil {
// fmt.Println("json.Marshal fail", err)
// return
// }
// // ⑥ 发送data,将其封装到writePkg函数
// err = 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
// }
//处理和客户端的通讯
func process(conn net.Conn) {
//这里需要延迟关闭conn
defer conn.Close()
// 这里调用总控创建一个总控
processor := &Processor{
Conn : conn,
}
err := processor.process2()
if err != nil {
fmt.Println("客户端和服务器端的通讯协程出现问题 err=", err)
return
}
}
func main() {
//提示信息
fmt.Println("服务器[新的结构]在8899监听。。。。")
listen, err := net.Listen("tcp", "0.0.0.0:8899")
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("liten Accept err=", err)
}
//一旦连接成功,则启动一个协程和客户端保持通讯。
go process(conn)
}
}
server/main/process.go
package main
import (
"fmt"
"net"
"chatroom/common/message"
"chatroom/server/utils"
"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 fail err=", err)
return err
}
}
err = this.serverProcessMes(&mes)
if err != nil {
return err
}
}
}
server/process/userprocess.go
package process2
import (
"fmt"
"net"
"encoding/json"
"chatroom/common/message"
"chatroom/server/utils"
)
type UserProcess struct {
// 字段
Conn net.Conn
}
// 编写一个函数serverProcessLogin函数,专门处理登陆请求
func(this *UserProcess) ServerProcessLogin(mes *message.Message) (err error){
// 核心代码。。。
// 1、先从mes中取出mes.Data,并直接反序列化成LoginMes
var loginMes message.LoginMes
err = json.Unmarshal([]byte(mes.Data), &loginMes)
if err != nil {
fmt.Println("json.Unmashal fail err=", err)
return
}
// ① 先声明一个resMes
var resMes message.Message
resMes.Type = message.LoginResMesType
// ② 再声明一个 loginResMes,并完成赋值
var loginResMes message.LoginResMes
//如果用户的id为100,密码为123456,认为合法, 否则 不合法
if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// 合法
loginResMes.Code = 200
} else {
//不合法
loginResMes.Code = 500 //500状态码,表示该用户不存在
loginResMes.Error = "该用户不存在,请注册后再使用"
}
// ③ 将 loginResMes 序列化
data, err := json.Marshal(loginResMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ④ 将data 赋值给resMes
resMes.Data = string(data)
// ⑤ 对resMes 进行序列化,准备发送
data, err = json.Marshal(resMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ⑥ 发送data,将其封装到writePkg函数
// 因为使用了分层的模式,先创建Transfer实例,然后读取
tf := &utils.Transfer{
Conn : this.Conn,
}
err = tf.WritePkg(data)
return
}
server/utils/utils.go
package utils
import (
"fmt"
"net"
"encoding/binary"
"encoding/json"
"chatroom/common/message"
)
// 这里将这些方法关联到结构体中
type Transfer struct {
// 分析他应该有哪些字段
Conn net.Conn
Buf [8064]byte //这是传输时使用的缓冲
}
func(this *Transfer) ReadPkg() (mes message.Message, err error) {
// buf := make([]byte, 8096)
fmt.Println("读取客户端发送的数据~~~")
//conn.Read 在conn没有被关闭的情况下,才会阻塞
// 如果客户端关闭了conn,则就不会阻塞了
_, err = this.Conn.Read(this.Buf[:4])
if err != nil {
return
}
//根据读到的长度buf[:4] 转成一个uint32类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
// 根据pkgLen读取内容
n, err := this.Conn.Read(this.Buf[:pkgLen])
if n != int(pkgLen) || err != nil {
return
}
// 把pkgLen反序列化成 -> message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarshal fail err=", err)
return
}
return
}
func(this *Transfer) WritePkg(data []byte) (err error){
// 先发送一个长度给对方
var pkgLen uint32
pkgLen = uint32(len(data))
// var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
// 发送长度
n, err := this.Conn.Write(this.Buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write fail", err)
return
}
// 发送data本身
n, err = this.Conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write fail", err)
return
}
return
}
客户端
框架图
client/main/main.go
package main
import (
"fmt"
"os"
"chatroom/client/process"
)
// 定义两个变量,一个表示用户的ID, 一个表示用户密码
var userId int
var userPwd string
func main() {
// 接收用户选择
var key int
// 判断是否还继续显示菜单
// var loop = true
for 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)
case 2 :
fmt.Println("注册用户")
// loop = false
case 3 :
fmt.Println("退出系统")
os.Exit(0)
default :
fmt.Println("你的输入有误,请重新输入")
}
}
// 根据用户的输入,显示新的提示信息
// if key ==1 {
// // 说明用户要登陆
// // 因为使用了分层的结构,
// //先把登陆的函数,写到另一个文件
// // 这里我们会需要重新调用
// // login(userId, userPwd)
// // if err != nil {
// // fmt.Println("登陆失败")
// // } else {
// // fmt.Println("登陆成功")
// // }
// } else if key ==2 {
// fmt.Println("进行用户注册的逻辑。。。。")
// }
}
client/process/server.go
package process
import (
"fmt"
"os"
"net"
"chatroom/client/utils"
)
// 显示登陆成功后的界面...
func ShowMenu() {
fmt.Println("----------恭喜登陆成功---------")
fmt.Println("-------1、显示在线用户列表------")
fmt.Println("-------2、发送消息------")
fmt.Println("-------3、信息列表------")
fmt.Println("-------4、退出系统------")
fmt.Println("请选择1-4")
var key int
fmt.Scanf("%d\n", &key)
switch key {
case 1 :
fmt.Println("1、显示在线用户列表")
case 2 :
fmt.Println("2、发送消息")
case 3 :
fmt.Println("3、信息列表")
case 4 :
fmt.Println("4、退出系统")
os.Exit(0)
default :
fmt.Println("你输入的选项不正确")
}
}
// 和服务器端保持通讯的协程
func serverProcessMes(Conn net.Conn){
// 创建transfer实例,不停的读取服务器发送的消息
tf := &utils.Transfer{
Conn : Conn,
}
for {
// 客户端不停的读取
fmt.Println("客户端正在等待读取服务器发送的消息")
mes, err := tf.ReadPkg()
if err != nil {
fmt.Println("RedPkg err=", err)
return
}
// 如果读取到了消息,下一步处理
fmt.Printf("mes=%v", mes)
}
}
client/process/userprocess.go
package process
import (
"fmt"
"net"
"encoding/binary"
"encoding/json"
"chatroom/common/message"
"chatroom/client/utils"
)
type UserProcess struct {
//暂时不需要字段
}
// 给关联一个用户登陆方法
// 写一个函数,完成登陆
func (this *UserProcess) Login(userId int, userPwd string) (err error) {
// 下一步就要开始定协议。。。
// fmt.Printf(" userID=%d userPwd=%s", userID, userPwd)
// return nil
//1、连接到feu武器
conn, err := net.Dial("tcp", "localhost:8899")
if err != nil {
fmt.Println("net.Dial err", err)
return
}
//延时关闭
defer conn.Close()
// 2、准备通过conn发送消息给服务器
var mes message.Message
mes.Type = message.LoginMesType
// 3、创建一个LoginMes
var loginMes message.LoginMes
loginMes.UserId = userId
loginMes.UserPwd = userPwd
// 4、将loginMes序列化
data, err := json.Marshal(loginMes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//5、将data赋给 mes.Data字段
mes.Data = string(data)
// 6、将mes进行序列化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//7、此时data就是我们要发送的消息
// 7.1 先把data的长度发生给服务器
// 先获取到data的长度->转换成一个表示长度的byte切片
var pkgLen uint32
pkgLen = uint32(len(data))
var buf [4]byte
binary.BigEndian.PutUint32(buf[0:4], pkgLen)
// 发送长度
n, err := conn.Write(buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write fail", err)
return
}
// fmt.Printf("客户端,发送消息长度成功=%d 内容是=%s", len(data), string(data))
// 发送消息本身
_, err = conn.Write(data)
if err != nil {
fmt.Println("conn.Write(data) fail", err)
return
}
//休眠20
// time.Sleep(20 * time.Second)
// fmt.Println("休眠了20秒")
// 这里还需要处理服务器端的消息
// 创建Transfer实例
tf := &utils.Transfer{
Conn : conn,
}
mes, err = tf.ReadPkg() //mes就是
if err != nil {
fmt.Println("readPkg err=", err)
return
}
// 将mes的Data部分反序列化成 LoginResMes
var loginResMes message.LoginResMes
err = 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
}
client/utils/utils.go
package utils
import (
"fmt"
"net"
"encoding/binary"
"encoding/json"
"chatroom/common/message"
)
// 这里将这些方法关联到结构体中
type Transfer struct {
// 分析他应该有哪些字段
Conn net.Conn
Buf [8064]byte //这是传输时使用的缓冲
}
func(this *Transfer) ReadPkg() (mes message.Message, err error) {
// buf := make([]byte, 8096)
fmt.Println("读取客户端发送的数据~~~")
//conn.Read 在conn没有被关闭的情况下,才会阻塞
// 如果客户端关闭了conn,则就不会阻塞了
_, err = this.Conn.Read(this.Buf[:4])
if err != nil {
return
}
//根据读到的长度buf[:4] 转成一个uint32类型
var pkgLen uint32
pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
// 根据pkgLen读取内容
n, err := this.Conn.Read(this.Buf[:pkgLen])
if n != int(pkgLen) || err != nil {
return
}
// 把pkgLen反序列化成 -> message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarshal fail err=", err)
return
}
return
}
func(this *Transfer) WritePkg(data []byte) (err error){
// 先发送一个长度给对方
var pkgLen uint32
pkgLen = uint32(len(data))
// var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
// 发送长度
n, err := this.Conn.Write(this.Buf[0:4])
if n != 4 || err != nil {
fmt.Println("conn.Write fail", err)
return
}
// 发送data本身
n, err = this.Conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write fail", err)
return
}
return
}
如果输入的用户名和密码正确在Redis中存在则登陆,否则退出系统,并给出相应的提示信息:
1、用户不存在,你也可以重新注册,再登录
2、你的密码错误
代码实现:编写了 server/model/user.go
package model
// 定义一个用户结构体
type User struct {
// 确定字段信息
// 为了序列化和反序列化成功,我们必须保证
// 用户信息的json字符串 和 结构体的字段对应的 tag名字 必须一致
UserId int `json:"userId"`
UserPwd string `json:"userPwd"`
UserName string `json:"userName"`
}
编写了 server/model/error.go
package model
import (
"errors"
)
// 根据业务逻辑的需要,自定义一些错误
var (
ERROR_USER_NOEXISTS = errors.New("该用户不存在")
ERROR_USER_EXISTS = errors.New("用户已经存在")
ERROR_USER_PWD = errors.New("用户密码错误")
)
编写了 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实例
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 哈希中,没有找到对应id
err = ERROR_USER_NOEXISTS
}
return
}
user = &User{}
// 需要把 res反序列化成user实例
err = json.Unmarshal([]byte(res), &user)
if err != nil {
fmt.Println("json.Unmarshal err=", err)
return
}
return
}
// 2、完成登陆的校验 Login
// ①、Login 完成对用户的验证
// ②、如果用户的id和pwd都正确,则返回一个user实例
// ③、如果用户的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_PWD
return
}
return
}
编写server/main/redis.go
package main
import (
"github.com/garyburd/redigo/redis"
"time"
)
// 定义一个全局pool
var pool *redis.Pool
func initPool(address string, maxIdle, maxActive int, idleTimeout time.Duration) {
pool = &redis.Pool{
MaxIdle:maxIdle, //最大空闲连接数
MaxActive:maxActive, //表示和数据库最大连接数,0表示没有限制
IdleTimeout:idleTimeout, //最大空闲时间
Dial:func()(redis.Conn,error){ //初始化连接的代码
return redis.Dial("tcp","localhost:6379")
}
}
}
server/main/main.go 新增代码
package main
import (
"fmt"
"net"
"time"
"chatroom/server/model"
)
//处理和客户端的通讯
func process(conn net.Conn) {
//这里需要延迟关闭conn
defer conn.Close()
// 这里调用总控创建一个总控
processor := &Processor{
Conn : conn,
}
err := processor.process2()
if err != nil {
fmt.Println("客户端和服务器端的通讯协程出现问题 err=", err)
return
}
}
// 这里编写一个函数,完成对UserDao初始化任务
func initUserDao() {
// 这里的pool本身就是一个全局的变量
// 需要注意一个初始化顺序问题
// 先initPool 再 initUserDao
model.MyUserDao = model.NewUserDao(pool)
}
func main() {
// 当服务器启动时,我们就去初始化Redis连接池
initPool("localhost:6379", 16, 0, 300 * time.Second)
initUserDao()
//提示信息
fmt.Println("服务器[新的结构]在8899监听。。。。")
listen, err := net.Listen("tcp", "0.0.0.0:8899")
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("liten Accept err=", err)
}
//一旦连接成功,则启动一个协程和客户端保持通讯。
go process(conn)
}
}
在server/process/userProcess.go 使用到redis的验证功能,新增代码
package process2
import (
"fmt"
"net"
"encoding/json"
"chatroom/common/message"
"chatroom/server/utils"
"chatroom/server/model"
)
type UserProcess struct {
// 字段
Conn net.Conn
}
// 编写一个函数serverProcessLogin函数,专门处理登陆请求
func(this *UserProcess) ServerProcessLogin(mes *message.Message) (err error){
// 核心代码。。。
// 1、先从mes中取出mes.Data,并直接反序列化成LoginMes
var loginMes message.LoginMes
err = json.Unmarshal([]byte(mes.Data), &loginMes)
if err != nil {
fmt.Println("json.Unmashal fail err=", err)
return
}
// ① 先声明一个resMes
var resMes message.Message
resMes.Type = message.LoginResMesType
// ② 再声明一个 loginResMes,并完成赋值
var loginResMes message.LoginResMes
// 需要到redis数据库完成验证
// 1、使用model.MyUserDao到Redis验证
user, err := model.MyUserDao.Login(loginMes.UserId, loginMes.UserPwd)
if err != nil {
if err == model.ERROR_USER_NOEXISTS {
loginResMes.Code = 500
loginResMes.Error = err.Error()
} else if err == model.ERROR_USER_PWD {
loginResMes.Code = 300
loginResMes.Error = err.Error()
} else {
loginResMes.Code = 403
loginResMes.Error = "未知错误"
}
} else {
loginResMes.Code = 200
fmt.Println(user, "登陆成功")
}
// //如果用户的id为100,密码为123456,认为合法, 否则 不合法
// if loginMes.UserId == 100 && loginMes.UserPwd == "123456" {
// // 合法
// loginResMes.Code = 200
// } else {
// //不合法
// loginResMes.Code = 500 //500状态码,表示该用户不存在
// loginResMes.Error = "该用户不存在,请注册后再使用"
// }
// ③ 将 loginResMes 序列化
data, err := json.Marshal(loginResMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ④ 将data 赋值给resMes
resMes.Data = string(data)
// ⑤ 对resMes 进行序列化,准备发送
data, err = json.Marshal(resMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ⑥ 发送data,将其封装到writePkg函数
// 因为使用了分层的模式,先创建Transfer实例,然后读取
tf := &utils.Transfer{
Conn : this.Conn,
}
err = tf.WritePkg(data)
return
}
client/process/userprocess.go
func (this *UserProcess) Register(userId int, userPwd string, userName string) (err error) {
//1、连接到服务器
conn, err := net.Dial("tcp", "localhost:8899")
if err != nil {
fmt.Println("net.Dial err", err)
return
}
//延时关闭
defer conn.Close()
// 2、准备通过conn发送消息给服务器
var mes message.Message
mes.Type = message.RegisterMesType
// 3、创建一个LoginMes
var registerMes message.RegisterMes
registerMes.User.UserId = userId
registerMes.User.UserPwd = userPwd
registerMes.User.UserName = userName
// 4、将RegisterMes序列化
data, err := json.Marshal(registerMes)
if err != nil {
fmt.Println("json.Marshal err=", err)
return
}
//5、将data赋给 mes.Data字段
mes.Data = string(data)
// 6、将mes进行序列化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal err=", 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就是RegisterResMes
if err != nil {
fmt.Println("readPkg err=", err)
return
}
// 将mes的Data部分反序列化成 ResterResMes
var registerResMes message.RegisterResMes
err = json.Unmarshal([]byte(mes.Data), ®isterResMes)
if registerResMes.Code == 200 {
fmt.Println("注册成功,你重新登陆一把")
os.Exit(0)
} else {
fmt.Println(registerResMes.Error)
os.Exit(0)
}
return
}
server/model/userDao.go 增加了方法
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_EXISTS
return
}
// 这时,说明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
}
在server/process/userProcess.go 增加了方法,处理注册
func (this *UserProcess) ServerProcessRegister(mes *message.Message) (err error){
// 1、先从mes中取出mes.Data,并直接反序列化成RegisterMes
var registerMes message.RegisterMes
err = json.Unmarshal([]byte(mes.Data), ®isterMes)
if err != nil {
fmt.Println("json.Unmashal fail err=", err)
return
}
// ① 先声明一个resMes
var resMes message.Message
resMes.Type = message.RegisterResMesType
// ② 再声明一个 loginResMes,并完成赋值
var registerResMes message.RegisterResMes
// 需要到redis数据库完成注册
// 1、使用model.MyUserDao到Redis验证
err = model.MyUserDao.Register(®isterMes.User)
if err != nil {
if err == model.ERROR_USER_EXISTS {
registerResMes.Code = 505
registerResMes.Error = model.ERROR_USER_EXISTS.Error()
} else {
registerResMes.Code = 506
registerResMes.Error = "注册发生未知错误"
}
} else {
registerResMes.Code = 200
}
data, err := json.Marshal(registerResMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ④ 将data 赋值给resMes
resMes.Data = string(data)
// ⑤ 对resMes 进行序列化,准备发送
data, err = json.Marshal(resMes)
if err != nil {
fmt.Println("json.Marshal fail", err)
return
}
// ⑥ 发送data,将其封装到writePkg函数
// 因为使用了分层的模式,先创建Transfer实例,然后读取
tf := &utils.Transfer{
Conn : this.Conn,
}
err = tf.WritePkg(data)
return
}
server/main/processor.go 调用了方法
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 :
// 处理注册
up := &process2.UserProcess{
Conn : this.Conn,
}
err = up.ServerProcessRegister(mes) //type : data
default :
fmt.Println("消息类型不存在,无法处理。。。。")
}
return
}
实现功能- 完成登陆时能返回当前在线用户
编写了 server/process/userMgr.go
package process2
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) AddonlineUserup(up *UserProcess){
this.onlineUsers[up.UserId] = up
}
// 删除
func (this *UserMgr) DelonlineUserup(userId int){
delete(this.onlineUsers, userId)
}
//返回当前所有在线用户
func (this *UserMgr) DelonlineUserupmap() map[int]*UserProcess{
return this.onlineUsers
}
// 根据ID返回对应的值
func (this *UserMgr) GetOnlienUserById(userId int) (up *UserProcess, err error) {
// 如何从map取出一个值,带检测的方式
up, ok := this.onlineUsers[userId]
if !ok { //说明你要查找的用户,当前不在线
err = fmt.Errorf("用户%d 不存在", userId)
return
}
return
}
client/process/userMg.go
package process
import (
"fmt"
"chatroom/common/message"
)
// 客户端要维护的map
var onlineUsers map[int]*message.User = make(map[int]*message.User, 10)
// 在客户端显示在线用户
func outputOnlineUser(){
// 遍历 一把 onlineUsers
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
outputOnlineUser()
}
感觉实现的不是很完美,还有一下些功能需要该进。留着给有需要的人改进了偷个懒。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)