close

因為是Golang的新手,因此想試著寫一個Socket Server(整體來說是給遊戲用的)來練習。

網路上找到的Socket Server在介紹Packet這塊都相當簡易,像聊天室那類的大都是拿換行符號來當Packet的切割做例子,不太符合遊戲的需求,因此寫了一個簡易的PacketHelper來處理一個完整來回的Packet。

一個完整的Packet描述如下

+------------------------------------+

+ Header |  Payload | CRC----+

+ 4bytes  | n bytes   | 4bytes +

+------------------------------------+

只要將Accept到的net.Conn傳入給PacketHelper,後續只透過Helper來代理進行封包的Read/Write (Recv/Send)

package network

import (
    "fmt"
    "hash/crc32"
    "net"
    "io"

    util "../utility"
)

//一個Packet接收的三個階段
const (
    waitHead    = 1 
    waitPayload = 2 
    waitCRC     = 3 
)

//自訂的Header大小
const HeaderSize = 4

//自訂的Crc CheckSum大小
const TailCrcSize = 4

type PacketHelper struct {
    _conn        net.Conn
    _payloadSize int 
}

//在一個Circle (head>payload>crc)完成之前,處於blocking的狀態
func (p *PacketHelper) Read() ([]byte, int, error) {

    recvBuff := []byte{}
    curState := waitHead

    for {
        switch curState {
        case waitHead:
            {
                buf, err := ReadLimitedLength(p._conn, HeaderSize)
                if err != nil {
                    return nil, 0, err
                }

                p._payloadSize = util.BytesToInt(buf)
                curState = waitPayload
            }
        case waitPayload:
            {
                buf, err := ReadLimitedLength(p._conn, p._payloadSize)
                if err != nil {
                    return nil, 0, err
                }

                recvBuff = append(recvBuff, buf...)
                curState = waitCRC
            }
        case waitCRC:
            {
                buf, err := ReadLimitedLength(p._conn, TailCrcSize)
                if err != nil {
                    return nil, 0, err
                }

                ClientCrcCheckSum := util.BytesToUInt(buf)
                ServerCrcCheckSum := crc32.ChecksumIEEE(recvBuff)

                if ClientCrcCheckSum != ServerCrcCheckSum {
                    return nil, 0, fmt.Errorf("CRC CheskSum Error!")
                }

                return recvBuff, p._payloadSize, nil
            }
        }
    }
}

func (p *PacketHelper) Write(payload []byte) {

    writer := util.NewBinaryWriter()

    //因為自製BinaryWriter在WriteBytes時已經會寫入Size(等於是這個Packet的Header),因此不用再二次寫入Header
    //writer.WriteInt(len(payload))
    writer.WriteBytes(payload)
    writer.WriteUInt(crc32.ChecksumIEEE(payload))

    packet := writer.ToBytes()
    p._conn.Write(packet)
}

func NewPacketHelper(conn net.Conn) *PacketHelper {
    reader := &PacketHelper{
        _conn:        conn,
        _payloadSize: 0,
    }
    return reader
}

//自訂的payload最大長度
const maximunBufferSize = 65535

//frag的Size可以限制conn讀取的長度,所以不會有讀超過的問題,但一次的Read可能低於需求量(封包特大的時候),所以必須對frag resize
func ReadLimitedLength(conn net.Conn, length int) ([]byte, error) {

    if length > maximunBufferSize {
        //return nil, 0, errors.New(fmt.Sprintf("Require size(%d) too long.", length))
        return nil, fmt.Errorf("Require size(%d) too long.", length)
    }

    buffer := []byte{}
    frag := make([]byte, length)
    totalSize := 0
    for {
        size, err := conn.Read(frag)
        if err != nil {
            if err != io.EOF {
                fmt.Println("read error:", err)
            }
            return nil, err
        }

        buffer = append(buffer, frag[:size]...)

        totalSize += size
        if totalSize == length {
            return buffer[:totalSize], nil
        }

        frag = make([]byte, length-totalSize)
    }
}

 

*Read在收完一個完整的Packet之前處於Blocking狀態

*這裡沒有做timeout等相關處理

*BinaryReader/BinaryWriter是土法煉鋼寫的utility,下篇再附上

 

下一篇:

[Golang] 學習筆記(2) BinaryReader/BinaryWriter

 

 

arrow
arrow
    文章標籤
    golang packet socket
    全站熱搜
    創作者介紹
    創作者 不來嗯 的頭像
    不來嗯

    不來嗯(咻~)的技術筆記

    不來嗯 發表在 痞客邦 留言(0) 人氣()