开发者学堂课程【Go 语言核心编程 - 面向对象、文件、单元测试、反射、TCP 编程:海量用户通讯系统——服务端结构改进2】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/626/detail/9804
2、对应操作:
(1)打开 server,新建第一个包包(文件夹)“main.go”。一般会有一个主包(主包相当于一个渐变层)。根据分析,应还有一个包存在,创建一个新的文件夹命名为”process“作用是处理器的那一层包。根据分析,应还有一个”model”包,这个包目前还无法写东西。另外还有一个”utils“包,这个包存放相应文件。这些做完后先进行保存,然后将现在的main扔到主文件中(操作:先复制,再将其粘贴进主文件,注意不要直接顿,发现没问题后,再将main进行删除)。根据分析,在process里面应该有两个文件,一个是”userProcess.go”(处理用户相关的),将此包包打起来,这个文件叫processes,接着进行保存;另一个是”smProcess.go”(处理短消息相关的)接着同理文件叫process。这两个写完后,看第三个,第三个有一个总控,一般来讲总控可以放在process,有些程序员也将它直接放在main,因为这些程序员认为总控和main关系很密切,因为main拿到交给总控让它去指挥,因此有些程序员直接把总控放在main这个包包下,这也是可以的。接着选用这种思想进行接下来的操作。将总控放在“processor.go”。在“utils”这个包下面写一个工具文件(就是专门放置工具类函数的)”utils.go“。
(2) 首先readpkg和readpkg应该属于工具类性质的(属于工具类性质原因:因为每个地方均要使用),将其复制一把再注销(复制readpkg),注意不要剪,然后将其扔入”utils.go”,接着输入
Package utils
Import(
“fmt”
)
再将刚复制的代码扔入,因为这样代码是不可运行的,接着进行处理。接着将writepkg的内容扔过来,写完后保存,会发现有大量的错误,首先包就没有来。将processor.go的包打入“package main”,包里面可以没东西,但不能没有包名。由于里面的writepkg可能会调用一些其他文件,因此存在的错误不能立即修改。所以先进行保存再进行接下来的操作。
fanc这处是处理登陆的,而serverProcessMes这处是处理消息的,此处相当于总控的部分。因此可将fanc这端代码拿掉,因为它是处理用户登录的因此从逻辑上来讲,应将他扔到“userprocess.go”。将代码复制将其扔入”userprosess.go”,先引一个包包,如下Package utils
Import(
“fmt”
)
先将其扔过来,扔过来之后再进行保存,这其中一定存在大量错误,先忽略错误,接着进行剥离,剥离后在调节代码。将剥离的代码进行注销。接着看serverProcessMes,显然这个文件应该放置在总控定位盒里,因为是process在serverProcessMes这个文件里决定到底是用哪一个process文件来处理。例如处理登陆的serverProcessLogin,处理注册用的是message.RegisterMesType,但在后面消息发来是可能要进行私聊,因此在这个地方进行的就是分配管控的目的,所以说将serverProcessMes扔到”processor.go”里面,将这段代码复制在注销,拿到”processor.go”里面,这时再进行保存。接着看一下现在的结构是什么样子的,文件实际上只保留了一点东西。在fanc main这里将其拿到另一个地方后,它进行了一个处理,它让它得到了一个连接,然后去处理一个server,这个结构就变化了。因为main.go的任务已经发生变化了,任务是监听、等待客户的连接、初始化的工作、调用processor让它去完成调度任务,那就意味着代码差不多要进行调整了。在调试过程中,先从最底层的走,也就是说那个被调用的多,就从这一层开始反着推过去将代码写完(写代码时刚好跟分析时是逆向的)。接着进行分析首先找到“utils.go”来一个个进行分析。因为现在用的是结构体的思路,既然用的是分成加面向对象,那么这个地方一定要做一个结构体。这里将这些方法关联到结构体中,然后写一个结构体”type Transfer struct”,接着分析它应该有哪些字段。认为作为一个传输者,连接一定要有(因为如果没有连接,无法传输),第二个应该有一个缓冲,这两个是必须存在的。那么这里首先来一个conn net.conn,因此前面的Package utils
Import(
“fmt”
)应该进行调整,将其调整为
Package utils
Import(
“fmt”
”net”
接着将buf直接放入字段中,跟着Transfer的实例走,如下:
conn net.conn
Buf[8096]byte
这里的Buf切片,写上数字就是宿主了。使用的时候当切片使用也是一样的。因为取时便是当作切片(切片和宿主的区别,用时指定一个起始位置那么它便是切片)。接着输入如下内容,如下:
conn net.conn
Buf[8096]byte//这时传输时,使用缓冲
这两个方法就是readPkg和Transfer要关联在一起,以后只要用到传输了,只要创建一个实例调查方法即可,接着走一个。
到此,已经将utils选完了,也就是说此时此刻已经将其剥离出来了,并且调试通过了。反过来应该写smsProcess.go和userProcess.go了,因为smsprocess.go是有空的,所以先不去考虑,先将userProcess.go搞出来,思路同上。先写出一个结构体,就叫”userProcess”,输入type userprocess,接着分析有哪些字段。有哪些字段主要是要考虑用来绑定或关联的需要哪些东西,由观察可得连接是必须的(因为将来userprocess再进行处理的时候没有连接,没有一个connect是不行的,因此要有一个连接。),所以将连接写入“conn net.conn”,其他的可以不要,buf可以不要,将buf绑定到了utils.go。到先通过userprocess将连接传给了utils就可以了。接着再来写绑定,如下:
//编写一个函数serverProcessLogin函数,专门处理登录请求
fanc (this *UserProcess) ServerProcessLogin(conn net.Conn, mes *message.Messsage) (err errror) {
//核心代码
//1. 先从mes中取出 mes . Data ,并直接反序列化成LoginMes
var loginMes message.LoginMes
err = json.Unmarshal([ ]byte(mes.Data), &loginMes)
if err != nil {
fmt.PrintIn(“json.Unmarshal fail err=”, err)
return
}
写完后会发现仍有错误,首先引包
package process
Import(
“fmt”
“net”
“go_code/chatromm/common/message”
)
接着往下看会发现一个json,将json也粘贴过来,如下
package process
Import(
“fmt”
“net”
“go_code/chatromm/common/message”
”encoding/binary”
“encoding/json”
)
粘贴过后保存一下。由于binary不需要因此将它删掉,并保存。
//6. undefined: writePkg到writePkg函数
err = writePkg(conn, data)
return
观察上面的的代码会发现现在的writePkg没有了,因为现在的关系是由userprocess去调用utils,所以这个时候的逻辑就会发生变化。就是说writePkg就要调用传输userProcess的对象,这是第一个要进行更改的。第二个,因为userProcess里面已经含有conn连接了,因此在传输时不需要在传输一个conn。也就是说把链接绑定到userProcess,以后只要用到连接不需要每个参数都传进来,只需要从字段中取即可。因为conn已经存在所以再次将其删除,由于删除的原因肯定要有很大的改动,接着进行更改。将发送这里进行改动,改动为
//6.发送data,我们将其分装到writePkg函数
//因为使用了分层模式(mvc),我们先创建一个Transfer 实例,然后读取
tf :=&utils.Transfer{
Conn : this.conn
}
err =tf. WritePkg(conn,data)
Return
接着将上面改动为
package process
Import (
“fmt”
“net”
“go_code/chatroom/common/message”
“go_code/chatroom/server/utils”
“encoing/json”
接着此处处理完毕,会发现下面一处还有问题
//6. 发送data,我们将其封装到writePkg函数
//因为使用分层模式(mvc),我们先创建一个Transfer 实例,然后读取
tf := &utils.Transfer {
Conn : this.Conn,
}
err = tf.writePkg(conn,data)
return
}
这里的conn没有带,这样从此处开始要做大的改动了。主要是将连接的方式进行改变,因为conn没有了,所以writePkg的conn也不需要了。readPkg里面也有与writePkg一样的连接,这个连接也没有必要再给了,因为连接Transfer已经有了,所以这里的也可以不要,也就是说Transfer和readPkg也要发生变化。将readPkg里面的的内容删掉,由于缺乏连接会发现有大量的错误。将以下内容进行更改
func (this *Transfer) ReadPkg() (mes message.Message, err error) {
//buf :=make([ ]byte, 8096)
fmt.PrintIn(“读取客户端发送的数据”)
//conn.Read 在conn没有被关闭的情况下才会阻塞
//如果客户端关闭了 conn 则,才会阻塞
_,err = conn.Read(Buf[:4])
If err != nil {
//err = errors.New(“read pkg header error”)
return
}
更改为
func (this *Transfer) ReadPkg() (mes message.Message, err error) {
//buf :=make([ ]byte, 8096)
fmt.PrintIn(“读取客户端发送的数据”)
//conn.Read 在conn没有被关闭的情况下才会阻塞
//如果客户端关闭了 conn 则,就不会阻塞
_,err = this.conn.Read(this.Buf[:4])
If err != nil {
//err = errors.New(“read pkg header error”)
return
}
将Buf前都填上this,在conn前也填上this,以这个作为改动。接着将writePkg里的conn也删掉,因为这边的Transfer内也有东西。将一些内容稍作更改,更改为
func (this *Transfer) writePkg(data [ ]byte) (err error) {
//先发送一个长度给对方
var pkgLen uint32
pkgLen = uint32(len(data))
//var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
//发送长度
n, err : = this.Conn.write(this.Buf[:4])
if n != 4 | | err != nil {
fmt.PrintIn(“conn.write(bytes) fail”,err)
Return
}
//发送data本身
n, err = this.Conn.write(data)
if n != int(pkgLen) | | err != nil {
fmt.PrintIn(“conn.write(bytes) fail”, err)
return
}
return
}
更改后,保存起来,进行接下来的操作。因为utils还有一个错误,如下:
//先发送一个长度给对方
var pkgLen uint32
pkgL buf declared and not used
//var buf [4]byte
binary.BigEndian.PutUint32(this.Buf[0:4], pkgLen)
//发送长度
n, err := this.conn.write(this.Buf[:4])
if n != 4 | | err != nil {
fmt.PrintIn(“conn.write(bytes) fail”,err)
return
}
其中有一个buf,这个已经没有意义了,所以删除或暂时先将其注销。完成上述步骤后,接着进行下面的处理。
//6. 发送data,我们将其装到writePkg函数
//因为使用分层模式(mvc),我们先创建一个Transfer 实例,然后读取
tf := &utils.Transfer {
Conn : this.Conn,
}
err = tf.writePkg(conn,data)
return
}
如上,由于使用的是writePkg所以里面的conn已经不需要传了,所以将它更改为err = tf.WritePkg(data),接着进行保存。这样serverProcess就处理完了,这个处理完后,接着进行剥取。接着剥取processor.go,先进入processor.go,输入“先创建一个Processor的一个结构体
type processor struct{
conn : net.conn
}
接着观察代码,看还有哪一处需要改动,创建了一个userProcessor,发现要用ServerProcessLogin来完成登录的事情,因此将它创建好,创建好之后调用它的方法存进去,最后该如何返回便如何返回。完成这些之后的任务就是如何将其一层一层调用起来,就是将userProcess.go、main、processor.go的关系搞懂即可。接着继续操作,main有一段代码是processor,processor里面有一堆循环,循环的逻辑先是读包包,读完包包看serverProcessMes。由此,这段代码最好交给总控去做,也就是说主函数就做一件事情,拿到连接起个协程。所以说可以对这串代码进行以下处理,将代码全部镂空剪走,将剪走的代码直接扔到图中这处。
func (this *Processor) process2() {
//循环的客户端发生的消息
for {
//这里我们将读取数据包,直接封装成一个函数readPkg( ),返回Message, Err
mes,err != readPkg(conn)
if err != nil {
if err == io.EOF
fmt.PrintIn(“客户端退出,服务端也退出..”)
return
{ else {
fmt.PrintIn(“readPkg err=“, err)
Return
}
}
err = serverProcessMes(conn, &mes)
if err != nil {
return
}
摘过来之后,还要创建一个Transfer 实例,完成读包的任务。接着输入代码
tf :=&utils.Transfer
conn this.conn,
输入后将mes,err行代码改为mes,err :tf.readPkg(),下面的代码不进行改动。由于连接不需要了,将 serverProcessMes进行改动,改为err = this.serverProcessMes(&mes),接着进行保存。因为刚刚将主函数剪空了,所以输入“这里要调用总控,创建一个”,接着输入代码“processor :=&processor”。这里有一个重要的东西就是连接,这个连接刚好从最先层的主函数一层一层创建下去。接着输入
processor :=&processor
Conn : conn,
接着调用process2,输入返回值,如下
func (this *Processor) process2() (err error) {
//循环的客户端发生的消息
for {
//这里我们将读取数据包,直接封装成一个函数readPkg( ),返回Message, Err
/创建一个Transfer 实例完成读包任务
tf := &utils.Transfer{
Conn : this.Conn,
}
mes, err := tf.ReadPkg( )
if err != nil {
fmt.PrintIn(“客户端退出,服务端也退出..”)
return
} else
fmt.PrintIn(“readPkg err=”, err)
return
}
接着引包,如下
package main
Import (
“fmt”
“net”
“go_code/chatroom/common/message”
“go_code/chatroom/server/utils”
)
引完之后,接着查找问题。因为processor用的是另一个包包,所以接着引入它的处理包,如下
package main
Import (
“fmt”
“net”
“go_code/chatroom/common/message”
“go_code/chatroom/server/utils”
“go_code/chatroom/server/process”
)
用的时候,由于是个包所以在 UserProcess 前加一个 process。经处理,现在应该没有太大问题了。经查找发现仍有七个错误,其中一个是 ReadPkg 没有大写,接着发现引入中缺少一些东西,将其引入为下面代码所示。
package main
Import (
“fmt”
“net”
“go_code/chatroom/common/message”
“go_code/chatroom/server/utils”
“go_code/chatroom/server/process”
“io”
)
引入后进行保存。接着查找错误,在main的processor里,将其改为err := processor.process2
If err !=nil
fmt.PrintIn(“客户端和服务器通讯的协程错误=err”,err)
Return
改完此处后,接着查找错误,会发现main.go引入的包包里面,很多都没有用到(因为现在将许多东西已经向下挪了),因此将它们删除,删除后结果如下代码所示
package main
import (
“fmt”
“net”
)
将其删掉后,再进行保存。保存过后,processor还有四个错误。接着调整错误,经查找,发现是err不明确,调整完如下所示
mes, err := tf.ReadPkg( )
if err != nil {
if err == io.EOF
fmt.PrintIn(“客户端退出,服务端也退出..”)
return err
} else(
fmt.PrintIn(“readPkg err=”, err”)
return err
}
err = this.serverProcessMes(&mes)
if err != nil {
return err
}
调整后进行保存。然后接着进行调整,查找到最后一个问题,包包名和一个函数名相同了,更改包的名字,改成 process2,然后保存,就没有错了。然后在调用的时候仍有一处需要改动,将up :那一行的 process 改成 process2,这样就可以了,然后进行再次保存,这次没有报错。接着尝试编写一下,看代码是否通过。在主函数入口就就新写一句话“服务器【新的结构】在8889监听....”,因为现在客户端不需要发生任何变化也应该能够运行起来,因为现在改动的地方与客户端没有任何关系,所以如果代码欸有问题,那么代码运行则不受影响。接着保存一下,然后打开 goproject,然后验证代码是否可以运行。将左面当成服务器,然后输入代码如下
接着查看是否可以运行,先输入一个不存在的用户名200,密码为abc,接着运行会发现代码完全正确。接着在尝试一下登录成功是否可以,输入用户名100,密码123456,再次验证了代码的正确性。重新编写,接着输入一个错误的用户名200和密码tom,显示输入正确,显示该用户不存在,请重新注册。接着在尝试一下存在的,读取,然后登录成功。也就是说通过刚才程序的改动,尽管改了很多东西,服务器仍是可以的。