Golangでのバイトシーケンス化操作


ネットワークプログラムを書くとき、構造体や整数などのデータ型をバイナリのbuffer列にシーケンス化する必要があります.あるいは1つのbufferから1つの構造体が解析され、最も典型的にはプロトコルのheader部分でhead lengthまたはbody lengthがパケットのパッケージングとパケットの取り外しの過程で、所定の整数タイプに従って解析する必要があり、サイズエンドシーケンスの問題に関連する.
1.Cでの操作方法
Cで一番簡単な方法はmemcpyで×××数や構造体などの他のタイプを1つのメモリにコピーし、必要なタイプに強く戻します.次のようになります.
    // produce
    int a = 32;
    char *buf  = (char *)malloc(sizeof(int));
    memcpy(buf,&a,sizeof(int));

    // consume
    int b ;
    memcpy(&b,buf,sizeof(int))

必要に応じてntoh/htonシリーズ関数を用いてサイズエンドシーケンスの変換を行う.
2.golangでの操作
「encoding/binary」を使用すると、一般的なバイナリシーケンス化機能を提供できます.このモジュールには、主に次のインタフェースがあります.
func Read(r io.Reader, order ByteOrder, data interface{}) error
func Write(w io.Writer, order ByteOrder, data interface{}) error
func Size(v interface{}) int

var BigEndian bigEndian
var LittleEndian littleEndian
/*
type ByteOrder interface {
Uint16([]byte) uint16
Uint32([]byte) uint32
Uint64([]byte) uint64
PutUint16([]byte, uint16)
PutUint32([]byte, uint32)
PutUint64([]byte, uint64)
String() string
}
/*

Readインタフェースではbufの内容をdataパラメータで表されるデータ構造に埋め込むことができ,Writeインタフェースではdataパラメータに含まれるデータをbufferに書き込むことができる.変数BigEndianおよびLittleEndianは、ByteOrderインタフェースを実装するオブジェクトであり、インタフェースで提供される方法によってuintxタイプをbufに直接シーケンス化(uintx()または逆シーケンス化(putuintx()することができる.
2.1構造体を1つのbufにシーケンス化する
構造オブジェクトをシーケンス化する場合、シーケンス化された構造のサイズは既知である必要があり、その構造のサイズはSizeインタフェースによって取得され、bufferのサイズを決定することができることに注意してください.
i := uint16(1)
size :=  binary.Size(i)

固定サイズの構造体は、構造体に[]byteのようなスライスメンバーが現れないことが要求されます.そうしないと、Sizeは-1を返し、通常のシーケンス化操作はできません.
type A struct {
    // should be exported member when read back from buffer
    One int32
    Two int32
}

var a A


a.One = int32(1)
a.Two = int32(2)

buf := new(bytes.Buffer)
fmt.Println("a's size is ",binary.Size(a))
binary.Write(buf,binary.LittleEndian,a)
fmt.Println("after write ,buf is:",buf.Bytes())

対応する出力は次のとおりです.
a's size is  8
after write ,buf is : [1 0 0 0 2 0 0 0]

必要なbufferの大きさはSizeで得ることができます.Writeによりオブジェクトaの内容をbufferにシーケンス化することができる.ここでは、小エンドシーケンス方式でシーケンス化を行う(x 86アーキテクチャはいずれも小エンドシーケンスであり、ネットワークバイトシーケンスは大エンドシーケンスである).
構造体の場合「」メンバーはシーケンス化されません.
2.2 bufから1つの構造に逆シーケンス化する
bufferから読み込む場合は、同様に構造体のサイズを固定し、逆シーケンス化が必要な構造体メンバーは、エクスポート可能、すなわち大文字で始まるメンバーでなければなりません.逆シーケンス化しない:
type A struct {
    // should be exported member when read back from buffer
    One int32
    Two int32
}

var aa A

buf := new(bytes.Buffer)
binary.Write(buf,binary.LittleEndian,a)
binary.Read(buf,binary.LittleEndian,&aa)
fmt.Println("after aa is ",aa)

出力:
after write ,bufis : [1 0 0 0 2 0 0 0]
before aa is : {0 0}
after aa is  {1 2}

ここではReadを用いてbufferから構造体オブジェクトaaにデータをインポートする.構造体の対応するメンバーがエクスポート可能でない場合、変換時にpanicエラーが発生します.
2.3整数をbufにシーケンス化し、bufから逆シーケンス化する
Read/Writeでuintxタイプの変数を直接読み書きすることで×××数のシーケンス化と逆シーケンス化.ネットワークでは×××数のシーケンス化は非常に一般的であるため、システムライブラリはtype ByteOrderインタフェースを提供し、uint 16/uint 32/uint 64のシーケンス化と逆シーケンス化を容易に行うことができます.
int16buf := new(bytes.Buffer)
i := uint16(1)
binary.Write(int16buf,binary.LittleEndian,i)
fmt.Println(“write buf is:”int16buf.Bytes())

var int16buf2 [2]byte
binary.LittleEndian.PutUint16(int16buf2[:],uint16(1))
fmt.Println("put buffer is :",int16buf2[:])

ii := binary.LittleEndian.Uint16(int16buf2[:])
fmt.Println("Get buf is :",ii)

出力:
write buffer is : [1 0]
put buf is: [1 0]
Get buf is : 1

binaryを呼び出すLittleEndian.PutUint 16は、uint 16型のデータを、小端順の形式でbufferにシーケンス化することができる.binaryを通ります.LittleEndian.UIT 16はbufferの内容を逆シーケンス化する.
3.実際の例
ネットワーク・パケット・ヘッダの定義と初期化を見てみましょう.
type Head struct {
    Cmd byte
    Version byte
    Magic   uint16
    Reserve byte
    HeadLen byte
    BodyLen uint16
}

func NewHead(buf []byte)*Head{
    head := new(Head)

    head.Cmd     = buf[0]
    head.Version = buf[1]
    head.Magic   = binary.BigEndian.Uint16(buf[2:4])
    head.Reserve = buf[4]
    head.HeadLen = buf[5]
    head.BodyLen = binary.BigEndian.Uint16(buf[6:8])
    return head
}

これは一般的なtcpパッケージングの例です.例ではbinary.BigEndian.UIT 16は、データをネットワーク順の形式で読み出し、headの対応する構造に入れる.