go 实现多人聊天系统客户端代码实现

一 客户端目录结构图

二 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), &notifyUserStatusMes)
         // 2 把这个用户的信息,状态保存到客户 map[int]User 中
         updateUserStatus(&notifyUserStatusMes)
      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), &registerResMes)
   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("你的输入有误,请重新输入")
      }
   }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值