背景
上篇文章我们介绍了消息的格式,并实现了消息的序列化和读取,这篇文章将详细介绍消息的类型和含义,并且通多代码实现消息的发送和接收。
消息解释
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:
have消息长度为5,载荷是一个分片的从零开始的已经下载的索引值
bitfield:
bitfield消息只有在握手完成后立即发送,并且要在其他消息前发送。他的长度是可变的,载荷是一个位字段,表示已经成功下载了的分片,每个分片对应一位,高位代表0分片的索引,其中0表示还没有这个分片,1表示有这个分片。
request:
request消息长度为13,用来请求一个块,载荷包含如下信息:
- index: integer 从0开始的分片索引
- begin: integer 从0开始的分片内的偏移
- length: integer 请求的长度(有争议,因为没有规定多长合适,太小了请求数量大,太长了容易断)
piece:
piece消息是长度可变的,x是块的长度,载荷包含如下信息:
代码实现
<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>
整个交换信息的流程是:
- 建立tcp连接->拨号
- 发起握手->“你好”“你好”
- 发送bitfield请求->“你都有哪些分片”“00100100”
- 发送unchoke请求->“请问你准备好了吗?”“没有阻塞,可以”
- 发送interested请求->“我感兴趣”
- 发起request请求->“请给我第n片数据”“给你第n片数据”
- 校验piece->“这个分片有没有被篡改?”
- 发起have请求->“我已经拥有这个分片了”
- 更新已下载信息->“增加已下载的数量,更新自己的bitfield”
好,到目前为止,我们已经可以和peers通信了,下篇我们将介绍比较有趣的并发和状态管理。敬请关注!
閱讀更多 愛碼士 的文章