教程:用golang从零开始手写一个bt下载客户端(5)

背景

上篇文章我们介绍并实现了和peers的连接和握手,本文将介绍并实现和peer的通信,即消息的发送和接收。我们并不能直接请求peer发送分片(piece),因为对方有可能还没有准备好,也有可能根本就没有我们要 的分片,所以我们需要先询问对方都有哪些分片,是不是已经准备好发送数据了。


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


消息格式

一条消息包含了如下内容:长度,类型,载荷,连起来就像这样:


消息结构

消息开始于一个长度标志位,用来告诉我们这个消息总共包含多少字节数据,这是一个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>


好,我们现在已经了解了消息的格式,并能够进行发送和接收,下篇我们将具体介绍几种类型的消息和含义,并尝试交换信息。敬请关注