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

背景

上篇文章我们介绍了消息的格式,并实现了消息的序列化和读取,这篇文章将详细介绍消息的类型和含义,并且通多代码实现消息的发送和接收。

消息解释

keep-alive:

keep-alive消息是一个长度为0的消息,它没有message ID也没有载荷。在一定周期内,peers如果没有收到消息,有可能关闭连接,因此需要发送一个keep-alive消息维护长时间(通常为2分钟)连接。

choke:

choke消息长度为1,没有载荷,表示阻塞

unchoke:

unchoke消息长度为1,没有载荷,表示解除阻塞

interested:

interested消息长度为1,没有载荷,表示感兴趣

not interested:

not interested消息长度为1,没有载荷,表示不感兴趣

have: <piece>

have消息长度为5,载荷是一个分片的从零开始的已经下载的索引值

bitfield: <bitfield>

bitfield消息只有在握手完成后立即发送,并且要在其他消息前发送。他的长度是可变的,载荷是一个位字段,表示已经成功下载了的分片,每个分片对应一位,高位代表0分片的索引,其中0表示还没有这个分片,1表示有这个分片。


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

bitfield


request: <index><begin><length>/<begin>/<index>

request消息长度为13,用来请求一个块,载荷包含如下信息:

  • index: integer 从0开始的分片索引
  • begin: integer 从0开始的分片内的偏移
  • length: integer 请求的长度(有争议,因为没有规定多长合适,太小了请求数量大,太长了容易断)

piece: <index><begin><block>/<begin>/<index>

piece消息是长度可变的,x是块的长度,载荷包含如下信息:

  • index: integer 从0开始的分片索引
  • begin: integer 从0开始的分片内的偏移
  • block: block of data 分片的子集
  • 代码实现

    <code>package client

    import (
    \t"bytes"
    \t"fmt"
    \t"net"
    \t"p2p/bitfield"
    \t"p2p/handshake"
    \t"p2p/message"
    \t"p2p/peers"
    \t"time"
    )

    type Client struct {
    \tConn net.Conn
    \tChoked bool
    \tBitfield bitfield.Bitfield
    \tpeer peers.Peer
    \tinfoHash [20]byte
    \tpeerID [20]byte
    }

    func completeHandshake(conn net.Conn, infohash, peerID [20]byte) (*handshake.Handshake, error) {
    \tconn.SetDeadline(time.Now().Add(3 * time.Second))
    \tdefer conn.SetDeadline(time.Time{})
    \treq := handshake.New(infohash, peerID)
    \t_, err := conn.Write(req.Serialize())
    \tif err != nil {
    \t\treturn nil, err
    \t}
    \tres, err := handshake.Read(conn)
    \tif err != nil {
    \t\treturn nil, err
    \t}

    \tif !bytes.Equal(res.InfoHash[:], infohash[:]) {
    \t\treturn nil, fmt.Errorf("Expected infohash %x got %x", infohash, res.InfoHash)
    \t}
    \treturn res, nil
    }

    func recvBitfield(conn net.Conn) (bitfield.Bitfield, error) {
    \tconn.SetDeadline(time.Now().Add(5 * time.Second))
    \tdefer conn.SetDeadline(time.Time{})
    \tmsg, err := message.Read(conn)
    \tif err != nil {
    \t\treturn nil, err
    \t}
    \tif msg.ID != message.MsgBitfield {
    \t\terr := fmt.Errorf("Excepted bitfield but got %d", msg.ID)
    \t\treturn nil, err
    \t}
    \treturn msg.Payload, nil
    }

    func New(peer peers.Peer, peerID [20]byte, infohash [20]byte) (*Client, error) {
    \tconn, err := net.DialTimeout("tcp", peer.String(), 3*time.Second)
    \tif err != nil {
    \t\treturn nil, err
    \t}
    \t_, err = completeHandshake(conn, infohash, peerID)
    \tif err != nil {
    \t\tconn.Close()
    \t\treturn nil, err
    \t}
    \tbf, err := recvBitfield(conn)
    \tif err != nil {
    \t\tconn.Close()
    \t\treturn nil, err
    \t}
    \tclient := &Client{
    \t\tConn: conn,
    \t\tChoked: true,
    \t\tBitfield: bf,
    \t\tpeer: peer,
    \t\tinfoHash: infohash,
    \t\tpeerID: peerID,
    \t}
    \treturn client, nil
    }

    func (c *Client) SendUnchoke() error {
    \tmsg := message.Message{ID: message.MsgUnchoke}
    \t_, err := c.Conn.Write(msg.Serialize())
    \treturn err

    }

    func (c *Client) SendInterested() error {
    \tmsg := message.Message{ID: message.MsgInterested}
    \t_, err := c.Conn.Write(msg.Serialize())
    \treturn err
    }

    func (c *Client) SendHave(index int) error {
    \tmsg := message.FormatHave(index)
    \t_, err := c.Conn.Write(msg.Serialize())
    \treturn err
    }

    func (c *Client) SendRequest(index int, begin int, length int) error {
    \treq := message.FormatRequest(index, begin, length)
    \t_, err := c.Conn.Write(req.Serialize())
    \treturn err
    }

    func (c *Client) Read() (*message.Message, error) {
    \tmsg, err := message.Read(c.Conn)
    \treturn msg, err
    }
    /<code>

    整个交换信息的流程是:

    1. 建立tcp连接->拨号
    2. 发起握手->“你好”“你好”
    3. 发送bitfield请求->“你都有哪些分片”“00100100”
    4. 发送unchoke请求->“请问你准备好了吗?”“没有阻塞,可以”
    5. 发送interested请求->“我感兴趣”
    6. 发起request请求->“请给我第n片数据”“给你第n片数据”
    7. 校验piece->“这个分片有没有被篡改?”
    8. 发起have请求->“我已经拥有这个分片了”
    9. 更新已下载信息->“增加已下载的数量,更新自己的bitfield”

    好,到目前为止,我们已经可以和peers通信了,下篇我们将介绍比较有趣的并发和状态管理。敬请关注!


    分享到:


    相關文章: