1.初步分析
dev Tool调试发现多人在线编辑,类似于多人在线聊天,服务器负责收集客户端消息,然后集中处理服务器本地存储的文档。
调试发现,该服务基于websocket协议,通过分析ws消息流,可以得到客户端和服务器交互的方式。
- 客户端链接,服务器回复一条数据
- 客户端发送带有“client ready”的数据
- 客户端6秒间隔发送心跳包
- 接下来就可以正常发送编辑、修改的消息了
截取了一条编辑的数据,可以看到
type: USER_CHANGES
task_id: 需要和上一次上上次……不一样,建议随机数实现
c:[["BB08J2",7,7,4,4],……表示操作的单元格8行5列
"2":[165,"@\\\\℃:@"]……表示内容,修改"@\\\\℃:@"即可
这里用golang实现该ws客户端:
// Copyright 2015 The Gorilla WebSocket Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"github.com/gorilla/websocket"
"log"
"os"
"os/signal"
"time"
)
//var addr = flag.String("addr", "localhost:8080", "http service address")
func main() {
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
//1.创建连接
c, _, err := websocket.DefaultDialer.Dial(wspath, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
//开启接收消息goroutine
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s", message)
}
}()
//发送client ready消息
c.WriteMessage(websocket.BinaryMessage,[]byte(`42["post","{\"apid\":{\"s\":\"554557239478\",\"t\":1652718593193,\"i\":\"DUFpBc0JhWXRia3Vk\",\"g\":\"f7be4c0beadf755e6b25852d69c8cb83725a42a4\",\"apid\":\"\",\"r\":99},\"type\":\"CLIENT_READY\",\"task_id\":808447158586389,\"docid\":\"300000000$PZAsBaYtbkud\",\"uid\":\"144115217770477896\",\"cookie\":\" SID=6587823b09994d1c9b3ed14a1986e711715ea884b3d5487690649ecc92f23cfe;\",\"base_rev\":99,\"code_ver\":1,\"session_type\":0,\"gray\":0,\"dver\":\"2.10.0\",\"wl\":\"\",\"u\":\"7b2be78e572847379db881e1288db933\",\"generalpacket\":{\"rel_rev\":\"sheet-hotfix-20220513_1447-a8b28d0a44\",\"dver\":\"2.10.27540461\",\"right_tag\":1},\"docs_type\":\"sheet\"}\n{\"roomType\":\"padpage\",\"roomName\":\"padpage/300000000$PZAsBaYtbkud\",\"upid\":\"DUFpBc0JhWXRia3Vk\",\"gid\":\"300000000$PZAsBaYtbkud\",\"sid\":\"554557239478\",\"tid\":1652718593193,\"sig\":\"f7be4c0beadf755e6b25852d69c8cb83725a42a4\",\"data\":{\"stats\":{\"screen\":\"\"}}}"]`))
//用来发送心跳消息
ticker := time.NewTicker(6*time.Second)
defer ticker.Stop()
for {
select {
case <-done:
return
case <-ticker.C:
err = c.WriteMessage(websocket.TextMessage, []byte("42[\"post\",\"{\\\"type\\\":\\\"HEART_BEAT\\\",\\\"task_id\\\":8245210039230595,\\\"docid\\\":\\\"300000000$PZAsBaYtbkud\\\",\\\"uid\\\":\\\"144115217770477896\\\"}\\n\"]"))
if err != nil {
log.Println("write:", err)
return
}
//捕获ctrl+c,没啥卵用
case <-interrupt:
log.Println("interrupt")
err = c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
就简单做了一个示例