GO网络编程(五):海量用户通信系统3:整体框架与C/S通信总体流程【重要】

news/2024/10/7 14:20:26/

这个系统其实是尚硅谷的老韩讲的(尚硅谷网络编程项目),但是他讲得很碎片化,思路不够清晰,时间又长,所以要掌握还是挺难的。如果你听了他的视频,不去梳理系统业务流程,不去看代码就往下听,那是很容易懵圈的。好在本人做了登录模块和服务器处理消息的业务流程图,我相信这一定有助于大家整理思路和理解系统业务流程。另外,因为老韩之后把login函数的部分代码封装到单独的包里了,所以我就干脆从头开始快速讲一遍,当然之前讲过的知识不会再讲。

目录

    • 一、系统框架搭建
    • 二、C/S通信总体流程
      • 1.登录模块总体流程
      • 2.服务器处理消息总体流程

一、系统框架搭建

首先我们需要搭建好目录结构,目录结构如下

海量用户通信系统/
├── go.mod
├── client/
│   ├── main.go
│   └── login.go
├── server/
│   └── main.go
└── common/├── message/│   └── message.go└── utils/└── utils.go

注意,模块名需要自定义,我的模块名叫MassUsrsCom。搭建好后,按如下步骤构建初步的代码:
1.在message.go中定义消息类型

package messageconst (LoginMesType    = "LoginMes"LoginResMesType = "LoginResMes"RegisterMesType = "RegisterMes"
)type Message struct {Type string `json:"type"` //消息类型Data string `json:"data"` //消息
}// 定义两个消息..后面需要再增加
type LoginMes struct {UserID   int    `json:"userID"`   //用户idUserPwd  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中声明读数据包和写数据包的函数

package utilsimport ("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函数

package mainimport ("MassUsrsCom/common/message""MassUsrsCom/common/utils""encoding/json""fmt""net"
)func login(userID int, userPwd string) (err error) {return
}

4.在client包的main.go中写完整的代码

package mainimport ("fmt"
)// 定义两个变量,一个表示用户id,一个表示用户密码
var userID int
var userPwd stringfunc main() {//接收用户的选择var key int//判断是否还继续显示菜单var loop = truefor 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 = falsecase 2:fmt.Println("注册用户")loop = falsecase 3:fmt.Println("退出系统")loop = falsedefault: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中写部分代码

package mainimport ("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.登录模块总体流程

流程图
在这里插入图片描述

代码

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.Messagemes.Type = message.LoginMesType//3.初始化一个LoginMes 结构体var loginMes message.LoginMesloginMes.UserID = userIDloginMes.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) //mesif err != nil {fmt.Println("ReadPkg(conn) error:", err)return}//9.将mes的Data部分反序列化成LoginResMesvar loginResMes message.LoginResMeserr = json.Unmarshal([]byte(mes.Data), &loginResMes)if err != nil {fmt.Println("json.Unmarshal error:", err)return}//10.验证LoginResMesif loginResMes.Code == 200 {fmt.Println("登录成功")} else if loginResMes.Code == 500 {fmt.Println(loginResMes.Error)}return
}

2.服务器处理消息总体流程

流程图
在这里插入图片描述

代码

// 处理和客户端的通信
func process(conn net.Conn) {//这里需要延时关闭conndefer 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)}
}

http://www.ppmy.cn/news/1535730.html

相关文章

北大对齐团队深度硬核解读:OpenAI o1开启「后训练」时代强化学习新范式

本文由readlecture.cn转录总结。ReadLecture专注于音、视频转录与总结,2小时视频,5分钟阅读,加速内容学习与传播。 大纲 引言 自我介绍与研究背景 分享主题概述 Post-Training Scaling Laws RL在后训练时代的新范式 OpenAI o1的技术细节与…

CSP-J 复赛算法 贪心算法练习

文章目录 前言纪念品分组贪心算法的分析过程C 代码实现代码解析 泥泞路分析过程1. **整理数据**2. **合并区间**什么叫做合并区间 例子说明1. **排序区间**2. **逐个检查区间是否可以合并**3. **最终的合并结果** 合并区间的算法思路伪代码例子代码说明合并区间的实际应用3. **…

图解C#高级教程(三):泛型

本讲用许多代码示例介绍了 C# 语言当中的泛型,主要包括泛型类、接口、结构、委托和方法。 文章目录 1. 为什么需要泛型?2. 泛型类的定义2.1 泛型类的定义2.2 使用泛型类创建变量和实例 3. 使用泛型类实现一个简单的栈3.1 类型参数的约束3.2 Where 子句3…

【MySQL】服务器管理与配置

MySQL服务器 服务器默认配置 查看服务器默认选项和系统变量 mysqld --verbose --help 查看运行时的系统变量,可以通过like去指定自己要查询的内容 状态变量的查看 系统变量和状态变量的作用域 全局作用域: 对于每个会话都会生效当前会话:只…

OpenJudge | 置换选择排序

总时间限制: 1000ms 内存限制: 65536kB 描述 给定初始整数顺串,以及大小固定并且初始元素已知的二叉最小堆(为完全二叉树或类似完全二叉树,且父元素键值总小于等于任何一个子结点的键值),要求利用堆实现置换选择排序&a…

十四、深入理解Mysql索引底层数据结构与算法

文章目录 一、索引的本质1、索引是帮助MySQL高效获取数据的排好序的数据结构2、索引的数据结构3、数据结构可视化网站 二、常见数据结构介绍1、B-Tree2、BTree(B-Tree变种)3、Hash结构 三、存储引擎的索引实现1、MyISAM存储引擎索引实现MyISAM索引文件和…

Node.js env 环境变量多种配置方式

目录 process.env 配置方式 dotenv 使用 cross-env process.env 在 Node.js 中,你可以使用 process.env 对象来读取环境变量。这个对象包含了所有的环境变量,你可以通过变量名来访问这些变量的值。 例如,如果你有一个名为 MY_VARIABLE …

Element UI教程:如何将Radio单选框的圆框改为方框

大家好,今天给大家带来一篇关于Element UI的使用技巧。在项目中,我们经常会用到Radio单选框组件,默认情况下,Radio单选框的样式是圆框。但有时候,为了满足设计需求,我们需要将圆框改为方框,如下…