首先,因为真实的客户端不能做到像pitaya-cli一样通过命令行(connect 127.0.0.1:3250)直接连接到服务器,所以需要我们了解客户端与服务端建立tcp连接的过程以及传输报文结构。
这里我们采用tcp连接工具(网络调试助手)来模拟客户端
1
查看pitaya-cli的源码,找到connect命令调用的函数,根据调用堆栈发现connect函数第一步是执行了sendHandshakeRequest()函数:
改代码直接让main函数调用到这里,发现发送的数据的ASCII码是这个:
\x01\x00\x00z{\"sys\":{\"platform\":\"mac\",\"libVersion\":\"0.3.5-release\",\"clientBuildNumber\":\"20\",\"clientVersion\":\"1.0.0\"},\"user\":{\"age\":30}}"
再根据调用的Encode函数得知,其中\x01=type=1,\x00\x00z=len(data)=122
那么服务端在收到数据后又干了什么呢?
这里我们需要进行源码调试来帮助我们理解。为了方便调试,这里直接将源码的demo(pitaya@v1.1.8\examples\demo\cluster\main.go)作为服务器代码。在整个源码中搜索handshake,找到这个函数:func (h *HandlerService) processPacket(,根据调用堆栈推断这里很有可能是处理客户端发来的handshake数据的地方,打上断点来验证:这里选中并直接F5运行起来服务端与客户端的main.go。服务端收到handshake,并回复客户端(SendHandshakeResponse()函数):
在整个源码中搜索handshakeResponse,找到这个函数:func (c *Client) handleHandshakeResponse(,并在客户端所在的项目找到它并打上断点。调试得知:客户端收到数据包handshakePacket,其中最重要的是这个Type=1=packet.Handshake表示数据包是handshake类型。
客户端收到handshake:
2
紧接着,客户端又发送Type=2=packet.HandshakeAck的数据包给服务端
(发送的数据的ASCII码是这个:\x02\x00\x00\x00)
服务端收到HandshakeAck:至此,tcp握手完毕,建立连接成功
客户端打印了connected!表示成功连接了
3
然后调试发送实际业务请求的流程:
客户端main函数添加:
logger := log.New(os.Stdout, "", log.LstdFlags)
executeCommand(logger, "connect 127.0.0.1:3250")
executeCommand(logger, "request connector.connector.getsessiondata")
select {}
其中我们请求了前端服务器connector的getsessiondata函数,在服务端找到并打个断点。根据调用堆栈找到客户端发送业务数据的核心函数:func (c *Client) sendMsg(
找到最终发送出去的数据的ASCII码:
"\x04\x00\x00%\x00\x01\"connector.connector.getsessiondata"
绿色部分:1个byte 对应constans.go文件的常量:Handshake= 0x01等 ,4表示是业务数据
蓝色部分:3个byte=0x25=% 对应"\x00\x01\"connector.connector.getsessiondata"这一串数据的长度=37
红色部分:分别对应结构体message.Message的type,id(每次累加1)
底层消息数据结构:
服务端收到业务数据请求(packet.Data=Type=4 表示业务)
最终到达GetSessionData()函数
总结
建立连接到发送业务过程:
1 客户端发送handlshake,服务端返回handlshake
2 客户端发送handshakeAck,连接成功
3 最后发业务数据
客户端发送数据(ASCII码):
1 \x01\x00\x00z{\"sys\":{\"platform\":\"mac\",\"libVersion\":\"0.3.5-release\",\"clientBuildNumber\":\"20\",\"clientVersion\":\"1.0.0\"},\"user\":{\"age\":30}}
2 \x02\x00\x00\x00
3 \x04\x00\x00%\x00\x01\"connector.connector.getsessiondata
然后我们直接通过网络调试助手来按照顺序发送以上内容来模拟真实客户端发送情况:
1
2
3
客户端发送的结构体格式:
1
type 1 byte
len 3 byte
type HandshakeData struct {
Platform string `json:"platform"`
LibVersion string `json:"libVersion"`
BuildNumber string `json:"clientBuildNumber"`
Version string `json:"clientVersion"`
User map[string]interface{} `json:"user,omitempty"`
}
2
type 1 byte
len 3 byte
3
type 1 byte
len 3 byte
type Message struct {
Type Type // message type
ID uint // unique id, zero while notify mode
Route string // route for locating service
Data []byte // payload
compressed bool // is message compressed
Err bool // is an error message
}