bt
github.com/xgfone/bt
go get github.com/xgfone/bt
bt

github.com/xgfone/bt

Another pure golang implementation of BitTorrent library.

by Aaron

v0.4.1 (see all)License:Apache-2.0
go get github.com/xgfone/bt
Readme

BT - Another Implementation For Golang Build Status GoDoc License

A pure golang implementation of BitTorrent library, which is inspired by dht and torrent.

Install

$ go get -u github.com/xgfone/bt

Features

  • Support Go1.9+.
  • Support IPv4/IPv6.
  • Multi-BEPs implementation.
  • Pure Go implementation without CGO.
  • Only library without any denpendencies. For the command tools, see bttools.

The Implemented Specifications

Example

See godoc or bttools.

Example 1: Download the file from the remote peer

package main

import (
    "context"
    "flag"
    "fmt"
    "io"
    "log"
    "net/url"
    "os"
    "time"

    "github.com/xgfone/bt/downloader"
    "github.com/xgfone/bt/metainfo"
    pp "github.com/xgfone/bt/peerprotocol"
    "github.com/xgfone/bt/tracker"
)

var peeraddr string

func init() {
    flag.StringVar(&peeraddr, "peeraddr", "", "The address of the peer storing the file.")
}

func getPeersFromTrackers(id, infohash metainfo.Hash, trackers []string) (peers []string) {
    c, cancel := context.WithTimeout(context.Background(), time.Second*3)
    defer cancel()

    resp := tracker.GetPeers(c, id, infohash, trackers)
    for r := range resp {
        for _, addr := range r.Resp.Addresses {
            addrs := addr.String()
            nonexist := true
            for _, peer := range peers {
                if peer == addrs {
                    nonexist = false
                    break
                }
            }

            if nonexist {
                peers = append(peers, addrs)
            }
        }
    }

    return
}

func main() {
    flag.Parse()

    torrentfile := os.Args[1]
    mi, err := metainfo.LoadFromFile(torrentfile)
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    id := metainfo.NewRandomHash()
    infohash := mi.InfoHash()
    info, err := mi.Info()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    var peers []string
    if peeraddr != "" {
        peers = []string{peeraddr}
    } else {
        // Get the peers from the trackers in the torrent file.
        trackers := mi.Announces().Unique()
        if len(trackers) == 0 {
            fmt.Println("no trackers")
            return
        }

        peers = getPeersFromTrackers(id, infohash, trackers)
        if len(peers) == 0 {
            fmt.Println("no peers")
            return
        }
    }

    // We save the downloaded file to the current directory.
    w := metainfo.NewWriter("", info, 0)
    defer w.Close()

    // We don't request the blocks from the remote peers concurrently,
    // and it is only an example. But you can do it concurrently.
    dm := newDownloadManager(w, info)
    for peerslen := len(peers); peerslen > 0 && !dm.IsFinished(); {
        peerslen--
        peer := peers[peerslen]
        peers = peers[:peerslen]
        downloadFileFromPeer(peer, id, infohash, dm)
    }
}

func downloadFileFromPeer(peer string, id, infohash metainfo.Hash, dm *downloadManager) {
    pc, err := pp.NewPeerConnByDial(peer, id, infohash, time.Second*3)
    if err != nil {
        log.Printf("fail to dial '%s'", peer)
        return
    }
    defer pc.Close()

    dm.doing = false
    pc.Timeout = time.Second * 10
    if err = pc.Handshake(); err != nil {
        log.Printf("fail to handshake with '%s': %s", peer, err)
        return
    }

    info := dm.writer.Info()
    bdh := downloader.NewBlockDownloadHandler(info, dm.OnBlock, dm.RequestBlock)
    if err = bdh.OnHandShake(pc); err != nil {
        log.Printf("handshake error with '%s': %s", peer, err)
        return
    }

    var msg pp.Message
    for !dm.IsFinished() {
        switch msg, err = pc.ReadMsg(); err {
        case nil:
            switch err = pc.HandleMessage(msg, bdh); err {
            case nil, pp.ErrChoked:
            default:
                log.Printf("fail to handle the msg from '%s': %s", peer, err)
                return
            }
        case io.EOF:
            log.Printf("got EOF from '%s'", peer)
            return
        default:
            log.Printf("fail to read the msg from '%s': %s", peer, err)
            return
        }
    }
}

func newDownloadManager(w metainfo.Writer, info metainfo.Info) *downloadManager {
    length := info.Piece(0).Length()
    return &downloadManager{writer: w, plength: length}
}

type downloadManager struct {
    writer  metainfo.Writer
    pindex  uint32
    poffset uint32
    plength int64
    doing   bool
}

func (dm *downloadManager) IsFinished() bool {
    if dm.pindex >= uint32(dm.writer.Info().CountPieces()) {
        return true
    }
    return false
}

func (dm *downloadManager) OnBlock(index, offset uint32, b []byte) (err error) {
    if dm.pindex != index {
        return fmt.Errorf("inconsistent piece: old=%d, new=%d", dm.pindex, index)
    } else if dm.poffset != offset {
        return fmt.Errorf("inconsistent offset for piece '%d': old=%d, new=%d",
            index, dm.poffset, offset)
    }

    dm.doing = false
    n, err := dm.writer.WriteBlock(index, offset, b)
    if err == nil {
        dm.poffset = offset + uint32(n)
        dm.plength -= int64(n)
    }
    return
}

func (dm *downloadManager) RequestBlock(pc *pp.PeerConn) (err error) {
    if dm.doing {
        return
    }

    if dm.plength <= 0 {
        dm.pindex++
        if dm.IsFinished() {
            return
        }

        dm.poffset = 0
        dm.plength = dm.writer.Info().Piece(int(dm.pindex)).Length()
    }

    index := dm.pindex
    begin := dm.poffset
    length := uint32(downloader.BlockSize)
    if length > uint32(dm.plength) {
        length = uint32(dm.plength)
    }

    log.Printf("Request Block from '%s': index=%d, offset=%d, length=%d",
        pc.RemoteAddr().String(), index, begin, length)
    if err = pc.SendRequest(index, begin, length); err == nil {
        dm.doing = true
    }
    return
}

GitHub Stars

12

LAST COMMIT

9mos ago

MAINTAINERS

0

CONTRIBUTORS

1

OPEN ISSUES

0

OPEN PRs

0
VersionTagPublished
v0.4.1
1yr ago
v0.4.1-0.20210208083425-55d5ad3494d2
1yr ago
v0.4.0
1yr ago
v0.3.0
1yr ago
No alternatives found
No tutorials found
Add a tutorial