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

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

目录

框架与目录结构

老韩讲的是分层架构,就和MVC差不多,服务器框架图如下

目录结构

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

utils.go

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如下:

go 复制代码
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

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

go 复制代码
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

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
}
相关推荐
hummhumm2 分钟前
第 12 章 - Go语言 方法
java·开发语言·javascript·后端·python·sql·golang
hummhumm2 分钟前
第 8 章 - Go语言 数组与切片
java·开发语言·javascript·python·sql·golang·database
杜杜的man7 分钟前
【go从零单排】Directories、Temporary Files and Directories目录和临时目录、临时文件
开发语言·后端·golang
qq_308957477 分钟前
Gin 框架入门(GO)-1
开发语言·golang·gin
@东辰12 小时前
【golang-技巧】-自定义k8s-operator-by kubebuilder
开发语言·golang·kubernetes
@东辰13 小时前
【golang-技巧】- 定时任务 - cron
开发语言·golang·cron
jerry60920 小时前
7天用Go从零实现分布式缓存GeeCache(改进)(未完待续)
分布式·缓存·golang
杜杜的man20 小时前
【go从零单排】Closing Channels通道关闭、Range over Channels
开发语言·后端·golang
甘橘籽1 天前
【RPC】 gRPC、pb基本使用--经验与总结
golang
杜杜的man1 天前
【go从零单排】HTTP客户端和服务端
开发语言·http·golang