※「GOならわかるシステムプログラミング」の3章の学習記録です。
主に
の2節の内容の学習記録です。
sample
sampleコードは以下
package main import ( "bytes" "encoding/binary" "fmt" "hash/crc32" "io" "os" ) const ( OFFSET = 8 ) func dumpChunk(chunk io.Reader) { var l int32 binary.Read(chunk, binary.BigEndian, &l) // png をbyteに変換した時の入れ物 buf := make([]byte, 4) chunk.Read(buf) fmt.Printf("chunk '%v' (%d byte)\n", string(buf), l) if bytes.Equal(buf, []byte("tExt")) { rawText := make([]byte, l) chunk.Read(rawText) fmt.Printf("%s\n", string(rawText)) } } func readChunks(file *os.File) []io.Reader { // pngをchunkに分けたときのchunkを格納する入れ物 var chunks []io.Reader // pngの最初の8byteを飛ばして9byte目から読み込む file.Seek(OFFSET, 0) var ofs int64 = OFFSET for { // data長のいれもの var l int32 err := binary.Read(file, binary.BigEndian, &l) if err == io.EOF { break } chunks = append(chunks, io.NewSectionReader(file, ofs, int64(l)+12)) // 次のchunkの先頭に移動 ofs, _ = file.Seek(int64(l+8), 1) } return chunks } func textChunk(txt string) io.Reader { byteData := []byte(txt) var buf bytes.Buffer binary.Write(&buf, binary.BigEndian, int32(len(byteData))) // 書き込むためのbufferを確保。長さは入力文字長。 buf.WriteString("tExt") //bufferに書き込む buf.Write(byteData) // 入力対象の文字をbufferに書き込む // CRCを計算して追加 crc := crc32.NewIEEE() io.WriteString(crc, "tExt") binary.Write(&buf, binary.BigEndian, crc.Sum32()) // crc分を新しくbufferに書き込む return &buf // 書き込み終わったbufferを返す } func WriteNewTextChunk() { // pngをチャンクに分けて読み込み file, err := os.Open("./imgs/Lenna.png") if err != nil { fmt.Printf("error! err: %v", err) os.Exit(-1) } defer file.Close() newFile, _ := os.Create("./imgs/result.png") defer newFile.Close() chunks := readChunks(file) // pngのシグニチャー書き込み io.WriteString(newFile, "\x89PNG\r\n\x1a\n") // 先頭にIHDR chunkを書き込む io.Copy(newFile, chunks[0]) // TextChunk を追加する io.Copy(newFile, textChunk("Test Text Chunk")) // 残りのチャンクを新しく追加する for _, c := range chunks[1:] { io.Copy(newFile, c) } } func main() { // 私いchunkを書き込んだfileを生成する WriteNewTextChunk() file, err := os.Open("./imgs/result.png") if err != nil { fmt.Printf("error! err: %v", err) os.Exit(-1) } chunks := readChunks(file) for _, c := range chunks { dumpChunk(c) } }
githubはこちら
TOPIC
- エンディアン変換
- binaryパッケージ
- Read
- Write
エンディアン変換について
エンディアン変換(以下 Byte Swapping)がgoには標準で装備されています。
CPUはデータの先頭には小さい桁から格納されるリトルエンディアンが採用されていますが、ネットワーク(TCP/IPプロトコル)ではデータの先頭に大きな桁から格納されるビックエンディアンが採用されています。
そのため、ビックエンディアンで格納されているネットワークで受け取ったデータをリトルエンディアンに修正する必要があります。
それが何度か出てくる以下の部分
binary.Read(r io.Reader, binary.BigEndian, data interface{})
TCP/IPプロトコルではデータは BigEndian
なので、rは BigEndian
で格納されているので、binary.Read
するときにorderに BigEndian
を指定することで、そのorderでio.Readerからデータを読み込み、dataの中に格納し直します。格納するときはリトルエンディアンに修正して格納されます。
binaryパッケージ
encoding/binary
についてのメモです。
binary.Read
encoding/binary パッケージ - golang.jp
Read関数
func Read(r io.Reader, order ByteOrder, data interface{}) os.Error Readは、構造化されたバイナリデータをrから読み込みます。データは固定サイズの値へのポインタ、または固定サイズの値のスライスでなければいけません。
バイナリかされたio.Reader型のデータから、任意のdata(interface型)へ変換する
binary.Write
encoding/binary パッケージ - golang.jp
func Write(w io.Writer, order ByteOrder, data interface{}) os.Error Writeは、wへdataのバイナリ表現を書き込みます。データは固定サイズの値、または固定サイズの値へのポインタでなければいけません。
data := "Hello World" var buf bytes.Buffer binary.Write(&buf, binary.BigEndian, int32(len(data))) // dataサイズの長さをbufに書き込む buf.Write([]byte(data)) // 確保したbufferにdataの内容を書き込む
binary表現の書き込みがどう行われているのか、知る機械になってよかった。こういったことはシステムプログラミングをしながらわかることなので、引き続き読み進めようと思います。