工作生活

从零开始的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

上一篇下一篇

猜你喜欢

热点阅读