从零开始的Go Socket Server(二):支持结构化数据
2019-07-03 本文已影响0人
洞渊的自习室
连续发送数据的问题
首先从Client端开始修改,让Client连续的发送多个json数据,看下上一节的Server端的输出有什么问题。
修改send方法,将发送的数据替换为连续的json,每个json数据中包含一个index和时间戳
func send(conn net.Conn) {
for i := 0; i < 30; i++ {
dic := make(map[string]interface{})
dic["index"] = i
dic["timestamp"] = time.Now().Format(time.RFC3339)
jsonString, err := json.Marshal(dic)
if err != nil {
log.Println(err)
}
_, err = conn.Write([]byte(jsonString))
if err != nil {
log.Println(err)
}
}
log.Println("send finished")
}
输出的数据会出现这种情况,多个json数据包连接在了一起。
127.0.0.1:53709 receive data string:
{"index":0,"timestamp":"2019-07-02T11:29:32+08:00"}{"index":1,"timestamp":"2019-07-02T11:29:32+08:00"}{"index":2,"timestamp":"2019-07-02T11:29:32+08:00"}
很明显,这样的数据格式是不能直接解析的。所以我们需要一个数据传输协议来提取正确的数据格式。
这个地方我们使用一个标志字符串(Header
)和数据长度(长度数据本身占用5个字符串长度)来作为传输数据解析的依据。
在Client端首先计算要发送的数据长度,然后将长度写在发送数据的前面。Server端接收到数据后读取长度信息,然后截取指定的长度数据,如果一次报文长度不够则等待下次的报文一起进行解析。
增加传输协议
首先我们定义数据长度的最大字符数是5位,即最大99999个字符。然后在发送数据时将Header
和字符长度写到发送数据的前面。
func send(conn net.Conn) {
for i := 0; i < 30; i++ {
dic := make(map[string]interface{})
dic["index"] = i
dic["timestamp"] = time.Now().Format(time.RFC3339)
jsonString, err := json.Marshal(dic)
if err != nil {
log.Println(err)
}
length := len(jsonString)
if length > 99999 {
//Header中标识字符串长度的最大为99999
panic("data is too long to send")
}
lengthText := strconv.Itoa(length)
textLength := fmt.Sprintf("%05s", lengthText)[:5]
headerText := append([]byte("Header"), textLength...)
jsonString = append(headerText, jsonString...)
_, err = conn.Write([]byte(jsonString))
if err != nil {
log.Println(err)
}
log.Println("send : ", jsonString)
}
log.Println("send finished")
}
每次发送的数据变成了这个样子
Header00052{"index":28,"timestamp":"2019-07-03T09:27:04+08:00"}
在服务端对接收到的数据进行拆解,
const HeaderText = "Header"
const HeaderTextLength = len(HeaderText)
const LengthTextLength = 5
func handleConnection(conn net.Conn) {
buffer := make([]byte, 2048, 2048)
for {
n, err := conn.Read(buffer)
if err != nil {
log.Println(conn.RemoteAddr().String(), " read error: ", err)
return
}
tmpBuffer := buffer[:n]
log.Println(conn.RemoteAddr().String(), "receive data string:")
//解析buffer中的内容
for {
if len(tmpBuffer) == 0 {
break
}
if string(tmpBuffer[:HeaderTextLength]) != HeaderText {
panic("buffer not started with 'Header'")
}
lengthText := string(tmpBuffer[HeaderTextLength: HeaderTextLength + LengthTextLength])
textLength, err := strconv.Atoi(lengthText)
if err != nil {
log.Println(err)
}
content := tmpBuffer[HeaderTextLength + LengthTextLength: HeaderTextLength + LengthTextLength + textLength]
tmpBuffer = tmpBuffer[HeaderTextLength + LengthTextLength + textLength:]
fmt.Println(string(content))
}
}
}
每次读到数据后,先读取接下来的数据的长度信息,然后根据长度截取数据,剩下的数据继续按照这个流程进行处理。最终将数据拆分成单条的json数据。
现在是一个Client在向Server发送数据,实际情况会有大量的Client,所以下一步我们要增加对多个Client的支持。
完整的代码在goSocket,Tag 1.1