需求分析->设计阶段->编码实现->测试阶段->实施阶段
(1).用户注册
(2).用户登录
(3).显示在线用户列表
(4).群聊(广播)
(5).点对点聊天
(6).离线留言
项目要保存用户信息和消息数据,因此需要数据库(mysql或者redis),这里使用redis进行数据的保存
功能:
能够正确地显示客户端的菜单
界面如下:
代码如下:
client/login.go
- package main
-
- import (
- "fmt"
- )
-
- //写一个函数, 完成登录
- func login(userId int, userPwd string) (err error) {
- //下一步 开始定协议
- fmt.Printf("userId = %d, userPwd = %s \n", userId, userPwd)
- 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\t 1 登录聊天室")
- fmt.Println("\t\t\t\t 2 注册用户")
- fmt.Println("\t\t\t\t 3 退出系统")
- fmt.Println("\t\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)
- //先把登录的函数写到另一个文件,比如:login.go
- err := login(userId, userPwd)
- if err != nil {
- fmt.Println("登录失败")
- } else {
- fmt.Println("登录成功")
- }
- } else if key == 2{
- fmt.Println("注册")
- }
- }
要求:
先完成指定用户的验证,用户 id = 100 ,密码 pw =123456 可以登录,其它用户不能登录, 这里需要先说明一个 Message的组成(示意图),并发送一个 Message 的流程
1).完成客户端可以发送消息长度,服务器端可以正常收到该长度值
分析思路
(1).先确定消息 Message 的格式和结构
(2).根据上图的分析完成代码
(3).示意图如下
server/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 err != nil || n != 4 {
- fmt.Printf(" conn read err = %v\n", err)
- return
- }
- fmt.Printf("读到的buf= %v\n", buf[:4])
- }
- }
-
- func main() {
- //提示信息
- fmt.Println("服务器正在监听8889端口...")
- listen, err := net.Listen("tcp", "127.0.0.1:8889")
- //这里需要延时关闭
- defer listen.Close()
-
- if err != nil {
- fmt.Printf("net listen err = %v\n", err)
- return
- }
- //一旦监听成功,就等待客户端来连接服务器
- for {
- fmt.Println("等待客户端来连接服务器...")
- conn, err := listen.Accept()
- if err != nil {
- fmt.Printf("listen accept err = %v\n", err)
- return
- }
- //一旦连接成功,则启动一个协程,保持和客户端通讯
- go process(conn)
- }
- }
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"` //返回状态码: 200 登录成功, 500 用户未注册
- Error string `json:"error"` //返回错误信息
- }
client/main.go 和前面代码一样,没有发生修改
clent/login.go
- package main
-
- import (
- "fmt"
- "net"
- "encoding/binary"
- "encoding/json"
- "go_code/chatroom/common/message"
- )
-
- //写一个函数, 完成登录
- func login(userId int, userPwd string) (err error) {
- // //下一步 开始定协议
- // fmt.Printf("userId = %d, userPwd = %s \n", userId, userPwd)
- // return
-
- //1.连接到服务器端
- conn, err := net.Dial("tcp", "127.0.0.1:8889")
- if err != nil {
- fmt.Printf("net dial err = %v\n", 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.Printf("json marshal err = %v\n", err)
- return
- }
- //5.将序列化后的loginMes byte切片赋给mes.Data
- mes.Data = string(data)
- //6.将mes序列化
- data, err = json.Marshal(mes)
- if err != nil {
- fmt.Printf("json marshal err = %v\n", err)
- return
- }
- //7.这个时候data就是要发送的消息
- //7.1先把data的长度发送给服务器
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- var buf [4]byte
- binary.BigEndian.PutUint32(buf[0:4], pkgLen)
- //发送长度
- n, err := conn.Write(buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
-
- fmt.Printf("客户端发送消息长度=%d,内容=%s\n", len(data), string(data))
- return
- }
2).完成客户端可以发送消息本身,服务器端可以正常接收到消息,并根据客户端发送的消息(LoginMes),判断用户的合法性,并返回相应的LoginResMes
思路分析:
(1).让客户端发送消息本身
(2).服务器端接受到消息,然后反序列话化成对应的消息结构体
(3).服务器端根据反序列化成对应的消意,判断登录用户是合法,返回LoginResMes
(4).客户端解析返回的LoginResMes ,显示对应界面
(5).这里需要做函数的封装
client/login.go做了点修改
-
- // fmt.Printf("客户端发送消息长度=%d,内容=%s\n", len(data), string(data))
- //发送消息本身
- _, err = conn.Write(data)
- if err != nil {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
-
- //休眠20s
- time.Sleep(20 * time.Second)
- fmt.Println("休眠20s...")
- //处理服务端返回的消息
-
- return
- }
server/main.go做了改动
- package main
-
- import (
- "fmt"
- "net"
- "go_code/chatroom/common/message"
- "encoding/binary"
- "encoding/json"
- _"errors"
- "io"
- )
-
- //处理和客户端通讯
- func process(conn net.Conn) {
- //这里需要延时关闭
- defer conn.Close()
-
- //读取客户端发送的消息
- for {
- //将读取数据包直接封装成一个函数readPkg(),返回messag,error
- mes, err := readPkg(conn)
- if err != nil {
- if err == io.EOF {
- fmt.Println("客户端退出了,服务器端也跟着退出")
- } else {
- fmt.Printf(" readPkg err = %v\n", err)
- }
-
- return
- }
- fmt.Printf("mes= %v\n", mes)
- }
- }
-
- //读取数据包直接封装成一个函数readPkg()
- func readPkg(conn net.Conn) (mes message.Message, err error) {
- //创建一个切片,用于后续conn.Read()
- buf := make([]byte, 8096)
- fmt.Println("读取客户端发送的数据...")
- //conn.Read在conn没有被关闭的情况下,才会阻塞
- //如果客户端关闭了conn,则不会阻塞
- _, err = conn.Read(buf[:4])
- if err != nil {
- // err = errors.New("read pkg header error")
- 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 {
- // err = errors.New("read pkg body error")
- return
- }
- //把pkgLen反序列化成message.Message
- err = json.Unmarshal(buf[:pkgLen], &mes)
- if err != nil {
- // err = errors.New("read pkg json.Unmarshal error")
- return
- }
- return
- }
-
- func main() {
- //提示信息
- fmt.Println("服务器正在监听8889端口...")
- listen, err := net.Listen("tcp", "127.0.0.1:8889")
- //这里需要延时关闭
- defer listen.Close()
-
- if err != nil {
- fmt.Printf("net listen err = %v\n", err)
- return
- }
- //一旦监听成功,就等待客户端来连接服务器
- for {
- fmt.Println("等待客户端来连接服务器...")
- conn, err := listen.Accept()
- if err != nil {
- fmt.Printf("listen accept err = %v\n", err)
- return
- }
- //一旦连接成功,则启动一个协程,保持和客户端通讯
- go process(conn)
- }
- }
能够完成登录,并提示相应信息
server/server.go修改
- package main
-
- import (
- "fmt"
- "net"
- "go_code/chatroom/common/message"
- "encoding/binary"
- "encoding/json"
- _"errors"
- "io"
- )
-
- //处理和客户端通讯
- func process(conn net.Conn) {
- //这里需要延时关闭
- defer conn.Close()
- //读取客户端发送的消息
- for {
- //将读取数据包直接封装成一个函数readPkg(),返回messag,error
- mes, err := readPkg(conn)
- if err != nil {
- if err == io.EOF {
- fmt.Println("客户端退出了,服务器端也跟着退出")
- } else {
- fmt.Printf(" readPkg err = %v\n", err)
- }
- return
- }
- err = serverProcessMes(conn, &mes)
- if err != nil {
- return
- }
- }
- }
-
- //编写一个函数serverProcessLogin函数,专门处理登录请求
- func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) {
- //先从mes中取出mes.Data,并直接反序列化成LoginMes
- var loginMes message.LoginMes
- err = json.Unmarshal([]byte(mes.Data), &loginMes)
- if err != nil {
- fmt.Printf("json.Unmarshal fail, err = %v\n", err)
- return
- }
- //1.声明一个resMes
- var resMes message.Message
- resMes.Type = message.LoginResMesType
- //2.再声明一个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 = "该用户不存在,请注册后使用"
- }
- //3.将loginResMes序列化
- data, err := json.Marshal(loginResMes)
- if err != nil {
- fmt.Printf("json.Marshal fail, err = %v\n", err)
- return
- }
- //4.将data赋值给resMes
- resMes.Data = string(data)
- //5.对resMes序列化,准备发送
- data, err = json.Marshal(resMes)
- if err != nil {
- fmt.Printf("json.Marshal fail, err = %v\n", err)
- return
- }
- //6.发送data,将其封装到writePkg函数中
- err = writePkg(conn, data)
- return
- }
-
- func writePkg(conn net.Conn, data []byte) (err error) {
- //先发送一个长度给对方
- //先把data的长度发送给对方
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- var buf [4]byte
- binary.BigEndian.PutUint32(buf[0:4], pkgLen)
- //发送长度
- n, err := conn.Write(buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
- //发送data本身
- n, err = conn.Write(data)
- if err != nil || n != int(pkgLen) {
- fmt.Printf(" conn write err = %v\n", err)
- }
- 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
- }
-
- //读取数据包直接封装成一个函数readPkg()
- func readPkg(conn net.Conn) (mes message.Message, err error) {
- //创建一个切片,用于后续conn.Read()
- 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 {
- // err = errors.New("read pkg body error")
- return
- }
- //把pkgLen反序列化成message.Message
- err = json.Unmarshal(buf[:pkgLen], &mes)
- if err != nil {
- return
- }
- return
- }
-
- func main() {
- //提示信息
- fmt.Println("服务器正在监听8889端口...")
- listen, err := net.Listen("tcp", "127.0.0.1:8889")
- //这里需要延时关闭
- defer listen.Close()
- if err != nil {
- fmt.Printf("net listen err = %v\n", err)
- return
- }
- //一旦监听成功,就等待客户端来连接服务器
- for {
- fmt.Println("等待客户端来连接服务器...")
- conn, err := listen.Accept()
- if err != nil {
- fmt.Printf("listen accept err = %v\n", err)
- return
- }
- //一旦连接成功,则启动一个协程,保持和客户端通讯
- go process(conn)
- }
- }
client/utils/utils.go
- package main
-
- import (
- "fmt"
- "net"
- "encoding/binary"
- "encoding/json"
- "go_code/chatroom/common/message"
- )
-
- //读取数据包直接封装成一个函数readPkg()
- func readPkg(conn net.Conn) (mes message.Message, err error) {
- //创建一个切片,用于后续conn.Read()
- buf := make([]byte, 8096)
- fmt.Println("读取客户端发送的数据...")
- //conn.Read在conn没有被关闭的情况下,才会阻塞
- //如果客户端关闭了conn,则不会阻塞
- _, err = conn.Read(buf[:4])
- if err != nil {
- // err = errors.New("read pkg header error")
- 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 {
- // err = errors.New("read pkg body error")
- return
- }
- //把pkgLen反序列化成message.Message
- err = json.Unmarshal(buf[:pkgLen], &mes)
- if err != nil {
- // err = errors.New("read pkg json.Unmarshal error")
- return
- }
- return
- }
-
- func writePkg(conn net.Conn, data []byte) (err error) {
- //先发送一个长度给对方
- //先把data的长度发送给对方
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- var buf [4]byte
- binary.BigEndian.PutUint32(buf[0:4], pkgLen)
- //发送长度
- n, err := conn.Write(buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
- //发送data本身
- n, err = conn.Write(data)
- if err != nil || n != int(pkgLen) {
- fmt.Printf(" conn write err = %v\n", err)
- }
- return
- }
client/client.go
- package main
-
- import (
- "fmt"
- "net"
- _"time"
- "encoding/binary"
- "encoding/json"
- "go_code/chatroom/common/message"
- )
-
- //写一个函数, 完成登录
- func login(userId int, userPwd string) (err error) {
- // //下一步 开始定协议
- // fmt.Printf("userId = %d, userPwd = %s \n", userId, userPwd)
- // return
-
- //1.连接到服务器端
- conn, err := net.Dial("tcp", "127.0.0.1:8889")
- if err != nil {
- fmt.Printf("net dial err = %v\n", 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.Printf("json marshal err = %v\n", err)
- return
- }
- //5.将序列化后的loginMes byte切片赋给mes.Data
- mes.Data = string(data)
- //6.将mes序列化
- data, err = json.Marshal(mes)
- if err != nil {
- fmt.Printf("json marshal err = %v\n", err)
- return
- }
- //7.这个时候data就是要发送的消息
- //7.1先把data的长度发送给服务器
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- var buf [4]byte
- binary.BigEndian.PutUint32(buf[0:4], pkgLen)
- //发送长度
- n, err := conn.Write(buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
- // fmt.Printf("客户端发送消息长度=%d,内容=%s\n", len(data), string(data))
- //发送消息本身
- _, err = conn.Write(data)
- if err != nil {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
-
- //休眠20s
- // time.Sleep(20 * time.Second)
- // fmt.Println("休眠20s...")
- //处理服务端返回的消息
- mes, err = readPkg(conn)
- 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 err != nil {
- fmt.Println("json.Unmarshal err = ", err)
- return
- }
- if loginResMes.Code == 200 {
- fmt.Println("登录成功")
- } else if loginResMes.Code == 500 {
- fmt.Println(loginResMes.Error)
- }
-
- return
- }
程序结构的改进,前面的程序虽然完成了功能,但是没有结构,系统的可读性、扩展性和维护性都不好,因此需要对程序的结构进行改进
1).先改进服务端.先画出程序的框架图,再写代码
2).步骤
(1).先把分析出来的文件,创建好,然后把对应的代码放入到相应的文件夹(包)
(2).根据各个文件,完成的任务不同,将main.go的代码剥离到对应的文件中即可
server/main/main.go
- package main
-
- import (
- "fmt"
- "net"
- )
-
- //处理和客户端通讯
- func process(conn net.Conn) {
- //这里需要延时关闭
- defer conn.Close()
- //这里调用总控,创建一个总控实例
- processor := &Processor{
- Conn: conn,
- }
- err := processor.ProcessMain()
- if err != nil {
- fmt.Printf("客户端和服务器端的协程出问题了,err= %v\n", err)
- return
- }
- }
-
- func main() {
- //提示信息
- fmt.Println("服务器[新结构]正在监听8889端口...")
- listen, err := net.Listen("tcp", "127.0.0.1:8889")
- //这里需要延时关闭
- defer listen.Close()
- if err != nil {
- fmt.Printf("net listen err = %v\n", err)
- return
- }
- //一旦监听成功,就等待客户端来连接服务器
- for {
- fmt.Println("等待客户端来连接服务器...")
- conn, err := listen.Accept()
- if err != nil {
- fmt.Printf("listen accept err = %v\n", err)
- return
- }
- //一旦连接成功,则启动一个协程,保持和客户端通讯
- go process(conn)
- }
- }
server/main/processor.go
- package main
-
- import (
- "fmt"
- "net"
- "io"
- "go_code/chatroom/common/message"
- "go_code/chatroom/server/utils"
- "go_code/chatroom/server/processBlock"
- )
-
- //创建一个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 := &processBlock.UserProcess{
- Conn: this.Conn,
- }
- err = up.ServerProcessLogin(mes)
- case message.RegisterMesType :
- //处理注册
- default :
- fmt.Println("消息类型不存在, 无法处理...")
- }
- return
- }
-
- func (this *Processor) ProcessMain() (err error) {
- //读取客户端发送的消息
- for {
- //将读取数据包直接封装成一个函数readPkg(),返回messag,error
- //创建一个Transfer,完成读包的任务
- tf := &utils.Transfer{
- Conn: this.Conn,
- }
- mes, err := tf.ReadPkg()
- if err != nil {
- if err == io.EOF {
- fmt.Println("客户端退出了,服务器端也跟着退出")
- } else {
- fmt.Printf(" readPkg err = %v\n", err)
- }
- return err
- }
- err = this.serverProcessMes(&mes)
- if err != nil {
- return err
- }
- }
- }
server/utils/utils.go
- package utils
-
- import (
- "fmt"
- "net"
- "go_code/chatroom/common/message"
- "encoding/binary"
- "encoding/json"
- )
-
- //将方法关联到结构体中
- type Transfer struct {
- Conn net.Conn
- Buf [8096]byte //传输时,使用的缓冲
- }
-
- func (this *Transfer) WritePkg(data []byte) (err error) {
- //先发送一个长度给对方
- //先把data的长度发送给对方
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
- //发送长度
- n, err := this.Conn.Write(this.Buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
- //发送data本身
- n, err = this.Conn.Write(data)
- if err != nil || n != int(pkgLen) {
- fmt.Printf(" conn write err = %v\n", err)
- }
- return
- }
-
- //读取数据包直接封装成一个函数readPkg()
- 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 {
- // err = errors.New("read pkg body error")
- return
- }
- //把pkgLen反序列化成message.Message
- err = json.Unmarshal(this.Buf[:pkgLen], &mes)
- if err != nil {
- return
- }
- return
- }
server/processBlock/userProcess.go
- package processBlock
-
- import (
- "fmt"
- "net"
- "go_code/chatroom/common/message"
- "go_code/chatroom/server/utils"
- "encoding/json"
- )
-
- type UserProcess struct {
- Conn net.Conn
- }
-
- //编写一个函数serverProcessLogin函数,专门处理登录请求
- func (this *UserProcess) ServerProcessLogin(mes *message.Message) (err error) {
- //先从mes中取出mes.Data,并直接反序列化成LoginMes
- var loginMes message.LoginMes
- err = json.Unmarshal([]byte(mes.Data), &loginMes)
- if err != nil {
- fmt.Printf("json.Unmarshal fail, err = %v\n", err)
- return
- }
- //1.声明一个resMes
- var resMes message.Message
- resMes.Type = message.LoginResMesType
- //2.再声明一个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 = "该用户不存在,请注册后使用"
- }
- //3.将loginResMes序列化
- data, err := json.Marshal(loginResMes)
- if err != nil {
- fmt.Printf("json.Marshal fail, err = %v\n", err)
- return
- }
- //4.将data赋值给resMes
- resMes.Data = string(data)
- //5.对resMes序列化,准备发送
- data, err = json.Marshal(resMes)
- if err != nil {
- fmt.Printf("json.Marshal fail, err = %v\n", err)
- return
- }
- //6.发送data,将其封装到writePkg函数中
- //因为使用了分层模式(mvc),先创建Transfer实例,然后读取
- tf := &utils.Transfer{
- Conn: this.Conn,
- }
- err = tf.WritePkg(data)
- return
- }
修改客户端,先画出程序框架图,再写代码
client/main.go
- package main
-
- import (
- "fmt"
- "os"
- "go_code/chatroom/client/processBlock"
- )
-
- //定义两个全局变量,一个用户id,一个用户密码
- var userId int
- var userPwd string
-
- func main() {
- //接收用户的选择
- var key int
-
- for true {
- fmt.Println("-------------------欢迎登录多人聊天系统-------------------")
- fmt.Println("\t\t\t\t 1 登录聊天室")
- fmt.Println("\t\t\t\t 2 注册用户")
- fmt.Println("\t\t\t\t 3 退出系统")
- fmt.Println("\t\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)
- //先把登录的函数写到另一个文件,比如:login.go
- //因为使用了分层模式(mvc),故调用分层模式中processBlock.UserProcess.Login()处理
- up := &processBlock.UserProcess{}
- up.Login(userId, userPwd)
- case 2 :
- fmt.Println("注册用户")
- case 3 :
- fmt.Println("退出系统")
- os.Exit(0)
- default:
- fmt.Println("输入有误,请重新输入")
- }
- }
- }
client/utils/utils.go
- package utils
-
- import (
- "fmt"
- "net"
- "go_code/chatroom/common/message"
- "encoding/binary"
- "encoding/json"
- )
-
- //将方法关联到结构体中
- type Transfer struct {
- Conn net.Conn
- Buf [8096]byte //传输时,使用的缓冲
- }
-
- func (this *Transfer) WritePkg(data []byte) (err error) {
- //先发送一个长度给对方
- //先把data的长度发送给对方
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
- //发送长度
- n, err := this.Conn.Write(this.Buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
- //发送data本身
- n, err = this.Conn.Write(data)
- if err != nil || n != int(pkgLen) {
- fmt.Printf(" conn write err = %v\n", err)
- }
- return
- }
-
- //读取数据包直接封装成一个函数readPkg()
- 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 {
- // err = errors.New("read pkg body error")
- return
- }
- //把pkgLen反序列化成message.Message
- err = json.Unmarshal(this.Buf[:pkgLen], &mes)
- if err != nil {
- return
- }
- return
- }
client/processBlock/server.go
- package processBlock
-
- import (
- "fmt"
- "os"
- "net"
- "go_code/chatroom/client/utils"
- )
-
- //显示登录成功后的界面
- func ShowMenu() {
- fmt.Println("--------恭喜xxx登录成功--------")
- 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("显示在线用户列表")
- 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.Println("客户端正在等待读取服务器发送的消息")
- mes, err := tf.ReadPkg()
- if err != nil {
- fmt.Println("tf.readpkg err =", err)
- return
- }
- //如果读取到消息,则进行下一步逻辑处理
- fmt.Printf("mes=%v\n", mes)
- }
- }
client/processBlock/userProcess.go
- package processBlock
-
- import (
- "fmt"
- "net"
- "encoding/binary"
- "encoding/json"
- "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
-
- //1.连接到服务器端
- conn, err := net.Dial("tcp", "127.0.0.1:8889")
- if err != nil {
- fmt.Printf("net dial err = %v\n", 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.Printf("json marshal err = %v\n", err)
- return
- }
- //5.将序列化后的loginMes byte切片赋给mes.Data
- mes.Data = string(data)
- //6.将mes序列化
- data, err = json.Marshal(mes)
- if err != nil {
- fmt.Printf("json marshal err = %v\n", err)
- return
- }
- //7.这个时候data就是要发送的消息
- //7.1先把data的长度发送给服务器
- //先获取到data的长度,然后把长度转换成一个表示长度的byte切片
- var pkgLen uint32
- pkgLen = uint32(len(data)) //把data的长度转换成uint32,供后续转成byte切片长度使用
- var buf [4]byte
- binary.BigEndian.PutUint32(buf[0:4], pkgLen)
- //发送长度
- n, err := conn.Write(buf[:4])
- if err != nil || n != 4 {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
- // fmt.Printf("客户端发送消息长度=%d,内容=%s\n", len(data), string(data))
- //发送消息本身
- _, err = conn.Write(data)
- if err != nil {
- fmt.Printf(" conn write err = %v\n", err)
- return
- }
-
- //休眠20s
- // time.Sleep(20 * time.Second)
- // fmt.Println("休眠20s...")
- //处理服务端返回的消息
- //创建一个Transfer实例
- tf := &utils.Transfer{
- Conn: conn,
- }
- mes, err = tf.ReadPkg()
- 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 err != nil {
- fmt.Println("json.Unmarshal err = ", err)
- return
- }
- if loginResMes.Code == 200 {
- // fmt.Println("登录成功")
- //这里需要启动一个协程,保持和服务器通讯,如果服务器有数据,则推送给客户端,客户端接收后显示在终端
- go ServerProcessMes(conn)
- //1.显示登录成功的菜单[循环显示]
- for {
- ShowMenu()
- }
- } else if loginResMes.Code == 500 {
- fmt.Println(loginResMes.Error)
- }
-
- return
- }