go读写网络消息
前几天项目需要写一个登录压力测试机器人对服务器进行压力测试。服务器是使用C++写的,为了快捷完成机器人,我并没有选择C++来写,一方面使用C++来写代码量比较大,另外一方面使用C++来模拟几百上千个机器人写起来没Erlang,Go,C#等这些自带协程(Erlang称为进程,与操作系统进程概念不一样)的语言写起来方便快捷。
我主要考虑使用Erlang或者Go来写。 前几年我使用Erlang语言做过一款MMORPG游戏的服务器,使用过2年多时间。使用Erlang写机器人这种程序非常适合,虽然有几年没使用了,但捡起来应该还是比较快。 另外就是Go语言,最近几年Go语言被越来越的公司使用,越来越火,用了十几年的C/C++了,也想学习一些新的流行语言。学习归学习还是要实践才出真知,要有项目做练习才能熟练。最终选择了Go来写机器人。
由于服务器(老项目迭代的)是使用的C++编写,而且没有使用Protobuf作为网络消息的协议,而是直接使用的古老的二进制序列化,所以机器人这边也需要按这种古老的方式进行处理。 为了方便阅读与维护,机器人这边不能像服务器一样单个变量进行序列化,而是定义成了一个结构,在收发消息时,根据消息号去处理相应的结构,当然也需要支持多个变量进行序列化。比如: 发送消息时,只需要把要发送的消息结构填充好,调用一个Write函数就序列化到发送Buffer中了;收消息时,只需要调用一个Read函数就可以从接收Buffer中解析到给定的消息结构中,函数原型如下:
1func (buff *sendBuff) Write(args ...interface{}) bool
2func (b *recvBuff) Read(arg ...interface{})
由于Go语言中没有泛型,所以需要对Write以及Read函数的参数进行类型判断,然后分别处理。 下面直接把源码附上:
1import (
2 "bytes"
3 "encoding/binary"
4 "reflect"
5)
6
7//RecvBuffer RecvBuffer
8type RecvBuffer interface {
9 Read(arg ...interface{})
10}
11
12//SendBuffer SendBuffer
13type SendBuffer interface {
14 WriteHead(id uint16)
15 Bytes() []byte
16 Write(args ...interface{}) bool
17}
18
19type netHead struct {
20 Len uint32
21 Cmd uint16
22}
23
24type recvBuff struct {
25 RecvBuffer
26 buf *bytes.Buffer
27}
28
29//sendBuff sendBuff
30type sendBuff struct {
31 SendBuffer
32 buf bytes.Buffer
33}
34
35//NewRecvBuffer NewRecvBuffer
36func NewRecvBuffer(b []byte) RecvBuffer {
37 buff := new(recvBuff)
38 buff.buf = bytes.NewBuffer(b)
39 return buff
40}
41
42//NewSendBuffer NewSendBuffer
43func NewSendBuffer() SendBuffer {
44 buff := new(sendBuff)
45 return buff
46}
47
48// Bytes Bytes
49func (buff sendBuff) Bytes() []byte {
50 v := buff.buf.Bytes()
51 l := int32(buff.buf.Len())
52 buf := bytes.NewBuffer(v[:0])
53 binary.Write(buf, binary.LittleEndian, l)
54 return v
55}
56
57//WriteHead WriteHead
58func (buff *sendBuff) WriteHead(id uint16) {
59 head := netHead{0, id}
60 buff.Write(head)
61}
62
63//Write Write
64func (buff *sendBuff) Write(args ...interface{}) bool {
65 order := binary.LittleEndian
66 var err error
67 for _, arg := range args {
68 if arg == nil {
69 continue
70 }
71 t := reflect.TypeOf(arg)
72 kind := t.Kind()
73 switch kind {
74 case reflect.String:
75 v := arg.(string)
76 err = binary.Write(&buff.buf, order, uint32(len(v)))
77 buff.buf.Write([]byte(v))
78 case reflect.Struct:
79 v := reflect.ValueOf(arg)
80 for k := 0; k < t.NumField(); k++ {
81 value := v.Field(k).Interface()
82 if !buff.Write(value) {
83 return false
84 }
85 }
86 default:
87 err = binary.Write(&buff.buf, order, arg)
88 }
89 }
90 return err == nil
91}
92
93func read(b *recvBuff, order binary.ByteOrder, arg interface{}) {
94 switch t := arg.(type) {
95 case *string:
96 var x uint32
97 binary.Read(b.buf, order, &x)
98 s := make([]byte, x)
99 binary.Read(b.buf, order, &s)
100 *t = string(s)
101 default:
102 err := binary.Read(b.buf, order, t)
103 if err != nil {
104 panic(err)
105 }
106 }
107}
108
109func (b *recvBuff) Read(arg ...interface{}) {
110 order := binary.LittleEndian
111 for _, a := range arg {
112 b.read(order, a)
113 }
114}
115
116func readArray(b *recvBuff, order binary.ByteOrder, rv *reflect.Value) {
117 for i := 0; i < rv.Len(); i++ {
118 vi := rv.Index(i)
119 tp := vi.Type()
120 value := reflect.New(tp).Interface()
121 b.read(order, value)
122 vi.Set(reflect.ValueOf(value).Elem())
123 }
124}
125
126func (b *recvBuff) read(order binary.ByteOrder, arg interface{}) {
127 t := reflect.TypeOf(arg)
128 kind := t.Kind()
129 if kind != reflect.Ptr {
130 panic("must be a pointer")
131 }
132 v := reflect.ValueOf(arg)
133 t = t.Elem()
134 kind = t.Kind()
135 v = v.Elem()
136 if kind == reflect.Struct {
137 for i := 0; i < t.NumField(); i++ {
138 name := t.Field(i).Name
139 fd := v.FieldByName(name)
140 if fd.Type().Kind() == reflect.Array {
141 readArray(b, order, &fd)
142 } else {
143 value := fd.Addr().Interface()
144 b.read(order, value)
145 fd.Set(reflect.ValueOf(value).Elem())
146 }
147 }
148 } else if kind == reflect.Slice || kind == reflect.Array {
149 readArray(b, order, &v)
150 } else {
151 read(b, order, arg)
152 }
153}
针对结构,使用了反射获取字段的数量,然后遍历字段,再递归调用。
Read时,支持结构、数组以及切片;Write时也支持结构以及标准类型的切片。
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2021/2021-03-10-go读写网络消息/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。