自学内容网 自学内容网

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

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

一、系统框架搭建

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

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

注意,模块名需要自定义,我的模块名叫MassUsrsCom。搭建好后,按如下步骤构建初步的代码:
1.在message.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中声明读数据包和写数据包的函数

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函数

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中写完整的代码

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中写部分代码

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

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

代码

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.服务器处理消息总体流程

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

代码

// 处理和客户端的通信
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)
}
}

原文地址:https://blog.csdn.net/weixin_54259326/article/details/142732165

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!