前几天项目需要写一个登录压力测试机器人对服务器进行压力测试。服务器是使用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时也支持结构以及标准类型的切片。