GO网络编程(七):海量用户通信系统5:分层架构

P323开始(尚硅谷GO教程)老韩又改目录结构了,没办法,和之前一样,先说下目录结构,再给代码,部分代码在之前讲过,还有知识的话由于本人近期很忙,所以这些就不多赘述了,读者可自行查阅官方文档(GO中文标准库)或其他网站。

目录

    • 框架与目录结构
    • utils.go
    • userProcess.go
    • processor.go
    • server的main
    • login.go

框架与目录结构

老韩讲的是分层架构,就和MVC差不多,服务器框架图如下
在这里插入图片描述

目录结构

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

utils.go

package utils

import (
	"MassUsrsCom/common/message"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"net"
)

// 这里将这些方法关联到结构体中
type Transfer struct {
	//分析它应该有哪些字段
	Conn net.Conn
	Buf  [8096]byte //传输时使用的缓冲
}

// 可以自定义错误变量err,即定义一个新的err,写入自定义的错误内容,
// 但考虑到上层可能要判断err的原本的类型,所以这里直接返回err
// 读数据包
func (tf *Transfer) ReadPkg() (mes message.Message, err error) {
	fmt.Println("读数据包...")
	buf := tf.Buf[:] //1.准备缓冲区
	//后续可能会读取更多字节,所以缓冲区大小一般都设置得比较大
	//2.读取消息头部并存入缓冲区
	n, err := tf.Conn.Read(buf[:4])
	//conn.Read 在conn没有被关闭的情况下,才会阻塞
	//如果客户端关闭了conn,则不会阻塞
	if n != 4 || err != nil {
		//因为服务端要通过判断err的类型来提示客户端关闭,所以这里什么都不做
		return
	}
	//3.将缓冲区的消息头部转换成消息长度
	var pkgLen = binary.BigEndian.Uint32(buf[:4])
	//4.根据消息长度读取消息内容
	n, err = tf.Conn.Read(buf[:pkgLen])
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Read error")
		return
	}
	//5.将消息内容反序列化并返回
	err = json.Unmarshal(buf[:pkgLen], &mes)
	//由于 mes 是在函数签名中命名的返回值变量,Go 自动为它创建了一个
	//初始的 message.Message 类型实例,这样就无需显式声明
	if err != nil {
		fmt.Println("read pkg body error,json.Unmarshal error")
		return
	}
	return
}

// 写数据包
func (tf *Transfer) WritePkg(data []byte) (err error) {
	fmt.Println("写数据包...")
	buf := tf.Buf[:4] //1.准备缓冲区
	//2.根据消息内容获取消息长度
	var pkgLen = uint32(len(data))
	//3.将消息长度存入缓冲区
	binary.BigEndian.PutUint32(buf, pkgLen)
	//4.发送消息长度
	n, err := tf.Conn.Write(buf)
	if n != 4 || err != nil {
		fmt.Println("conn.Write error")
		return
	}
	//5.发送消息内容
	n, err = tf.Conn.Write(data)
	if n != int(pkgLen) || err != nil {
		fmt.Println("conn.Write error")
		return
	}
	return
}

userProcess.go

smsProcess暂时声明一个process包,userProcess如下:

package process

import (
	"MassUsrsCom/common/message"
	"MassUsrsCom/server/utils"
	"encoding/json"
	"fmt"
	"net"
)

type UserProcess struct {
	Conn net.Conn
}

// 处理登录消息
func (up *UserProcess) ServerProcessLogin(mes *message.Message) (err error) {
	//1.先从mes中取出mes.Data,并直接反序列化成LoginMes
	var loginMes message.LoginMes
	err = json.Unmarshal([]byte(mes.Data), &loginMes)
	if err != nil {
		fmt.Println("json.Unmarshal error:")
		return
	}
	//1初始化一个Mes 结构体
	var resMes message.Message
	resMes.Type = message.LoginResMesType
	//2创建一个LoginResMes 结构体
	var loginResMes message.LoginResMes
	//如果用户id=100,密码=123456,认为合法,否则不合法
	if loginMes.UserID == 100 && loginMes.UserPwd == "123456" {
		//合法
		loginResMes.Code = 200
	} else {
		//不合法
		loginResMes.Code = 500 //状态码,表示该用户不存在
		loginResMes.Error = "该用户不存在,请注册再使用"
	}
	//3 将loginResMes序列化
	data, err := json.Marshal(loginResMes)
	if err != nil {
		fmt.Println("json.Marshal error:")
		return
	}
	//4.将序列化后的loginResMes作为给resMes的data
	resMes.Data = string(data)
	//5.对resMes进行序列化,准备发送
	data, err = json.Marshal(resMes)
	if err != nil {
		fmt.Println("json.Marshal error:")
		return
	}
	//6.发送消息
	//因为使用分层模式(mvc),我们先创建一个Transfer实例,然后读取
	tf := &utils.Transfer{Conn: up.Conn}
	err = tf.WritePkg(data)
	if err != nil {
		fmt.Println("WritePkg(conn) error:")
		return
	}
	return
}

processor.go

package main

import (
	"MassUsrsCom/common/message"
	"MassUsrsCom/server/process"
	"MassUsrsCom/server/utils"
	"fmt"
	"io"
	"net"
)

type Processor struct {
	Conn net.Conn
}

// 判断并处理不同种类的消息
func (proc *Processor) serverProcessMes(mes *message.Message) (err error) {
	switch mes.Type {
	case message.LoginMesType:
		//处理登录逻辑
		//创建一个UserProcess实例
		up := &process.UserProcess{Conn: proc.Conn}
		err = up.ServerProcessLogin(mes)
	case message.RegisterMesType:
		//处理注册
	default:
		fmt.Println("消息类型不存在,无法处理...")
	}
	return
}
func (proc *Processor) process2() (err error) {
	//循环读取客户端发送的信息
	for {
		//创建Transfer实例完成读包的任务
		tf := &utils.Transfer{Conn: proc.Conn}
		mes, err := tf.ReadPkg() //读取客户端消息
		if err != nil {
			if err == io.EOF {
				fmt.Println("客户端退出,相关的服务器协程也退出...")
				return err
			} else {
				fmt.Println(err)
				return err
			}
		}
		err = proc.serverProcessMes(&mes) //处理客户端的消息
		if err != nil {
			fmt.Println(err)
			return err
		}
	}
}

server的main

package main

import (
	"fmt"
	"net"
)

// 处理和客户端的通信
func goroutine(conn net.Conn) {
	//这里需要延时关闭conn
	defer conn.Close()
	//这里调用总控,创建一个
	processor := &Processor{Conn: conn}
	err := processor.process2()
	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 goroutine(conn)
	}
}

login.go

package main

import (
	"MassUsrsCom/common/message"
	"MassUsrsCom/server/utils"
	"encoding/json"
	"fmt"
	"net"
)

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部分
	tf := utils.Transfer{Conn: conn}
	err = tf.WritePkg(data)
	if err != nil {
		fmt.Println("WritePkg(conn) error:", err)
		return
	}
	//8.接收服务器端返回的信息(消息mes或错误)
	mes, err = tf.ReadPkg() //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
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/889906.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web自动化测试基础(从配置环境到自动化实现登录测试用例的执行,vscode如何导入自己的python包)

接下来的一段时间里我会和大家分享自动化测试相关的一些知识希望大家可以多多支持,一起进步。 一、环境的配置 前提安装好了python解释器并配好了环境,并安装好了VScode 下载的浏览器和浏览器驱动需要一样的版本号(只看大版本)。 1、安装浏览器 Chro…

vue-live2d看板娘集成方案设计使用教程

文章目录 前言v1.1.x版本:vue集成看板娘(暂不使用,在v1.2.x已替换)集成看板娘实现看板娘拖拽效果方案资源备份存储 当前最新调研:2024.10.2开源方案1:OhMyLive2D(推荐)开源方案2&…

SpringMVC2~~~

目录 数据格式化 基本数据类型可以和字符串自动转换 特殊数据类型和字符串间的转换 验证及国际化 自定义验证错误信息 细节 数据类型转换校验核心类DataBinder 工作机制 取消某个属性的绑定 中文乱码处理 处理json和HttpMessageConverter 处理Json-ResponseBody 处理…

go开发环境设置-安装与交叉编译(二)

1. 引言 Go语言,又称Golang,是Google开发的一门编程语言,以其高效、简洁和并发编程的优势受到广泛欢迎。作为一门静态类型、编译型语言,Go在构建网络服务器、微服务和命令行工具方面表现突出。 在开发过程中,开发者常…

吸毛效果好的宠物空气净化器分享,希喂、霍尼韦尔、米家实测

说起宠物空气净化器,几年前我可能会一脸鄙夷:为啥要花这种智商税冤枉钱? 直到之前养了一只猫,被家中乱飞的浮毛和滂臭的异味搞到头晕,于是作为i一个养宠的家电测评博主,索性对宠物空气净化器这玩意做了超级…

前端继承:原理、实现方式与应用场景

目录 一、定义 二、语法和实现方式 1.原型链继承 2.构造函数继承 3.组合继承 4.ES6类继承 三、使用方式 四、优点 五、缺点 六、适用场景 一、定义 前端继承是指在面向对象编程中,一个对象可以继承另一个对象的属性和方法。在前端领域,通常是指…

OpenCV高级图形用户界面(1)创建滑动条函数createTrackbar()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 创建一个滑动条并将其附加到指定的窗口。 该函数 createTrackbar 创建一个具有指定名称和范围的滑动条(滑块或范围控制)…

C语言之扫雷小游戏(完整代码版)

说起扫雷游戏,这应该是很多人童年的回忆吧,中小学电脑课最常玩的必有扫雷游戏,那么大家知道它是如何开发出来的吗,扫雷游戏背后的原理是什么呢?今天就让我们一探究竟! 扫雷游戏介绍 如下图,简…

使用3080ti配置安装blip2

使用3080ti运行blip2的案例 本机环境(大家主要看GPU,ubuntu版本和cuda版本即可):安装流程我最后安装的所有包的信息(python 3.9 )以供参考(environment.yml): 本机环境&a…

【python实操】python小程序之计算对象个数、游戏更新分数

引言 python小程序之计算对象个数、游戏更新分数 文章目录 引言一、计算对象个数1.1 题目1.2 代码1.3 代码解释1.3.1 代码结构1.3.2 模块解释1.3.3 解释输出 二、游戏更新分数2.1 题目2.2 代码2.3 代码解释2.3.1 定义 Game 类2.3.2 创建 Game 实例并调用方法 三、思考3.1 计算对…

安卓13禁止锁屏 关闭锁屏 android13禁止锁屏 关闭锁屏

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.彩蛋1.前言 设置 =》安全 =》屏幕锁定 =》 无。 我们通过修改系统屏幕锁定配置,来达到设置屏幕不锁屏的配置。像网上好多文章都只写了在哪里改,改什么东西,但是实际上并未写明为什么要改那…

RabbitMQ 高级特性——死信队列

文章目录 前言死信队列什么是死信常见面试题死信队列的概念:死信的来源(造成死信的原因有哪些)死信队列的应用场景 前言 前面我们学习了为消息和队列设置 TTL 过期时间,这样可以保证消息的积压,那么对于这些过期了的消…

数据结构-4.6.KMP算法(旧版下)-朴素模式匹配算法的优化

一.绪论: 当主串字符和模式串字符不匹配时会执行jnext[j]来改变模式串的指针,但主串的指针不变。 二.求模式串的next数组: 1.例一: 如模式串abcabd,当第六个字符d匹配失败时,此时主串中前五个字符abcab都…

连锁店线下线上一体化收银系统源码

近年来线下线上一体化已经成为很多连锁门店追求的方向。其中,线下门店能够赋予品牌发展的价值依然不可小觑。在线下门店中,收银系统可以说是运营管理的关键工具,好的收银系统能够为品牌门店赋能。对于连锁品牌而言,对收银系统的要…

软媒市场新蓝海:软文媒体自助发布与自助发稿的崛起

在信息时代的浪潮中,软媒市场以其独特的魅力和无限的潜力,成为了企业营销的新宠。随着互联网的飞速发展,软文媒体自助发布平台应运而生,为企业提供了更加高效、便捷的营销方式。而自助发稿功能的加入,更是让软媒市场的蓝海变得更加广阔。 软媒市场的独特价值 软媒市场之所以能…

Android Studio Koala中Kotlin引入序列化Parcelable

找了一堆资料没有新构建序列化的方法,踩坑经历如下: 前提是使用Kotlin创建的项目 之前的build.gradle版本写法如下: 但是新版Android Studio Koala使用序列化模式发生了改变,如下: 测试成功如下: 发出来…

【万字长文】Word2Vec计算详解(三)分层Softmax与负采样

【万字长文】Word2Vec计算详解(三)分层Softmax与负采样 写在前面 第三部分介绍Word2Vec模型的两种优化方案。 【万字长文】Word2Vec计算详解(一)CBOW模型 markdown行 9000 【万字长文】Word2Vec计算详解(二&#xff0…

PyCharm+ssh跳板机+服务器

PyCharmssh跳板机服务器 文章目录 PyCharmssh跳板机服务器准备工作登录服务器查看CUDA查看conda创建虚拟环境 前言配置ssh免密登录设置ssh隧道配置pycharm测试第一种第二种 传输数据 准备工作 登录服务器 直接ssh连接就行,在终端(命令行)直接输入下面命令: 跳板机&#xff1…

windows系统更新升级node指定版本【避坑篇!!!亲测有效】(附带各版本node下载链接)一定看到最后!不用删旧版!

Node.js 是一个开源、跨平台的 JavaScript 运行时环境,广泛应用于服务器端和网络应用的开发。随着 Node.js 版本的不断更新,我们可能需要升级到特定版本以满足项目需求或修复安全漏洞。又或者是学习开发另外一个新项目,新项目对Node版本要求更…

数学建模算法与应用 第12章 现代优化算法

目录 12.1 粒子群优化算法 Matlab代码示例:粒子群优化算法求解函数最小值 12.2 遗传算法 Matlab代码示例:遗传算法求解函数最小值 12.3 蚁群算法 Matlab代码示例:蚁群算法求解旅行商问题 12.4 Matlab 遗传算法工具 使用遗传算法工具箱…