这个系统其实是尚硅谷的老韩讲的(尚硅谷网络编程项目),但是他讲得很碎片化,思路不够清晰,时间又长,所以要掌握还是挺难的。如果你听了他的视频,不去梳理系统业务流程,不去看代码就往下听,那是很容易懵圈的。好在本人做了登录模块和服务器处理消息的业务流程图,我相信这一定有助于大家整理思路和理解系统业务流程。另外,因为老韩之后把login函数的部分代码封装到单独的包里了,所以我就干脆从头开始快速讲一遍,当然之前讲过的知识不会再讲。
目录
一、系统框架搭建
首先我们需要搭建好目录结构,目录结构如下
go
海量用户通信系统/
├── go.mod
├── client/
│ ├── main.go
│ └── login.go
├── server/
│ └── main.go
└── common/
├── message/
│ └── message.go
└── utils/
└── utils.go
注意,模块名需要自定义,我的模块名叫MassUsrsCom。搭建好后,按如下步骤构建初步的代码:
1.在message.go中定义消息类型
go
package message
const (
LoginMesType = "LoginMes"
LoginResMesType = "LoginResMes"
RegisterMesType = "RegisterMes"
)
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"` //返回错误信息
}
type RegisterMes struct {
}
2.在utils.go中声明读数据包和写数据包的函数
go
package utils
import (
"MassUsrsCom/common/message"
"encoding/binary"
"encoding/json"
"fmt"
"net"
)
// 读数据包
func ReadPkg(conn net.Conn) (mes message.Message, err error) {
return
}
// 写数据包
func WritePkg(conn net.Conn, data []byte) (err error) {
return
}
3.在login.go中声明 login函数
go
package main
import (
"MassUsrsCom/common/message"
"MassUsrsCom/common/utils"
"encoding/json"
"fmt"
"net"
)
func login(userID int, userPwd string) (err error) {
return
}
4.在client包的main.go中写完整的代码
go
package main
import (
"fmt"
)
// 定义两个变量,一个表示用户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.Scanln(&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.Printf("请输入用户的id号:")
fmt.Scanf("%d\n", &userID)
fmt.Printf("请输入用户的密码:")
fmt.Scanf("%s\n", &userPwd)
login(userID, userPwd)
} else if key == 2 {
fmt.Println("进行用户注册的逻辑...")
}
}
5.在server包的main.go中写部分代码
go
package main
import (
"MassUsrsCom/common/message"
"MassUsrsCom/common/utils"
"encoding/json"
"fmt"
"io"
"net"
)
// 处理登录消息
func serverProcessLogin(conn net.Conn, mes *message.Message) (err error) {
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.Println("消息类型不存在,无法处理...")
}
return
}
// 处理和客户端的通信
func process(conn net.Conn) {
}
func main() {
}
二、C/S通信总体流程
1.登录模块总体流程
流程图
代码
go
func login(userID int, userPwd string) (err error) {
//下一个就要开始定协议
// fmt.Printf("userId=%d pwd=%s\n", userId, pwd)
// return nil
//1.连接到服务器
conn, err := net.Dial("tcp", "localhost:8889")
if err != nil {
fmt.Println("net.Dial error:", err)
return
}
//延时关闭
defer conn.Close()
//2.初始化一个Mes 结构体
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 error:", err)
return
}
//5.将序列化后的loginMes作为mes的Data部分
mes.Data = string(data)
//6.将mes序列化
data, err = json.Marshal(mes)
if err != nil {
fmt.Println("json.Marshal error:", err)
return
}
//7.发送消息,即mes的Data部分
err = utils.WritePkg(conn, data)
if err != nil {
fmt.Println("WritePkg(conn) error:", err)
return
}
//8.接收服务器端返回的信息(消息mes或错误)
mes, err = utils.ReadPkg(conn) //mes
if err != nil {
fmt.Println("ReadPkg(conn) error:", err)
return
}
//9.将mes的Data部分反序列化成LoginResMes
var loginResMes message.LoginResMes
err = json.Unmarshal([]byte(mes.Data), &loginResMes)
if err != nil {
fmt.Println("json.Unmarshal error:", err)
return
}
//10.验证LoginResMes
if loginResMes.Code == 200 {
fmt.Println("登录成功")
} else if loginResMes.Code == 500 {
fmt.Println(loginResMes.Error)
}
return
}
2.服务器处理消息总体流程
流程图
代码
go
// 处理和客户端的通信
func process(conn net.Conn) {
//这里需要延时关闭conn
defer conn.Close()
//循环读取客户端发送的信息
for {
mes, err := utils.ReadPkg(conn) //读取客户端消息
if err != nil {
if err == io.EOF {
fmt.Println("客户端退出,相关的服务器协程也退出...")
return
} else {
fmt.Println(err)
return
}
}
err = serverProcessMes(conn, &mes) //处理客户端的消息
if err != nil {
fmt.Println(err)
return
}
}
}
func main() {
//提示信息
fmt.Println("服务器在8889端口监听")
listen, err := net.Listen("tcp", "0.0.0.0:8889")
if err != nil {
fmt.Println("服务器监听端口失败:", err)
return
}
defer listen.Close()
//一旦监听成功,就等待客户端来连接服务器
for {
fmt.Println("等待客户端来连接服务器......")
conn, err := listen.Accept()
if err != nil {
fmt.Println("客户端连接服务器失败:", err)
continue
}
//一旦连接成功,则启动一个协程和客户端保持通信
go process(conn)
}
}