52. Socket Server 自定义协议的简单实现

作者: 厚土火焱 | 来源:发表于2017-09-15 01:01 被阅读161次

    在 Server 和 Client 通讯中,由于网络等原因很有可能会发生数据丢包的现象。如果数据确实,服务端接收的信息不完整,就会造成混乱。
    我们就需要在 Server 和 Client 之间建立一个通讯协议,通过协议中的规则,判断当前接收到的信息是否完整。根据信息的完整情况,采取不同的处理方法。
    通讯协议 protocol 的核心就是设计一个头部。如果传来的信息不包含这个头部,就说明当前信息和之前的信息是同一条。那么就把当前信息和之前的那条信息合并成一条。
    而协议主要包含的功能是封装(Enpack)和解析(Depack)。
    Enpack 是客户端对信息进行数据封装。封装之后可以传递给服务器。
    Depack 是服务端对信息进行数据解封。
    其中有个 Const 部分,用于定义头部、头部长度、客户端传入信息长度。
    在代码中,我们这样定义

    const (
        ConstHeader = "Headers"
        ConstHeaderLength = 7
        ConstMLength = 4
    )
    

    头部的内容为 "Headers",长度为 7 。
    所以 ConstHeaderLength = 7
    而信息传递中,我们会把 int 类型转换成 byte 类型。一个 int 的长度等于 4 个 byte 的长度。因此,我们设置 ConstMLength = 4。代表客户端传来的信息大小。
    自定义协议 protocol 的代码示例如下:

    /**
    * protocol
    * @Author:  Jian Junbo
    * @Email:   junbojian@qq.com
    * @Create:  2017/9/14 11:49
    *
    * Description:  通讯协议处理
    */
    package protocol
    
    import (
        "bytes"
        "encoding/binary"
    )
    
    const (
        ConstHeader = "Headers"
        ConstHeaderLength = 7
        ConstMLength = 4
    )
    
    //封包
    func Enpack(message []byte) []byte {
        return append(append([]byte(ConstHeader), IntToBytes(len(message))...), message...)
    }
    
    //解包
    func Depack(buffer []byte) []byte {
        length := len(buffer)
    
        var i int
        data := make([]byte, 32)
        for i = 0; i < length; i++ {
    
            if length < i + ConstHeaderLength + ConstMLength{
                break
            }
            if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
                messageLength := ByteToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
                if length < i+ConstHeaderLength+ConstMLength+messageLength {
                    break
                }
                data = buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
            }
        }
    
        if i == length {
            return make([]byte, 0)
        }
    
        return data
    }
    
    //字节转换成整形
    func ByteToInt(n []byte) int {
        bytesbuffer := bytes.NewBuffer(n)
        var x int32
        binary.Read(bytesbuffer, binary.BigEndian, &x)
    
        return int(x)
    }
    
    //整数转换成字节
    func IntToBytes(n int) []byte {
        x := int32(n)
        bytesBuffer := bytes.NewBuffer([]byte{})
        binary.Write(bytesBuffer, binary.BigEndian, x)
        return bytesBuffer.Bytes()
    }
    
    

    Server 端主要是通过协议来解析客户端发送来的信息。
    建立一个函数,用来完成连接对接收信息的处理。其中建立了通道readerChannel,并把接收来的信息放在通道里。在放入通道之前,使用 protocol 的 Depack 对信息进行解析。

    //连接处理
        func handleConnection(conn net.Conn) {
            //缓冲区,存储被截断的数据
            tmpBuffer := make([]byte, 0)
            //接收解包
            readerChannel := make(chan []byte, 10000)
            go reader(readerChannel)
        
            buffer := make([]byte, 1024)
            for{
                n, err := conn.Read(buffer)
                if err != nil{
                    Log(conn.RemoteAddr().String(), "connection error: ", err)
                    return
                }
                tmpBuffer = protocol.Depack(append(tmpBuffer, buffer[:n]...))
                readerChannel <- tmpBuffer      //接收的信息写入通道
        
            }
            defer conn.Close()
        }
    

    如果信息读取发生错误(包括读取到信息结束符 EOF),都会打印错误信息,并挑出循环。

    Log(conn.RemoteAddr().String(), "connection error: ", err)
    return
    

    由于通道内的数据是 []byte 型的。需要转换成 string。这个工作有专门的获取通道数据的 reader(readerChannel chan []byte) 来完成。

    //获取通道数据
    func reader(readerchannel chan []byte) {
        for{
            select {
            case data := <-readerchannel:
                Log(string(data))       //打印通道内的信息
            }
        }
    }
    

    查看 Server 端代码示例:

    /**
    * MySocketProtocalServer
    * @Author:  Jian Junbo
    * @Email:   junbojian@qq.com
    * @Create:  2017/9/14 13:54
    * Copyright (c) 2017 Jian Junbo All rights reserved.
    *
    * Description:  服务端,接收客户端传来的信息
    */
    package main
    
    import (
        "net"
        "fmt"
        "os"
        "log"
        "protocol"
    )
    
    func main() {
        netListen, err := net.Listen("tcp", "localhost:7373")
        CheckErr(err)
        defer netListen.Close()
    
        Log("Waiting for client ...")       //启动后,等待客户端访问。
        for{
            conn, err := netListen.Accept()     //监听客户端
            if err != nil {
                Log(conn.RemoteAddr().String(), "发了了错误:", err)
                continue
            }
            Log(conn.RemoteAddr().String(), "tcp connection success")
            go handleConnection(conn)
        }
    }
    
        //连接处理
        func handleConnection(conn net.Conn) {
            //缓冲区,存储被截断的数据
            tmpBuffer := make([]byte, 0)
            //接收解包
            readerChannel := make(chan []byte, 10000)
            go reader(readerChannel)
    
            buffer := make([]byte, 1024)
            for{
                n, err := conn.Read(buffer)
                if err != nil{
                    Log(conn.RemoteAddr().String(), "connection error: ", err)
                    return
                }
                tmpBuffer = protocol.Depack(append(tmpBuffer, buffer[:n]...))
                readerChannel <- tmpBuffer      //接收的信息写入通道
    
            }
            defer conn.Close()
        }
    
    //获取通道数据
    func reader(readerchannel chan []byte) {
        for{
            select {
            case data := <-readerchannel:
                Log(string(data))       //打印通道内的信息
            }
        }
    }
    
    //日志处理
    func Log(v ...interface{}) {
        log.Println(v...)
    }
    
    //错误处理
    func CheckErr(err error) {
        if err != nil {
            fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
            os.Exit(1)
        }
    }
    
    

    客户端使用 Enpack 封装要发送到服务端的信息后,写入连接 conn 中。

    /**
    * MySocketProtocalClient
    * @Author:  Jian Junbo
    * @Email:   junbojian@qq.com
    * @Create:  2017/9/14 15:23
    * Copyright (c) 2017 Jian Junbo All rights reserved.
    *
    * Description:  
    */
    package main
    
    import (
        "net"
        "time"
        "strconv"
        "protocol"
        "fmt"
        "os"
    )
    
    //发送100次请求
    func send(conn net.Conn)  {
        for i := 0; i < 100; i++ {
            session := GetSession()
            words := "{\"ID\":\""+strconv.Itoa(i)+"\",\"Session\":\""+session+"20170914165908\",\"Meta\":\"golang\",\"Content\":\"message\"}"
            conn.Write(protocol.Enpack([]byte(words)))
            fmt.Println(words)      //打印发送出去的信息
        }
        fmt.Println("send over")
        defer conn.Close()
    }
    //用当前时间做识别。当前时间的十进制整数
    func GetSession() string {
        gs1 := time.Now().Unix()
        gs2 := strconv.FormatInt(gs1, 10)
        return gs2
    }
    
    func main() {
        server := "localhost:7373"
        tcpAddr, err := net.ResolveTCPAddr("tcp4", server)
        if err != nil{
            fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
            os.Exit(1)
        }
    
        conn, err := net.DialTCP("tcp", nil, tcpAddr)
        if err != nil{
            fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
            os.Exit(1)
        }
    
        fmt.Println("connect success")
    
        send(conn)
    
    }
    
    

    代码运行效果:

    服务端运行效果 客户端运行效果

    相关文章

      网友评论

        本文标题:52. Socket Server 自定义协议的简单实现

        本文链接:https://www.haomeiwen.com/subject/nikjsxtx.html