• go 客户端服务端通信


    client.go

    package main
    
    import (
        "bufio"
        "encoding/json"
        "fmt"
        "hash/crc32"
        "math/rand"
        "net"
        "os"
        // "sync"
        "time"
    )
    
    //数据包类型
    const (
        HEART_BEAT_PACKET = 0x00
        REPORT_PACKET     = 0x01
    )
    
    //默认的服务器地址
    var (
        server = "127.0.0.1:8080"
    )
    
    //数据包
    type Packet struct {
        PacketType    byte
        PacketContent []byte
    }
    
    //心跳包
    type HeartPacket struct {
        Version   string `json:"version"`
        Timestamp int64  `json:"timestamp"`
    }
    
    //数据包
    type ReportPacket struct {
        Content   string `json:"content"`
        Rand      int    `json:"rand"`
        Timestamp int64  `json:"timestamp"`
    }
    
    //注册
    type RegisterReq struct {
        PERAESKey  string `json:"PERAESKey"`
        VIN        string `json:"VIN"`
        T_Box_SN   string `json:"T_Box_SN"`
        IMSI       string `json:"IMSI"`
        rollNumber string `json:"rollNumber"`
    }
    
    //客户端对象
    type TcpClient struct {
        connection *net.TCPConn
        hawkServer *net.TCPAddr
        stopChan   chan struct{}
    }
    
    func main() {
        //拿到服务器地址信息
        hawkServer, err := net.ResolveTCPAddr("tcp", server)
        if err != nil {
            fmt.Printf("hawk server [%s] resolve error: [%s]", server, err.Error())
            os.Exit(1)
        }
        //连接服务器
        connection, err := net.DialTCP("tcp", nil, hawkServer)
        if err != nil {
            fmt.Printf("connect to hawk server error: [%s]", err.Error())
            os.Exit(1)
        }
        client := &TcpClient{
            connection: connection,
            hawkServer: hawkServer,
            stopChan:   make(chan struct{}),
        }
        //启动接收
        go client.receivePackets()
    
        //发送心跳的goroutine
        /*go func() {
            heartBeatTick := time.Tick(2 * time.Second)
            for {
                select {
                case <-heartBeatTick:
                    client.sendHeartPacket()
                case <-client.stopChan:
                    return
                }
            }
        }()*/
    
        //测试用的,开300个goroutine每秒发送一个包
        // for i := 0; i < 1; i++ {
        go func() {
            sendTimer := time.After(5 * time.Second)
            for {
                select {
                case <-sendTimer:
                    client.sendReportPacket()
                    sendTimer = time.After(1 * time.Second)
                case <-client.stopChan:
                    return
                }
            }
        }()
        // }
        //等待退出
        <-client.stopChan
    }
    
    // 接收数据包
    func (client *TcpClient) receivePackets() {
        reader := bufio.NewReader(client.connection)
        for {
            //承接上面说的服务器端的偷懒,我这里读也只是以
    为界限来读区分包
            msg, err := reader.ReadString('
    ')
            if err != nil {
                //在这里也请处理如果服务器关闭时的异常
                close(client.stopChan)
                break
            }
            fmt.Print(msg)
        }
    }
    
    //发送数据包
    //仔细看代码其实这里做了两次json的序列化,有一次其实是不需要的
    func (client *TcpClient) sendReportPacket() {
        registPacket := RegisterReq{
            PERAESKey:  "123456",
            VIN:        "abcdef",
            T_Box_SN:   "abcdef123456",
            IMSI:       "IMSI",
            rollNumber: getRandString(),
            /*Content:   getRandString(),
            Timestamp: time.Now().Unix(),
            Rand:      rand.Int(),*/
        }
        fmt.Println("registPacket:", registPacket)
        packetBytes, err := json.Marshal(registPacket) //返回值是字节数组byte[],
        if err != nil {
            fmt.Println(err.Error())
        }
        //这一次其实可以不需要,在封包的地方把类型和数据传进去即可
        /*packet := Packet{
            PacketType:    REPORT_PACKET,
            PacketContent: packetBytes,
        }
        sendBytes, err := json.Marshal(packet)
        if err != nil {
            fmt.Println(err.Error())
        }*/
        //发送
    
        client.connection.Write(EnPackSendData(packetBytes))
        // fmt.Println("EnPackSendData(packetBytes):%v", EnPackSendData(packetBytes))
        // fmt.Println("Send metric data success!")
    }
    
    //使用的协议与服务器端保持一致
    func EnPackSendData(sendBytes []byte) []byte {
        packetLength := len(sendBytes) + 8
        result := make([]byte, packetLength)
        result[0] = 0xFF
        result[1] = 0xFF
        result[2] = byte(uint16(len(sendBytes)) >> 8) //除以2的8次方,byte是0-255,
        result[3] = byte(uint16(len(sendBytes)) & 0xFF)
        copy(result[4:], sendBytes)
        sendCrc := crc32.ChecksumIEEE(sendBytes)
        result[packetLength-4] = byte(sendCrc >> 24)
        result[packetLength-3] = byte(sendCrc >> 16 & 0xFF)
        result[packetLength-2] = 0xFF
        result[packetLength-1] = 0xFE
        fmt.Println(result)
        return result
    }
    
    //发送心跳包,与发送数据包一样
    func (client *TcpClient) sendHeartPacket() {
        heartPacket := HeartPacket{
            Version:   "1.0",
            Timestamp: time.Now().Unix(),
        }
        packetBytes, err := json.Marshal(heartPacket)
        if err != nil {
            fmt.Println(err.Error())
        }
        packet := Packet{
            PacketType:    HEART_BEAT_PACKET,
            PacketContent: packetBytes,
        }
        sendBytes, err := json.Marshal(packet)
        if err != nil {
            fmt.Println(err.Error())
        }
        client.connection.Write(EnPackSendData(sendBytes))
        fmt.Println("Send heartbeat data success!")
    }
    
    //拿一串随机字符
    func getRandString() string {
        // length := rand.Intn(10)
        strBytes := make([]byte, 10)
        for i := 0; i < 10; i++ {
            strBytes[i] = byte(rand.Intn(26) + 97)
        }
        return string(strBytes)
    }
    
    /*作者:getyouyou
    链接:https://www.jianshu.com/p/dbc62a879081
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/

    server.go

    package main
    
    import (
        "bufio"
        "encoding/json"
        "fmt"
        "hash/crc32"
        "io"
        "net"
        "os"
    )
    
    //数据包的类型
    const (
        HEART_BEAT_PACKET = 0x00
        REPORT_PACKET     = 0x01
    )
    
    var (
        server = "127.0.0.1:8080"
    )
    
    //这里是包的结构体,其实是可以不需要的
    type Packet struct {
        PacketType    byte
        PacketContent []byte
    }
    
    //注册
    type RegisterReq struct {
        PERAESKey  string `json:"PERAESKey"`
        VIN        string `json:"VIN"`
        T_Box_SN   string `json:"T_Box_SN"`
        IMSI       string `json:"IMSI"`
        rollNumber string `json:"rollNumber"`
    }
    
    //心跳包,这里用了json来序列化,也可以用github上的gogo/protobuf包
    //具体见(https://github.com/gogo/protobuf)
    type HeartPacket struct {
        Version   string `json:"version"`
        Timestamp int64  `json:"timestamp"`
    }
    
    //正式上传的数据包
    type ReportPacket struct {
        Content   string `json:"content"`
        Rand      int    `json:"rand"`
        Timestamp int64  `json:"timestamp"`
    }
    
    //与服务器相关的资源都放在这里面
    type TcpServer struct {
        listener   *net.TCPListener
        hawkServer *net.TCPAddr
    }
    
    func main() {
        //类似于初始化套接字,绑定端口
        hawkServer, err := net.ResolveTCPAddr("tcp", server)
        checkErr(err)
        //侦听
        listen, err := net.ListenTCP("tcp", hawkServer)
        checkErr(err)
        //记得关闭
        defer listen.Close()
        tcpServer := &TcpServer{
            listener:   listen,
            hawkServer: hawkServer,
        }
        fmt.Println("start server successful......")
        //开始接收请求
        for {
            conn, err := tcpServer.listener.Accept()
            fmt.Println("accept tcp client %s", conn.RemoteAddr().String())
            checkErr(err)
            // 每次建立一个连接就放到单独的协程内做处理
            go Handle(conn)
        }
    }
    
    //处理函数,这是一个状态机
    //根据数据包来做解析
    //数据包的格式为|0xFF|0xFF|len(高)|len(低)|Data|CRC高16位|0xFF|0xFE
    //其中len为data的长度,实际长度为len(高)*256+len(低)
    //CRC为32位CRC,取了最高16位共2Bytes
    //0xFF|0xFF和0xFF|0xFE类似于前导码
    func Handle(conn net.Conn) {
        // close connection before exit
        defer conn.Close()
        // 状态机状态
        state := 0x00
        // 数据包长度
        length := uint16(0)
        // crc校验和
        crc16 := uint16(0)
        var recvBuffer []byte
        // 游标
        cursor := uint16(0)
        bufferReader := bufio.NewReader(conn)
        //状态机处理数据
        for {
            recvByte, err := bufferReader.ReadByte() //recvByte是每次读到的字节
            if err != nil {
                //这里因为做了心跳,所以就没有加deadline时间,如果客户端断开连接
                //这里ReadByte方法返回一个io.EOF的错误,具体可考虑文档
                /*Handle方法在一个死循环中使用了一个无阻塞的buff来读取套接字中的数据,
                  因此当客户端主动关闭连接时,如果不对这个io.EOF进行处理,会导致这个goroutine空转,
                  疯狂吃cpu,在这里io.EOF的处理非常重要:)*/
                if err == io.EOF {
                    fmt.Printf("client %s is close!
    ", conn.RemoteAddr().String())
                }
                //在这里直接退出goroutine,关闭由defer操作完成
                return
            }
            //进入状态机,根据不同的状态来处理
            switch state {
            case 0x00:
                if recvByte == 0xFF {
                    state = 0x01
                    //初始化状态机
                    recvBuffer = nil
                    length = 0
                    crc16 = 0
                } else {
                    state = 0x00
                }
                break
            case 0x01:
                if recvByte == 0xFF {
                    state = 0x02
                } else {
                    state = 0x00
                }
                break
            case 0x02:
                length += uint16(recvByte) * 256      //length这次是发送数据的长度
                fmt.Println("0x02,length:%d", length) //0
                state = 0x03
                break
            case 0x03:
                length += uint16(recvByte)
                fmt.Println("0x03,length:%d", length) //77
                // 一次申请缓存,初始化游标,准备读数据
                recvBuffer = make([]byte, length)
                cursor = 0
                state = 0x04
                break
            case 0x04:
                //不断地在这个状态下读数据,直到满足长度为止
                recvBuffer[cursor] = recvByte
                cursor++
                if cursor == length {
                    state = 0x05
                }
                break
            case 0x05:
                crc16 += uint16(recvByte) * 256 //crc32编码
                state = 0x06
                break
            case 0x06:
                crc16 += uint16(recvByte)
                state = 0x07
                break
            case 0x07:
                if recvByte == 0xFF {
                    state = 0x08
                } else {
                    state = 0x00
                }
            case 0x08:
                if recvByte == 0xFE {
                    //执行数据包校验
                    if (crc32.ChecksumIEEE(recvBuffer)>>16)&0xFFFF == uint32(crc16) {
                        var packet RegisterReq
                        //把拿到的数据反序列化出来
                        json.Unmarshal(recvBuffer, &packet)
                        //新开协程处理数据
                        go processRecvData(&packet, conn)
                    } else {
                        fmt.Println("丢弃数据!")
                    }
                }
                //状态机归位,接收下一个包
                state = 0x00
            }
        }
    }
    
    //在这里处理收到的包,就和一般的逻辑一样了,根据类型进行不同的处理,因人而异
    //我这里处理了心跳和一个上报数据包
    //服务器往客户端的数据包很简单地以
    换行结束了,偷了一个懒:),正常情况下也可根据自己的协议来封装好
    //然后在客户端写一个状态来处理
    func processRecvData(packet *RegisterReq, conn net.Conn) {
        // switch packet.PacketType {
        // case HEART_BEAT_PACKET:
        // var beatPacket HeartPacket
        // json.Unmarshal(packet.PacketContent, &beatPacket)
        fmt.Printf("recieve heat beat from [%s] ,data is [%v]
    ", conn.RemoteAddr().String(), packet)
        conn.Write([]byte("heartBeat
    "))
        return
        // case REPORT_PACKET:
        //     var reportPacket ReportPacket
        //     json.Unmarshal(packet.PacketContent, &reportPacket)
        //     fmt.Printf("recieve report data from [%s] ,data is [%v]
    ", conn.RemoteAddr().String(), reportPacket)
        //     conn.Write([]byte("Report data has recive
    "))
        //     return
        // }
    }
    
    //处理错误,根据实际情况选择这样处理,还是在函数调之后不同的地方不同处理
    func checkErr(err error) {
        if err != nil {
            fmt.Println(err)
            os.Exit(-1)
        }
    }
    
    /*作者:getyouyou
    链接:https://www.jianshu.com/p/dbc62a879081
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/
  • 相关阅读:
    Python中的浅复制、深复制
    Python
    CSS中
    Fluent_Python_Part3函数即对象,05-1class-func,一等函数,函数即对象
    Python
    本地简单HTTP服务器
    Fluent_Python_Part2数据结构,04-text-byte,文本和字节序列
    Fluent_Python_Part2数据结构,03-dict-set,字典和集合
    Fluent_Python_Part2数据结构,02-array-seq,序列类型
    Codeforces 246E Blood Cousins Return(树上启发式合并)
  • 原文地址:https://www.cnblogs.com/yaowen/p/8468863.html
Copyright © 2020-2023  润新知