Bootstrap

go 聊天系统项目-1

1、登录界面

说明:这一节的内容采用 go mod 管理【GO111MODULE=‘’】的模块,从第二节开始使用【GO111MODULE=‘off’】GOPATH 管理模块。具体参见 go 包相关知识

1.1登录界面代码目录结构

代码所在目录/Users/zld/Go-project/day8/chatroom/
在这里插入图片描述

1.2登录界面代码

main.go

package main

import (
	"fmt"
)

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("退出系统")
			loop = false
		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("进行用户注册的逻辑")

	}
}

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
}

1.3初始化模块

go mod init client 

注意:init 后跟的名字和二进制文件名字(go build -o 后的名字)一样

go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
go: creating new go.mod: module client
go: to add module requirements and sums:
        go mod tidy

1.4编译

cd /Users/zld/Go-project/day8/chatroom/client/
go build -o client ./
输出
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject

1.5演示代码

go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
1
登录聊天室
请输入用户的id
123
请输入用户密码
qwe
userId = 123 userPwd = qwe
登录成功
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
3
退出系统
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
2
注册用户
进行用户注册的逻辑
go run client 
go: warning: ignoring go.mod in $GOPATH /Users/zld/goproject
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
>
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
?
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):
5
你的输入有误,请重新输入
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

2、客户端服务端简单交互

2.1代码目录结构

GOPATH=‘/Users/zld/Go-project’
项目目录结构,项目在 /Users/zld/Go-project/src 【GOPATH指定的目录】下

tree
.
└── chatroom
    ├── client
    │   ├── login.go
    │   ├── main.go  
    ├── common
    │   └── message
    │       └── message.go
    └── server
        └── main.go

6 directories, 4 files

2.2代码

2.2.1 day8/chatroom/common/message/message.go
package message

const (
	LoginMesType    = "LoginMes"
	LoginResMesType = "LoginResMes"
)

type Message struct {
	Type string `josn: "type"`
	Data string `json: "Data"`
}
type LoginMes struct {
	UserId   int    `json: "userId"`
	UserPwd  string `json: "userPwd"`
	UserName string `json: "userName"`
}
type LoginResMes struct {
	Code  int    `json: "code"`
	Error string `json: "error"`
}
2.2.2 day8/chatroom/server/main.go
package main

import (
	"fmt"
	"net"
)

// 处理和客户端的通信
func process(conn net.Conn) {
	//这里需要延时关闭conn
	defer conn.Close()
	//循环的客户端发送的信息
	for {
		buf := make([]byte, 8096)
		n, err := conn.Read(buf[:4])
		if n != 4 || err != nil {
			fmt.Println("conn.Read err=", err)
			return
		}
		fmt.Printf("读到的buf=%d\n", buf[:4])
	}

}

func main() {
	//提示信息
	fmt.Println("服务器在 8889 端口监听......")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	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)
	}
}
2.2.3 day8/chatroom/client/client.go
package main

import (
	"fmt"
)

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("退出系统")
			loop = false
		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("进行用户注册的逻辑")

	}
}
2.2.4 day8/chatroom/client/main.go
package main

import (
	"day8/chatroom/common/message" // 这里是写 go mod init 的 chatroom,然后最后是文件夹
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
)

func login(userId int, userPwd string) (err error) {
	//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	//return nil
	//连接到服务器
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err=", err)
		return
	}
	//延时关闭
	defer conn.Close()
	//准备通过 conn 发送消息给服务器
	var mes message.Message
	mes.Type = message.LoginMesType
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	//将 loginMes 序列化
	data, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//将data赋值给 message 结构体 Data 字段
	mes.Data = string(data)
	//将 mes 进行序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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(bytes) fail", err)
		return
	}
	fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
	return
}

2.3 编译项目代码

注意:如果在 GO111MODULE=‘off’ 的情况下,编译代码一定要进到 $GOPATH 目录。

cd $GOPATH
go build -o server day8/chatroom/server/
go build -o client day8/chatroom/client/

2.4 演示代码

./server 
服务器在 8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
qwe
客户端,发送消息的长度=83,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"qwe\",\"UserName\":\"\"}"}
登录成功

server

等待客户端连接服务器......
读到的buf=[0 0 0 83]
conn.Read err= EOF

3、判断用户输入账户密码并改进代码结构

3.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom
    ├── client
    │   ├── login.go
    │   ├── main.go
    │   └── utils.go
    ├── common
    │   └── message
    │       └── message.go
    └── server
        └── main.go

6 directories, 5 files

3.2 代码

3.2.1 day8/chatroom/client/login.go
package main

import (
	"day8/chatroom/common/message" // 这里是写 go mod init 的 chatroom,然后最后是文件夹
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
	//"time"
)

func login(userId int, userPwd string) (err error) {
	//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	//return nil
	//连接到服务器
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err=", err)
		return
	}
	//延时关闭
	defer conn.Close()
	//准备通过 conn 发送消息给服务器
	var mes message.Message
	mes.Type = message.LoginMesType
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	//将 loginMes 序列化
	data, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//将data赋值给 message 结构体 Data 字段
	mes.Data = string(data)
	//将 mes 进行序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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(bytes) fail", err)
		return
	}
	fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
	_, err = conn.Write(data)
	if err != nil {
		fmt.Printf("conn.Write(data) fail", err)
		return
	}
	//time.sleep(20*time.Second)
	//fmt.Println("休眠了20S")
	//这里还需要处理服务器返回的消息
	mes, err = readPkg(conn) //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 {
		fmt.Println("登录成功")
	} else if loginResMes.Code == 500 {
		fmt.Println(loginResMes.Error)
	}

	return
}
3.2.2 day8/chatroom/client/main.go
package main

import (
	"fmt"
)

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("退出系统")
			loop = false
		default:
			fmt.Println("你的输入有误,请重新输入")
		}
	}

	if key == 1 {
		fmt.Println("请输入用户的id")
		fmt.Scanf("%d\n", &userId)
		fmt.Println("请输入用户密码")
		fmt.Scanf("%s\n", &userPwd)
		login(userId, userPwd)
		//err := login(userId, userPwd)
		// if err != nil {
		// 	fmt.Println("登录失败")
		// } else {
		// 	fmt.Println("登录成功")
		// }
	} else if key == 2 {
		fmt.Println("进行用户注册的逻辑")

	}
}
3.2.3 day8/chatroom/client/utils.go
package main

import (
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
	"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 {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(buf[0:4])
	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("json.Unmarshal error")
		return
	}
	return

}

func writePkg(conn net.Conn, data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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(bytes) fail", err)
		return
	}
	//发送 data本身
	n, err = conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	return
}
3.2.4 day8/chatroom/common/message/message.go
package message

const (
	LoginMesType    = "LoginMes"
	LoginResMesType = "LoginResMes"
	RegisterMesType = "RegisterMes"
)

type Message struct {
	Type string `josn: "type"`
	Data string `json: "Data"`
}
type LoginMes struct {
	UserId   int    `json: "userId"`
	UserPwd  string `json: "userPwd"`
	UserName string `json: "userName"`
}
type LoginResMes struct {
	Code  int    `json: "code"`
	Error string `json: "error"`
}

type RegisterMes struct{
	//
}
3.2.5 day8/chatroom/server/main.go
package main

import (
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
)

func readPkg(conn net.Conn) (mes message.Message, err error) {
	buf := make([]byte, 8096)
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = conn.Read(buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(buf[0:4])
	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("json.Unmarshal error")
		return
	}
	return

}

func writePkg(conn net.Conn, data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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
	}
	//发送 data本身
	n, err = conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write(bytes) fail", err)
		return
	}
	return
}

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.Println("json.Unmarshal 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
		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,将其封装到函数中
	err = writePkg(conn, data)
	return
}

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.Printf("消息类型不存在,无法处理")
	}
	return
}

// 处理和客户端的通信
func process(conn net.Conn) {
	//这里需要延时关闭conn
	defer conn.Close()
	//循环客户端发送信息
	for {
		mes, err := readPkg(conn)

		if err != nil {
			if err == io.EOF {
				fmt.Println("客户端退出,服务器端也退出..")
				return
			} else {
				fmt.Println("readPkg err=", err)
				return
			}
		}

		//fmt.Println("mes=", mes)
		err = serverProcessMes(conn, &mes)
		if err != nil {
			return
		}
	}

}

func main() {
	//提示信息
	fmt.Println("服务器在 8889 端口监听......")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	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.3 编译项目代码

go build -o server day8/chatroom/server/
go build -o client day8/chatroom/client/

3.4 演示代码

./server 
服务器在 8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
123
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":123,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
读取客户端发送的数据...
该用户不存在,请注册再使用...

server

等待客户端连接服务器......
客户端退出,服务器端也退出..

4、改进服务端代码结构

客户端 client 目录下的代码不变

4.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom
    ├── client
    │   ├── login.go
    │   ├── main.go
    │   └── utils.go
    ├── common
    │   └── message
    │       └── message.go
    └── server
        ├── main
        │   ├── main.go
        │   └── processor.go
        ├── model
        ├── process
        │   ├── smsProcess.go
        │   └── userProcess.go
        └── utils
            └── utils.go

10 directories, 9 files

4.2 代码

这里只展示改动的 server 目录下的代码。

4.2.1 day8/chatroom/server/main/main.go
package main

import (
	// "day8/chatroom/common/message"
	// "encoding/binary"
	// "encoding/json"
	// "errors"
	"fmt"
	// "io"
	"net"
)

// func readPkg(conn net.Conn) (mes message.Message, err error) {
// 	buf := make([]byte, 8096)
// 	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
// 	//如果客户端关闭了conn 就不会阻塞
// 	_, err = conn.Read(buf[:4])
// 	if err != nil {
// 		//err = errors.New("read pkg header error")
// 		return
// 	}
// 	//根据读到的  buf[:4] 转成一个 unit32 类型
// 	var pkgLen uint32
// 	pkgLen = binary.BigEndian.Uint32(buf[0:4])
// 	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("json.Unmarshal error")
// 		return
// 	}
// 	return

// }

// func writePkg(conn net.Conn, data []byte) (err error) {
// 	//先发送一个长度给对方
// 	//data是 我们要发送的消息,先发送 data 长度
// 	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
// 	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
// 	}
// 	//发送 data本身
// 	n, err = conn.Write(data)
// 	if n != int(pkgLen) || err != nil {
// 		fmt.Println("conn.Write(bytes) fail", err)
// 		return
// 	}
// 	return
// }


// 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.Println("json.Unmarshal 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
// 		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,将其封装到函数中
// 	err = writePkg(conn, data)
// 	return
// }

// 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.Printf("消息类型不存在,无法处理")
// 	}
// 	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
	}
	// //循环客户端发送信息
	// for {
	// 	mes, err := readPkg(conn)

	// 	if err != nil {
	// 		if err == io.EOF {
	// 			fmt.Println("客户端退出,服务器端也退出..")
	// 			return
	// 		} else {
	// 			fmt.Println("readPkg err=", err)
	// 			return
	// 		}
	// 	}

	// 	//fmt.Println("mes=", mes)
	// 	err = serverProcessMes(conn, &mes)
	// 	if err != nil {
	// 		return
	// 	}
	// }

}

func main() {
	//提示信息
	fmt.Println("服务器[新的结构]在 8889 端口监听......")
	listen, err := net.Listen("tcp", "0.0.0.0:8889")
	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)
	}
}
4.2.2 day8/chatroom/server/main/processor.go
package main
import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"day8/chatroom/server/process"
	"day8/chatroom/server/utils"
	"io"
)

//先创建一个processor的结构体
type Processor struct{
	Conn net.Conn
}

func (this *Processor) serverProcessMes(mes *message.Message) (err error) {
	switch mes.Type {
	case message.LoginMesType:
		//处理登录
		up := &process2.UserProcess{
			Conn : this.Conn,
		}
		err = up.ServerProcessLogin(mes)
	case message.RegisterMesType:
	//处理注册
	default:
		fmt.Printf("消息类型不存在,无法处理")
	}
	return
}

func (this *Processor) process2() (err error) {
	//循环客户端发送信息
	for {
		//创建一个 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
			}
		}
		//fmt.Println("mes=", mes)
		err = this.serverProcessMes(&mes)
		if err != nil {
			return err
		}
	}
}
4.2.3 day8/chatroom/server/process/smsProcess.go
package process2
4.2.4 day8/chatroom/server/process/userProcess.go
package process2

import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"day8/chatroom/server/utils"
	"encoding/json"

)

type UserProcess struct{
	//
	Conn net.Conn
}
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.Println("json.Unmarshal 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
		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,将其封装到函数中
	//因为使用分层模式(mvc),我们先创建一个 Transfer 实例,然后读取
	tf := &utils.Transfer{
		Conn : this.Conn,
	}
	err = tf.WritePkg(data)
	return
}
4.2.5 day8/chatroom/server/utils/utils.go
package utils
import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
)
//这里将这些方法关联到结构体中
type Transfer struct{
	//分析它应该有哪些字段
	Conn net.Conn
	Buf [8096]byte // 这是传输时,使用缓冲
}


func (this *Transfer) ReadPkg() (mes message.Message, err error) {
	//buf := make([]byte, 8096)
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = this.Conn.Read(this.Buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
	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 {
		err = errors.New("json.Unmarshal error")
		return
	}
	return

}

func (this *Transfer) WritePkg(data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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[: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
}

4.3 编译项目代码

go build -o server day8/chatroom/server/main
go build -o client day8/chatroom/client/

4.4 演示代码

./server 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
读取客户端发送的数据...
登录成功

server

等待客户端连接服务器......
客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF

5、改进客户端代码结构

5.1 代码目录结构

echo $PWD
/Go-project/src/day8
tree
.
└── chatroom
    ├── client
    │   ├── main
    │   │   └── main.go
    │   ├── model
    │   ├── process
    │   │   ├── server.go
    │   │   ├── smsProcess.go
    │   │   └── userProcess.go
    │   └── utils
    │       └── utils.go
    ├── common
    │   └── message
    │       └── message.go
    └── server
        ├── main
        │   ├── main.go
        │   └── processor.go
        ├── model
        ├── process
        │   ├── smsProcess.go
        │   └── userProcess.go
        └── utils
            └── utils.go

14 directories, 11 files

5.2 代码

5.2.1 day8/chatroom/client/main/main.go
package main

import (
	"day8/chatroom/client/process"
	"fmt"
)

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("登录聊天室")
			fmt.Println("请输入用户的id")
			fmt.Scanf("%d\n", &userId)
			fmt.Println("请输入用户密码")
			fmt.Scanf("%s\n", &userPwd)
			//完成登录
			//1.创建一个UserProcess的实例
			up := &process.UserProcess{}
			up.Login(userId, userPwd)

			//loop = false
		case 2:
			fmt.Println("注册用户")
			loop = false
		case 3:
			fmt.Println("退出系统")
			loop = false
		default:
			fmt.Println("你的输入有误,请重新输入")
		}
	}

	//if key == 1 {

	//这里需要重新调用
	//因为使用了新的程序结构,因此我们创建
	//login(userId, userPwd)
	//err := login(userId, userPwd)
	// if err != nil {
	// 	fmt.Println("登录失败")
	// } else {
	// 	fmt.Println("登录成功")
	// }
	//} else if key == 2 {
	//	fmt.Println("进行用户注册的逻辑")

	//}
}
5.2.2 day8/chatroom/client/process/server.go
package process

import (
	"day8/chatroom/client/utils"
	"fmt"
	"net"
	"os"
)

func ShowMenu() {
	fmt.Println("----------恭喜xxx登录成功--------")
	fmt.Println("--------1、显示在线用户列表--------")
	fmt.Println("--------2、发送消息--------")
	fmt.Println("--------3、信息列表--------")
	fmt.Println("--------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("客户端%s正在等待读取服务器发送的消息")
		mes, err := tf.ReadPkg()
		if err != nil {
			fmt.Println("tf.ReadPkg err=", err)
			return
		}
		//如果读取到消息,又是下一步处理逻辑
		fmt.Printf("mes=%v", mes)
	}
}
5.2.3 day8/chatroom/client/process/smsProcess.go
package process
5.2.4 day8/chatroom/client/process/userProcess.go
package process

import (
	"day8/chatroom/client/utils"
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
)

type UserProcess struct {
}

func (this *UserProcess) Login(userId int, userPwd string) (err error) {
	//fmt.Printf("userId = %d userPwd = %s\n", userId, userPwd)
	//return nil
	//连接到服务器
	conn, err := net.Dial("tcp", "localhost:8889")
	if err != nil {
		fmt.Println("net.Dial err=", err)
		return
	}
	//延时关闭
	defer conn.Close()
	//准备通过 conn 发送消息给服务器
	var mes message.Message
	mes.Type = message.LoginMesType
	var loginMes message.LoginMes
	loginMes.UserId = userId
	loginMes.UserPwd = userPwd
	//将 loginMes 序列化
	data, err := json.Marshal(loginMes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//将data赋值给 message 结构体 Data 字段
	mes.Data = string(data)
	//将 mes 进行序列化
	data, err = json.Marshal(mes)
	if err != nil {
		fmt.Println("json.Marshal err=", err)
		return
	}
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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(bytes) fail", err)
		return
	}
	fmt.Printf("客户端,发送消息的长度=%d,消息内容为: %s\n", len(data), string(data))
	_, err = conn.Write(data)
	if err != nil {
		fmt.Printf("conn.Write(data) fail", err)
		return
	}
	//time.sleep(20*time.Second)
	//fmt.Println("休眠了20S")
	//这里还需要处理服务器返回的消息
	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 {
		//fmt.Println("登录成功")
		//这里我们还需要在客户端启动一个协程
		//该协程保持和服务器端的通讯,如果服务器有数据推送给客户端
		//则接收并显示在客户端的终端
		go serverProcessMes(conn)
		//1.显示登录成功后的菜单
		for {
			ShowMenu()
		}
	} else if loginResMes.Code == 500 {
		fmt.Println(loginResMes.Error)
	}

	return
}
5.2.5 day8/chatroom/client/utils/utils.go
package utils
import(
	"fmt"
	"net"
	"day8/chatroom/common/message"
	"encoding/binary"
	"encoding/json"
	"errors"
)
//这里将这些方法关联到结构体中
type Transfer struct{
	//分析它应该有哪些字段
	Conn net.Conn
	Buf [8096]byte // 这是传输时,使用缓冲
}


func (this *Transfer) ReadPkg() (mes message.Message, err error) {
	//buf := make([]byte, 8096)
	// conn.Read 在 conn 没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn 就不会阻塞
	_, err = this.Conn.Read(this.Buf[:4])
	if err != nil {
		//err = errors.New("read pkg header error")
		return
	}
	//根据读到的  buf[:4] 转成一个 unit32 类型
	var pkgLen uint32
	pkgLen = binary.BigEndian.Uint32(this.Buf[0:4])
	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 {
		err = errors.New("json.Unmarshal error")
		return
	}
	return

}

func (this *Transfer) WritePkg(data []byte) (err error) {
	//先发送一个长度给对方
	//data是 我们要发送的消息,先发送 data 长度
	//由于 conn 接口的 Write 方法参数要求是 bytes 切片
	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[: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
}

5.3 编译项目代码

go build -o server day8/chatroom/server/main
go build -o client day8/chatroom/client/main

5.4 演示代码

 ./server 
服务器[新的结构]8889 端口监听......
等待客户端连接服务器......
./client 
---------------欢迎登录多人聊天系统-------------------
                         1、登录聊天室
                         2、注册用户
                         3、退出系统
                         请选择(1-3):

client

1
登录聊天室
请输入用户的id
100
请输入用户密码
123456
客户端,发送消息的长度=86,消息内容为: {"Type":"LoginMes","Data":"{\"UserId\":100,\"UserPwd\":\"123456\",\"UserName\":\"\"}"}
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
客户端%s正在等待读取服务器发送的消息

server

等待客户端连接服务器......

client

1
显示在线用户列表
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
2
发送消息
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
3
信息列表
----------恭喜xxx登录成功--------
--------1、显示在线用户列表--------
--------2、发送消息--------
--------3、信息列表--------
--------4、退出系统--------
4
你选择退出了系统...

server

客户端退出,服务器端也退出..
客户端和服务器端通讯的协程错误=err EOF
;