一 客户端目录结构图
二 model 包
chatroom\client\model\curUser.go
package model
import (
"chatroom/common/message"
"net"
)
// 因为在客户端,我们很多地方会使用到 curUser,所以将其定义为一个全局
type CurUser struct {
Conn net.Conn
message.User
}
三 utils 包
chatroom\client\utils\utils.go
package utils
import (
"chatroom/common/message"
"encoding/binary"
"encoding/json"
"fmt"
"net"
)
// 将这些方法关联到 Transfer 结构体中
type Transfer struct {
// 连接
Conn net.Conn
// 传输数据的缓冲
Buf [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 {
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
}
// 反序列化成 message.Message
err = json.Unmarshal(this.Buf[:pkgLen], &mes)
if err != nil {
fmt.Println("json.Unmarsha err=", err)
return
}
return
}
// 写消息
func (this *Transfer) WritePkg(data []byte) (err error) {
// 先发送一个长度给对方
var pkgLen uint32
pkgLen = uint32(len(data))
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
// 发送长度
n, err := this.Conn.Write(this.Buf[:4])
if n != 4 || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
// 发送 data 本身
n, err = this.Conn.Write(data)
if n != int(pkgLen) || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
return
}
四 process 包
chatroom\client\process\server.go
package process
import (
"chatroom/client/utils"
"chatroom/common/message"
"encoding/json"
"fmt"
"net"
"os"
)
// 显示登录成功后的界面
func ShowMenu() {
fmt.Println("-------恭喜登录成功---------")
fmt.Println("-------1 显示在线用户列表---------")
fmt.Println("-------2 发送消息---------")
fmt.Println("-------3 信息列表---------")
fmt.Println("-------4 退出系统---------")
fmt.Println("请选择(1-4):")
var key int
var content string
// 因为总会使用到 SmsProcess 实例,因此将其定义在 swtich 外部
smsProcess := &SmsProcess{}
fmt.Scanf("%d\n", &key)
switch key {
case 1:
// 显示在线用户列表
outputOnlineUser()
case 2:
fmt.Println("想对大家说点什么:)")
fmt.Scanf("%s\n", &content)
// 群发消息
smsProcess.SendGroupMes(content)
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.Println("客户端正在等待读取服务器发送的消息")
mes, err := tf.ReadPkg()
if err != nil {
fmt.Println("tf.ReadPkg err=", err)
return
}
// 如果读取到消息
switch mes.Type {
case message.NotifyUserStatusMesType: // 有人上线了
// 1 取出 NotifyUserStatusMes
var notifyUserStatusMes message.NotifyUserStatusMes
json.Unmarshal([]byte(mes.Data), ¬ifyUserStatusMes)
// 2 把这个用户的信息,状态保存到客户 map[int]User 中
updateUserStatus(¬ifyUserStatusMes)
case message.SmsMesType: // 有人群发消息
outputGroupMes(&mes)
default:
fmt.Println("服务器端返回了未知的消息类型")
}
}
}
chatroom\client\process\smsMgr.go
package process
import (
"chatroom/common/message"
"encoding/json"
"fmt"
)
// 显示群发消息
func outputGroupMes(mes *message.Message) { // 这个地方 mes 一定 SmsMes
// 1 反序列化 mes.Data 成 SmsMes
var smsMes message.SmsMes
err := json.Unmarshal([]byte(mes.Data), &smsMes)
if err != nil {
fmt.Println("json.Unmarshal err=", err.Error())
return
}
// 显示信息
info := fmt.Sprintf("用户id:\t%d 对大家说:\t%s",
smsMes.UserId, smsMes.Content)
fmt.Println(info)
fmt.Println()
}
chatroom\client\process\smsProcess.go
package process
import (
"chatroom/client/utils"
"chatroom/common/message"
"encoding/json"
"fmt"
)
type SmsProcess struct {
}
// 客户端发送群聊的消息
func (this *SmsProcess) SendGroupMes(content string) (err error) {
// 1 创建一个Mes
var mes message.Message
mes.Type = message.SmsMesType
// 2 创建一个 SmsMes 实例
var smsMes message.SmsMes
smsMes.Content = content // 消息内容
smsMes.UserId = CurUser.UserId // 用户Id
smsMes.UserStatus = CurUser.UserStatus // 用户状态
// 3 序列化 smsMes
data, err := json.Marshal(smsMes)
if err != nil {
fmt.Println("SendGroupMes json.Marshal fail =", err.Error())
return
}
mes.Data = string(data)
// 4 对 mes 再次序列化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("SendGroupMes json.Marshal fail =", err.Error())
return
}
// 5 创建一个 Transfer 实例
tf := &utils.Transfer{
Conn: CurUser.Conn,
}
//6 将 mes 发送给服务器
err = tf.WritePkg(data)
if err != nil {
fmt.Println("SendGroupMes err=", err.Error())
return
}
return
}
chatroom\client\process\userMgr.go
package process
import (
"chatroom/client/model"
"chatroom/common/message"
"fmt"
)
// 客户端要维护的在线用户的 map
var onlineUsers map[int]*message.User = make(map[int]*message.User, 10)
var CurUser model.CurUser // 在用户登录成功后,完成对 CurUser 初始化
// 在客户端显示当前在线的用户
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()
}
chatroom\client\process\userProcess.go
package process
import (
"chatroom/client/utils"
"chatroom/common/message"
"encoding/binary"
"encoding/json"
"fmt"
"net"
"os"
)
type UserProcess struct {
// 暂时不需要字段
}
// 注册
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 准备要发送的消息
var mes message.Message
mes.Type = message.RegisterMesType
// 3 创建一个 RegisterMes 结构体
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(conn) err=", err)
return
}
// 将 mes 的 Data 部分反序列化成 RegisterResMes
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
}
// 用户登录
func (this *UserProcess) Login(userId int, userPwd 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.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[:4])
if n != 4 || err != nil {
fmt.Println("conn.Write(bytes) fail", err)
return
}
fmt.Printf("客户端,发送消息的长度=%d 内容=%s", len(data), string(data))
// 7.1 再把 data 的本身发送给服务器
_, err = conn.Write(data)
if err != nil {
fmt.Println("conn.Write(data) fail", err)
return
}
// 创建一个Transfer 实例
tf := &utils.Transfer{
Conn: conn,
}
mes, err = tf.ReadPkg() // mes 就是返回的消息
if err != nil {
fmt.Println("readPkg(conn) err=", err)
return
}
// 将 mes 的 Data 部分反序列化成 LoginResMes
var loginResMes message.LoginResMes
err = json.Unmarshal([]byte(mes.Data), &loginResMes)
if loginResMes.Code == 200 {
// 初始化 CurUser
CurUser.Conn = conn
CurUser.UserId = userId
CurUser.UserStatus = message.UserOnline
//fmt.Println("登录成功")
//可以显示当前在线用户列表,遍历loginResMes.UsersId
fmt.Println("当前在线用户列表如下:")
for _, v := range loginResMes.UsersId {
//如果我们要求不显示自己在线,下面我们增加一个代码
if v == userId {
continue
}
fmt.Println("用户id:\t", v)
//完成 客户端的 onlineUsers 完成初始化
user := &message.User{
UserId: v,
UserStatus: message.UserOnline,
}
onlineUsers[v] = user
}
fmt.Print("\n\n")
//这里我们还需要在客户端启动一个协程
//该协程保持和服务器端的通讯.如果服务器有数据推送给客户端
//则接收并显示在客户端的终端.
go serverProcessMes(conn)
//1. 显示我们的登录成功的菜单[循环]..
for {
ShowMenu()
}
} else {
fmt.Println(loginResMes.Error)
}
return
}
五 main 包
chatroom\client\main\main.go
package main
import (
"chatroom/client/process"
"fmt"
"os"
)
// 用户 id
var userId int
// 用户密码
var userPwd string
// 用户名
var userName string
func main() {
// 接收用户的选择
var key int
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)
// 创建一个 UserProcess 的实例
up := &process.UserProcess{}
// 用户登录处理
up.Login(userId, userPwd)
case 2:
fmt.Println("注册用户")
fmt.Println("请输入用户id:")
fmt.Scanf("%d\n", &userId)
fmt.Println("请输入用户密码:")
fmt.Scanf("%s\n", &userPwd)
fmt.Println("请输入用户名字(nickname):")
fmt.Scanf("%s\n", &userName)
//2 调用UserProcess,完成注册的请求
up := &process.UserProcess{}
up.Register(userId, userPwd, userName)
case 3:
fmt.Println("退出系统")
os.Exit(0)
default:
fmt.Println("你的输入有误,请重新输入")
}
}
}