教程:用golang從零開始手寫一個bt下載客戶端(5)

背景

上篇文章我們介紹並實現了和peers的連接和握手,本文將介紹並實現和peer的通信,即消息的發送和接收。我們並不能直接請求peer發送分片(piece),因為對方有可能還沒有準備好,也有可能根本就沒有我們要 的分片,所以我們需要先詢問對方都有哪些分片,是不是已經準備好發送數據了。


教程:用golang從零開始手寫一個bt下載客戶端(5)

“我想要#1分片” “不,你啥也不想”


消息格式

一條消息包含了如下內容:長度,類型,載荷,連起來就像這樣:


教程:用golang從零開始手寫一個bt下載客戶端(5)

消息結構

消息開始於一個長度標誌位,用來告訴我們這個消息總共包含多少字節數據,這是一個32位整形,使用4個字節以大端序組合。緊接著一個字節表示消息的類型ID,告訴我們接收到的消息是什麼類型的,例如2表示“interested”。最後面填充的是一個可選的消息載荷。

讀取消息的時候,我們只需要根據消息的格式一步步來即可。首先讀取前4個字節,把它轉為大端序uint32整數得到消息長度;緊接著讀取後面第一個字節,得到消息ID,最後是載荷的所有字節。


代碼實現

<code>package message

import (
\t"encoding/binary"
\t"fmt"
\t"io"
)

type messageID uint8

const (
\tMsgChoke messageID = 0
\tMsgUnchoke messageID = 1
\tMsgInterested messageID = 2
\tMsgNotInterested messageID = 3
\tMsgHave messageID = 4
\tMsgBitfield messageID = 5
\tMsgRequest messageID = 6
\tMsgPiece messageID = 7

\tMsgCancel messageID = 8
)

type Message struct {
\tID messageID
\tPayload []byte
}

// 序列化一個消息
func (m *Message) Serialize() []byte {
\tif m == nil {
\t\treturn make([]byte, 4)
\t}
\tlength := uint32(len(m.Payload) + 1) // +1 for id
\tbuf := make([]byte, 4+length)
\tbinary.BigEndian.PutUint32(buf[0:4], length)
\tbuf[4] = byte(m.ID)
\tcopy(buf[5:], m.Payload)
\treturn buf
}

func Read(r io.Reader) (*Message, error) {
\tlengthBuf := make([]byte, 4)
\t_, err := io.ReadFull(r, lengthBuf)
\tif err != nil {
\t\treturn nil, err
\t}
\tlength := binary.BigEndian.Uint32(lengthBuf)
\tif length == 0 {
\t\treturn nil, nil
\t}
\tmessageBuf := make([]byte, length)
\t_, err = io.ReadFull(r, messageBuf)
\tif err != nil {
\t\treturn nil, err
\t}
\tm := Message{
\t\tID: messageID(messageBuf[0]),
\t\tPayload: messageBuf[1:],
\t}
\treturn &m, nil
}/<code>


好,我們現在已經瞭解了消息的格式,並能夠進行發送和接收,下篇我們將具體介紹幾種類型的消息和含義,並嘗試交換信息。敬請關注


分享到:


相關文章: