前言:
其实之前已经用python实现过了(协议工具),最近又用go重构了一遍(刚入门go)。所以本篇会分别从go(详细讲)以及py(粗略讲)两方面讲叙。由于鄙人也是小小白,因此会讲的没那么高大上,尽可能俗一点,让其他刚进入游戏的老铁也能看个明白。
背景分析:
需求与方案制定:
至少需要实现:从客户端那边log输出中复制所有“发送协议–>>XXX”协议内容,通过工具可以实现并发去创建多个账号并执行这些协议内容。
为了应对协议内容的增删改变化,决定偷懒,不一一通过原有协议数据执行序列化与反序列化,而是全部通过GM协议执行。好处是可以偷懒,不需要采用其他方式(比如动态import去导入对应需要的xx.pd.go),坏处是不能够对所有协议作反序列化。但是这个并不影响我的核心需求。
1 2 3 4 5
| message CmdGMReqMsg { required string command = 1; } message CmdGMRspMsg { }
|
接下来请看代码实战。
GO篇
HTTP请求
“net/http”包使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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) } 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
|
proto文件处理
最简单直接的办法,从官网直接拉protoc.exe文件,通过命令行转换即可。
举个栗子:
protoc --proto_path=..\\protos --python_out=..\\protos ..\\protos\XXX.proto
转换成go之后得到文件:XX.pb.go,接着如何根据协议内容创建结构体呢?稍微看一下官方例子就不难写出:
1 2 3 4 5 6 7 8
| func Gmmsg(gm string) []byte { gm_mess := &pb.CmdGMReqMsg{ Command: &gm, } out, _ := proto.Marshal(gm_mess) return out }
|
我这边根据实际项目内容分析,协议最后都是嵌套作为message ClientCmdData中 data值,那么也不难写出:
1 2 3 4 5 6 7 8
| func Clientmsg(data []byte) []byte { clinetmsg := &pb.ClientCmdData{ Data: data, } out, _ := proto.Marshal(clinetmsg) return out }
|
websocket连接
主要使用包:"github.com/gorilla/websocket"
。这个去github学习一下,基本都能写出基础的websocket链接。
1 2 3 4 5 6 7 8 9 10
| 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 }
|
发送与接收,以及反序列化尝试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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) } return serdata }
|
上面注释代码举例了某个协议号的反序列化,得到反序列化后服务器的返回数据:
实际游戏的所有协议反序列化则不再详叙。
另外可以根据需要,多加点实用的功能,比如我增加的:如果服务器处理协议失败可以重发10次,超过10次不管;协议数据中最多会涉及到一个变化的playerid,这个是需要根据实际注册账号得到的playerid的;还有其他小细节可以在工具使用过程中慢慢改善。
协议Log内容处理
处理从客户端复制下来的一大批协议内容:发送协议:> XXXXXXX
这块主要涉及go文件处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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游戏角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 } time.Sleep(1 * time.Second) con.Close() }
|
go的并发(太香了)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| 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-------------")
|
实战结果
根据图中reg_time可以明确知道,我们的并发成功了,游戏中已经如实创建了我们想要的数据账号。快登录游戏享受吧!
Python篇
相信大伙的python都比我厉害,因此Python篇会简略一点。
- http请求用request
- proto文件处理同go
- websockets这个也不难
- asyncio使用
- log文件处理相信大家都会了
- config文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
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()) async with websockets.connect(_server_ws_url) as ws: await send_message(XXXXXX) def clientmsg(self, messageid, msg=None): cmsg = Cmd_pb2.ClientCmdData() cmsg.messageId = messageid cmsg.clientIndex = 0 if msg: cmsg.data = msg return cmsg
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| [config]
serverid = 冬天的秘密 _server_ws_url = 冬天的秘密
platformId = 冬天的秘密
nickname = nihao start = 39 num = 1
playerid = 冬天的秘密 log = 高级账号.log
|
另外还有一套配置用于jenkins:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| class config(object): def __init__(self): server = { "A服": '冬天的秘密', "B网": '冬天的秘密', } serverid = { "A服": XXX, "B网": XX, } platformId = { "A服": XX, "B网": XX, } log = { "高级账号": "D:/高级账号.log", "创建新号": "D:/创建新号.log", "XX": "D:/XX.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")]
|
优化
将ws接受单独出来作为单独协程处理接受服务器数据,该协程写在ws创建连接之后func connect() (c *websocket.Conn) {}
1 2 3 4 5 6 7 8 9 10
| func connect() (c *websocket.Conn) { go func(){ for{ } } }
|
同时把客户端发送的协议内容也一并写入Log日志,效果如上