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

这个系统其实是尚硅谷的老韩讲的(尚硅谷网络编程项目),但是他讲得很碎片化,思路不够清晰,时间又长,所以要掌握还是挺难的。如果你听了他的视频,不去梳理系统业务流程,不去看代码就往下听,那是很容易懵圈的。好在本人做了登录模块和服务器处理消息的业务流程图,我相信这一定有助于大家整理思路和理解系统业务流程。另外,因为老韩之后把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)
	}
}
相关推荐
唐墨1232 小时前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang
老大白菜3 小时前
FastAPI vs Go 性能对比分析
开发语言·golang·fastapi
千年死缓6 小时前
golang结构体转map
开发语言·后端·golang
zyh_0305216 小时前
GO--堆(have TODO)
数据结构·算法·golang
翔云1234568 小时前
raft: Failed to contact
golang·raft·gc
蟾宫曲12 小时前
网络编程 03:端口的定义、分类,端口映射,通过 Java 实现了 IP 和端口的信息获取
java·网络·网络编程·ip·端口
ahhhhaaaa-13 小时前
【AI图像生成网站&Golang】项目架构
开发语言·架构·golang
ac-er888815 小时前
Go 语言GC(垃圾回收)的工作原理
java·jvm·golang
techdashen17 小时前
Go, Jocko, Kafka
开发语言·golang·kafka
入 梦皆星河20 小时前
学习go中的Resty, 比标准库net/http更加方便友好
学习·http·golang