前几天项目需要写一个登录压力测试机器人对服务器进行压力测试。服务器是使用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中解析到给定的消息结构中,函数原型如下:
func (buff *sendBuff) Write(args ...interface{}) bool
func (b *recvBuff) Read(arg ...interface{})
由于Go语言中没有泛型,所以需要对Write以及Read函数的参数进行类型判断,然后分别处理。
下面直接把源码附上:
import (
"bytes"
"encoding/binary"
"reflect"
)
//RecvBuffer RecvBuffer
type RecvBuffer interface {
Read(arg ...interface{})
}
//SendBuffer SendBuffer
type SendBuffer interface {
WriteHead(id uint16)
Bytes() []byte
Write(args ...interface{}) bool
}
type netHead struct {
Len uint32
Cmd uint16
}
type recvBuff struct {
RecvBuffer
buf *bytes.Buffer
}
//sendBuff sendBuff
type sendBuff struct {
SendBuffer
buf bytes.Buffer
}
//NewRecvBuffer NewRecvBuffer
func NewRecvBuffer(b []byte) RecvBuffer {
buff := new(recvBuff)
buff.buf = bytes.NewBuffer(b)
return buff
}
//NewSendBuffer NewSendBuffer
func NewSendBuffer() SendBuffer {
buff := new(sendBuff)
return buff
}
// Bytes Bytes
func (buff sendBuff) Bytes() []byte {
v := buff.buf.Bytes()
l := int32(buff.buf.Len())
buf := bytes.NewBuffer(v[:0])
binary.Write(buf, binary.LittleEndian, l)
return v
}
//WriteHead WriteHead
func (buff *sendBuff) WriteHead(id uint16) {
head := netHead{0, id}
buff.Write(head)
}
//Write Write
func (buff *sendBuff) Write(args ...interface{}) bool {
order := binary.LittleEndian
var err error
for _, arg := range args {
if arg == nil {
continue
}
t := reflect.TypeOf(arg)
kind := t.Kind()
switch kind {
case reflect.String:
v := arg.(string)
err = binary.Write(&buff.buf, order, uint32(len(v)))
buff.buf.Write([]byte(v))
case reflect.Struct:
v := reflect.ValueOf(arg)
for k := 0; k < t.NumField(); k++ {
value := v.Field(k).Interface()
if !buff.Write(value) {
return false
}
}
default:
err = binary.Write(&buff.buf, order, arg)
}
}
return err == nil
}
func read(b *recvBuff, order binary.ByteOrder, arg interface{}) {
switch t := arg.(type) {
case *string:
var x uint32
binary.Read(b.buf, order, &x)
s := make([]byte, x)
binary.Read(b.buf, order, &s)
*t = string(s)
default:
err := binary.Read(b.buf, order, t)
if err != nil {
panic(err)
}
}
}
func (b *recvBuff) Read(arg ...interface{}) {
order := binary.LittleEndian
for _, a := range arg {
b.read(order, a)
}
}
func readArray(b *recvBuff, order binary.ByteOrder, rv *reflect.Value) {
for i := 0; i < rv.Len(); i++ {
vi := rv.Index(i)
tp := vi.Type()
value := reflect.New(tp).Interface()
b.read(order, value)
vi.Set(reflect.ValueOf(value).Elem())
}
}
func (b *recvBuff) read(order binary.ByteOrder, arg interface{}) {
t := reflect.TypeOf(arg)
kind := t.Kind()
if kind != reflect.Ptr {
panic("must be a pointer")
}
v := reflect.ValueOf(arg)
t = t.Elem()
kind = t.Kind()
v = v.Elem()
if kind == reflect.Struct {
for i := 0; i < t.NumField(); i++ {
name := t.Field(i).Name
fd := v.FieldByName(name)
if fd.Type().Kind() == reflect.Array {
readArray(b, order, &fd)
} else {
value := fd.Addr().Interface()
b.read(order, value)
fd.Set(reflect.ValueOf(value).Elem())
}
}
} else if kind == reflect.Slice || kind == reflect.Array {
readArray(b, order, &v)
} else {
read(b, order, arg)
}
}
针对结构,使用了反射获取字段的数量,然后遍历字段,再递归调用。
Read时,支持结构、数组以及切片;Write时也支持结构以及标准类型的切片。