其实之前已经用 python 实现过了(协议工具),最近又用 go 重构了一遍(刚入门 go)。所以本篇会分别从 go(详细讲)以及 py(粗略讲) 两方面讲叙。由于鄙人也是小小白,因此会讲的没那么高大上,尽可能俗一点,让其他刚进入游戏的老铁也能看个明白。
至少需要实现:从客户端那边 log 输出中复制所有 “发送协议-->>XXX” 协议内容,通过工具可以实现并发去创建多个账号并执行这些协议内容。
为了应对协议内容的增删改变化,决定偷懒,不一一通过原有协议数据执行序列化与反序列化,而是全部通过 GM 协议执行。好处是可以偷懒,不需要采用其他方式(比如动态 import 去导入对应需要的 xx.pd.go),坏处是不能够对所有协议作反序列化。但是这个并不影响我的核心需求。
message CmdGMReqMsg {
required string command = 1;
}
message CmdGMRspMsg {
}
接下来请看代码实战。
GO 篇"net/http"包使用
func Get(yoururl string, data map[string]string) map[string]interface{} {
request, err := http.NewRequest("GET", yoururl, nil)
if err != nil {
log.Println("err->", err)
}
//加入get参数
q := request.URL.Query()
for key, value := range data {
q.Add(key, value)
}
request.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(request)
if err != nil {
log.Println("err->", err)
}
defer resp.Body.Close()
data1, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println("err->", err)
}
rdata := string(data1)
result := make(map[string]interface{})
err1 := json.Unmarshal([]byte(rdata), &result)
if err1 != nil {
fmt.Println(err)
}
return result
最简单直接的办法,从官网直接拉 protoc.exe 文件,通过命令行转换即可。
举个栗子:
protoc --proto_path=..\\protos --python_out=..\\protos ..\\protos\XXX.proto
转换成 go 之后得到文件:XX.pb.go,接着如何根据协议内容创建结构体呢?稍微看一下官方例子就不难写出:
func Gmmsg(gm string) []byte {
gm_mess := &pb.CmdGMReqMsg{
Command: &gm,
}
//通过"google.golang.org/protobuf/proto"序列化
out, _ := proto.Marshal(gm_mess)
return out
}
我这边根据实际项目内容分析,协议最后都是嵌套作为 message ClientCmdData 中 data 值,那么也不难写出:
func Clientmsg(data []byte) []byte {
clinetmsg := &pb.ClientCmdData{
//部分字段省略
Data: data,
}
out, _ := proto.Marshal(clinetmsg)
return out
}
主要使用包:"github.com/gorilla/websocket"
。这个去 github 学习一下,基本都能写出基础的 websocket 链接。
func connect() (c *websocket.Conn) {
var addr string = "冬天的秘密"
var u = url.URL{Scheme: "ws", Host: addr, Path: "冬天的秘密"}
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
log.Printf("connecting to %s", u.String())
return
}
发送与接收,以及反序列化尝试
func Send(t []byte, c *websocket.Conn) *pb.ServerCmdData {
c.WriteMessage(websocket.BinaryMessage, t)
time.Sleep(500 * time.Millisecond)
_, message, _ := c.ReadMessage()
serdata := &pb.ServerCmdData{}
if err := proto.Unmarshal(message, serdata); err != nil {
log.Fatalln("failed----------:", err)
}
//在我有需要的时候去掉注释,可以针对指定协议反序列化拿到服务器返回的数据
// if *serdata.MessageId == int32(协议号) {
// temp := &pb.XXXXX{}
// if err := proto.Unmarshal(serdata.Data, temp); err != nil {
// log.Fatalln("failed----------:", err)
// }
// fmt.Printf("%+v#####", temp)
// }
return serdata
}
比如反序列化拿某个 message 的某个字段 id
另外可以根据需要,多加点实用的功能,比如知道服务器处理协议失败可以重发 10 次,超过 10 次不管;协议数据中最多会涉及到一个变化的 playerid,这个是需要根据实际注册账号得到的 playerid 的;还有其他小细节可以自己在使用过程中慢慢发挥。
处理从客户端复制下来的一大批协议内容:发送协议:> XXXXXXX
这块主要涉及 go 文件处理
func Handfile() (result []string) {
file := "冬天的秘密.log"
f, err := os.Open(file)
if err != nil {
log.Fatal(err)
}
defer func() {
if err = f.Close(); err != nil {
log.Fatal(err)
}
}()
s := bufio.NewScanner(f)
for s.Scan() {
result = append(result, s.Text())
}
err = s.Err()
if err != nil {
log.Fatal(err)
}
return
}
接收 log 内容,做一定处理(根据需要)然后发送;接收 nickname,根据 nickname 注册并创建 nickname 游戏角色
func Handmess(nickname string, mess []string) {
//涉及协议内容格式,不敢放太多
con := connect()
loginmsg := Clientmsg(1001, Loginmessage(token, accountid))
Resend(loginmsg, con)
registmsg := Clientmsg(1000, Regist(nickname, token, accountid))
serverrsg := Resend(registmsg, con)
if !*serverrsg.Result {
log.Printf("%s注册失败,该账号可能已经注册并在游戏中创建了账号(或者账号非法)", nickname)
return
}
//省略mess处理与发送
time.Sleep(1 * time.Second)
con.Close()
}
type Glimit struct {
n int
c chan struct{}
}
func New(n int) *Glimit {
return &Glimit{
n: n,
c: make(chan struct{}, n),
}
}
func (g *Glimit) Run(f func()) {
g.c <- struct{}{}
go func() {
f()
<-g.c
}()
}
g := New(20)
log.Printf("start-------------")
filemess := websockets_test.Handfile()
var wg sync.WaitGroup
for i := 100; i < 200; i++ {
wg.Add(1)
value := strconv.Itoa(i)
gofunc := func() {
websockets_test.Handmess("newnick"+value, filemess)
log.Printf("end-------------%s", "newnick"+value)
wg.Done()
}
g.Run(gofunc)
}
wg.Wait()
log.Printf("end-------------")
相信大伙的 python 都比我厉害,因此 Python 篇会简略一点。
# asyncio
async def run():
semaphore = asyncio.Semaphore(15)
to_get = [main_logic(i, semaphore) for i in range(num)]
await asyncio.wait(to_get)
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
# websockets
async with websockets.connect(_server_ws_url) as ws:
# ws.close_timeout = 200
await send_message(XXXXXX)
# pd数据的引用
def clientmsg(self, messageid, msg=None):
cmsg = Cmd_pb2.ClientCmdData()
cmsg.messageId = messageid
cmsg.clientIndex = 0
if msg:
cmsg.data = msg
return cmsg
[config]
# 服务器id
serverid = 冬天的秘密
_server_ws_url = 冬天的秘密
# 平台id
platformId = 冬天的秘密
# 你想要登录的账号,如果不存在会自动注册
nickname = nihao
start = 39
num = 1
# playerid(自增id),进阶功能,可以将协议数据中所有用到playerid替换为该player的正确playerid
playerid = 冬天的秘密
log = 高级账号.log
另外还有一套用于 jenkins 配置
class config(object):
def __init__(self):
server = {
"A服": '冬天的秘密',
"B网": '冬天的秘密',
}
serverid = {
"A服": XXX,
"B网": XX,
}
platformId = {
"A服": XX,
"B网": XX,
}
log = {
"高级账号": "D:/高级账号.log",
"创建新号": "D://创建新号.log",
"第一章": "D://什么都不做只通关第一章.log",
}
if not os.getenv("choice_server"):
print("你没有选择服务器!!!")
sys.exit(0)
if not os.getenv("可选协议") and not os.getenv("protos"):
print("你没有填写任何协议内容!!!")
sys.exit(0)
self.serverid = serverid[os.getenv("choice_server")]
self.platformId = platformId[os.getenv("choice_server")]
gamename = re.split(r'\n',os.getenv("gamename"))
self.nickname = gamename[0]
self.num = gamename[2]
self.start = gamename[1]
if os.getenv("可选协议"):
self.log = linecache.getlines(log[os.getenv("可选协议")])
else:
self.log = re.split(r'\n', os.getenv("protos"))
self._server_ws_url = server[os.getenv("choice_server")]