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
}