refactor: piece download logic

This commit is contained in:
liwei committed 2020-03-03 10:08:39 +08:00
1 parent 753830accf
commit b823dfbb36
5 files changed
+119 -142

No files matched your search

-10
View File
@@ -3,7 +3,6 @@ package metainfo
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"github.com/RoaringBitmap/roaring"
"testing" "testing"
) )
@@ -18,12 +17,3 @@ func TestParseFromFile(t *testing.T) {
fmt.Printf("%+v\n", metaInfo.Info.PieceLength) fmt.Printf("%+v\n", metaInfo.Info.PieceLength)
fmt.Printf("%+v\n", hex.EncodeToString(metaInfo.infoHash[:])) fmt.Printf("%+v\n", hex.EncodeToString(metaInfo.infoHash[:]))
} }
func TestParseFromFile2(t *testing.T) {
bitmap := roaring.NewBitmap()
bitmap.AddMany([]uint32{1, 2, 3, 4, 5, 6, 7, 10, 20, 9, 9, 1})
fmt.Println(bitmap.Contains(1))
fmt.Println(bitmap.Contains(2))
fmt.Println(bitmap.Contains(8))
fmt.Println(bitmap.GetCardinality())
}
+18 -23
View File
@@ -7,7 +7,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"net" "net"
"os" "os"
"path" "path"
@@ -49,9 +48,6 @@ type peerConn struct {
downloadedCh chan error downloadedCh chan error
disconnectCh chan error disconnectCh chan error
keepaliveCh chan error
// block个数
blockCount int
// block下载队列,官方推荐为5 // block下载队列,官方推荐为5
blockQueueCh chan interface{} blockQueueCh chan interface{}
} }
@@ -134,8 +130,6 @@ func (pc *peerConn) handleKeepalive() {
break break
} }
} }
pc.keepaliveCh <- fmt.Errorf("keepalive fail")
close(pc.keepaliveCh)
} }
// 准备下载 // 准备下载
@@ -149,7 +143,6 @@ func (pc *peerConn) ready() error {
pc.readyEnd = false pc.readyEnd = false
readyCh := make(chan bool) readyCh := make(chan bool)
pc.disconnectCh = make(chan error) pc.disconnectCh = make(chan error)
pc.keepaliveCh = make(chan error)
go pc.handleKeepalive() go pc.handleKeepalive()
go func() { go func() {
scanner := bufio.NewScanner(pc.conn) scanner := bufio.NewScanner(pc.conn)
@@ -217,20 +210,20 @@ func (pc *peerConn) ready() error {
// 下载指定piece // 下载指定piece
func (pc *peerConn) downloadPiece(index int) (err error) { func (pc *peerConn) downloadPiece(index int) (err error) {
pieceLength := pc.torrent.MetaInfo.GetPieceLength(index) pieceLength := pc.torrent.MetaInfo.GetPieceLength(index)
pc.blockCount = int(math.Ceil(float64(pieceLength) / blockSize))
pc.downloadedCh = make(chan error) pc.downloadedCh = make(chan error)
pc.blockQueueCh = make(chan interface{}, 5) pc.blockQueueCh = make(chan interface{}, 5)
defer close(pc.blockQueueCh) defer close(pc.blockQueueCh)
// 按块下载分片 // 按块下载分片
for i := 0; i < pc.blockCount; i++ { blockCount := pc.torrent.pieceStates.states[index].blockCount
for i := 0; i < blockCount; i++ {
offset := blockSize * i offset := blockSize * i
// 如果已经下载过就跳过 // 如果已经下载过就跳过
if pc.torrent.pieces.isBlockDownloaded(index, offset) { if pc.torrent.pieceStates.isBlockDone(index, offset) {
continue continue
} }
var blockLength uint32 var blockLength uint32
if i == pc.blockCount-1 { if i == blockCount-1 {
// 最后一个block大小需要计算出来 // 最后一个block大小需要计算出来
blockLength = uint32(pieceLength - offset) blockLength = uint32(pieceLength - offset)
} else { } else {
@@ -244,8 +237,6 @@ func (pc *peerConn) downloadPiece(index int) (err error) {
break break
case err = <-pc.disconnectCh: case err = <-pc.disconnectCh:
break break
case err = <-pc.keepaliveCh:
break
} }
if err != nil { if err != nil {
pc.conn.Close() pc.conn.Close()
@@ -262,8 +253,6 @@ func (pc *peerConn) downloadPiece(index int) (err error) {
break break
case err = <-pc.disconnectCh: case err = <-pc.disconnectCh:
break break
case err = <-pc.keepaliveCh:
break
} }
if err != nil { if err != nil {
pc.conn.Close() pc.conn.Close()
@@ -355,27 +344,33 @@ func (pc *peerConn) handlePiece(buf []byte) {
} }
// 开始写入文件 // 开始写入文件
for _, f := range fileBlocks { for _, f := range fileBlocks {
func() { err := func() error {
name := filepath.Join(pc.torrent.Path, f.filepath) name := filepath.Join(pc.torrent.Path, f.filepath)
file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0644) file, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0644)
if err != nil { if err != nil {
panic(err) return err
} }
defer file.Close() defer file.Close()
_, err = file.WriteAt(piece.Block[f.blockBegin:f.blockEnd], f.fileSeek) _, err = file.WriteAt(piece.Block[f.blockBegin:f.blockEnd], f.fileSeek)
if err != nil { if err != nil {
panic(err) return err
} }
return nil
}() }()
if err != nil {
pc.downloadedCh <- errPieceCheckFailed
pc.conn.Close()
return
}
} }
pc.torrent.pieces.setBlockDownloaded(int(piece.Index), int(piece.Begin)) pc.torrent.pieceStates.setBlockDone(int(piece.Index), int(piece.Begin))
// 出队 // 出队
<-pc.blockQueueCh <-pc.blockQueueCh
log.Debugf("piece:%d block:%d already:%d", piece.Index, piece.Begin, pc.torrent.pieces.blockSize(int(piece.Index))) log.Debugf("piece:%d block:%d", piece.Index, piece.Begin)
// piece全部下载完 // piece全部下载完
if pc.torrent.pieces.blockSize(int(piece.Index)) == pc.blockCount { if pc.torrent.pieceStates.isPieceDone(int(piece.Index)) {
// 计算piece对应的文件偏移 // 计算piece对应的文件偏移
sha1 := sha1.New() sha1 := sha1.New()
pieceLength := pc.torrent.MetaInfo.GetPieceLength(int(piece.Index)) pieceLength := pc.torrent.MetaInfo.GetPieceLength(int(piece.Index))
@@ -442,9 +437,9 @@ type fileBlock struct {
// 获取peer能提供需要下载的文件分片 // 获取peer能提供需要下载的文件分片
func (pc *peerConn) getHavePieces(bitfield *message.Bitfield) []int { func (pc *peerConn) getHavePieces(bitfield *message.Bitfield) []int {
states := make([]bool, pc.torrent.pieces.size()) states := make([]bool, pc.torrent.pieceStates.size())
for i := range states { for i := range states {
states[i] = pc.torrent.pieces.getState(i) == stateFinished states[i] = pc.torrent.pieceStates.getState(i) == stateFinish
} }
return bitfield.Have(states) return bitfield.Have(states)
} }
+56 -57
View File
@@ -1,6 +1,8 @@
package torrent package torrent
import ( import (
"github.com/monkeyWie/gopeed/protocol/bt/metainfo"
"math"
"sync" "sync"
"github.com/RoaringBitmap/roaring" "github.com/RoaringBitmap/roaring"
@@ -11,100 +13,97 @@ type state int
const ( const (
stateReady = iota stateReady = iota
stateDownloading stateDownloading
stateFinished stateFinish
) )
// 所有piece的下载状态 // 所有piece的下载状态
type pieces struct { type pieceStates struct {
rw *sync.RWMutex lock *sync.RWMutex
arr []piece // 所有piece的block下载进度
states map[int]*pieceState
} }
type piece struct { type pieceState struct {
// piece的下载状态
state state state state
// piece中block的下载状态 // piece中block的个数
blocks *roaring.Bitmap blockCount int
// piece中block的下载记录
blockRecord *roaring.Bitmap
} }
func newPiecesState(size int) *pieces { func newPiecesState(meta *metainfo.MetaInfo) *pieceStates {
ps := &pieces{ pieceCount := len(meta.Info.Pieces)
rw: &sync.RWMutex{}, ps := &pieceStates{
arr: make([]piece, size), lock: &sync.RWMutex{},
states: make(map[int]*pieceState, pieceCount),
} }
for i := 0; i < size; i++ { for i := 0; i < pieceCount; i++ {
ps.arr[i].state = stateReady ps.states[i] = &pieceState{
ps.arr[i].blocks = roaring.NewBitmap() state: stateReady,
blockCount: int(math.Ceil(float64(meta.GetPieceLength(i)) / blockSize)),
blockRecord: roaring.New(),
}
} }
return ps return ps
} }
func (ps *pieces) getState(index int) state { func (ps *pieceStates) getState(index int) state {
ps.rw.RLock() ps.lock.RLock()
defer ps.rw.RUnlock() defer ps.lock.RUnlock()
return ps.arr[index].state return ps.states[index].state
} }
func (ps *pieces) setState(index int, state state) { func (ps *pieceStates) setState(index int, state state) {
ps.rw.Lock() ps.lock.Lock()
defer ps.rw.Unlock() defer ps.lock.Unlock()
ps.arr[index].state = state ps.states[index].state = state
} }
func (ps *pieces) getReady() int { func (ps *pieceStates) size() int {
ps.rw.Lock() return len(ps.states)
defer ps.rw.Unlock()
for i, s := range ps.arr {
if s.state == stateReady {
ps.arr[i].state = stateDownloading
return i
}
}
return -1
} }
func (ps *pieces) getLeft() int { func (ps *pieceStates) getLeft() int {
ps.rw.RLock() ps.lock.RLock()
defer ps.rw.RUnlock() defer ps.lock.RUnlock()
count := 0 count := 0
for _, s := range ps.arr { for _, v := range ps.states {
if s.state != stateFinished { if v.state == stateReady || v.state == stateDownloading {
count++ count++
} }
} }
return count return count
} }
func (ps *pieces) size() int { func (ps *pieceStates) isDone() bool {
return len(ps.arr) ps.lock.RLock()
} defer ps.lock.RUnlock()
for _, v := range ps.states {
func (ps *pieces) isDone() bool { if v.state == stateReady || v.state == stateDownloading {
ps.rw.RLock()
defer ps.rw.RUnlock()
for _, s := range ps.arr {
if s.state != stateFinished {
return false return false
} }
} }
return true return true
} }
func (ps *pieceStates) isPieceDone(index int) bool {
ps.lock.RLock()
defer ps.lock.RUnlock()
state := ps.states[index]
return uint64(state.blockCount) == state.blockRecord.GetCardinality()
}
// 指定piece下的某个block是否下载完成 // 指定piece下的某个block是否下载完成
func (ps *pieces) isBlockDownloaded(pieceIndex int, blockIndex int) bool { func (ps *pieceStates) isBlockDone(pieceIndex int, blockIndex int) bool {
return ps.arr[pieceIndex].blocks.ContainsInt(blockIndex) return ps.states[pieceIndex].blockRecord.ContainsInt(blockIndex)
} }
// 指定piece下的某个block下载完成 // 指定piece下的某个block下载完成
func (ps *pieces) setBlockDownloaded(pieceIndex int, blockIndex int) { func (ps *pieceStates) setBlockDone(pieceIndex int, blockIndex int) {
ps.arr[pieceIndex].blocks.AddInt(blockIndex) ps.states[pieceIndex].blockRecord.AddInt(blockIndex)
} }
// 清空指定piece下所有的block下载记录 // 清空指定piece下所有的block下载记录
func (ps *pieces) clearBlocks(index int) { func (ps *pieceStates) clearBlocks(index int) {
ps.arr[index].blocks.Clear() ps.states[index].blockRecord.Clear()
}
func (ps *pieces) blockSize(index int) int {
return int(ps.arr[index].blocks.GetCardinality())
} }
+44 -50
View File
@@ -12,18 +12,16 @@ type Torrent struct {
PeerID [20]byte PeerID [20]byte
MetaInfo *metainfo.MetaInfo MetaInfo *metainfo.MetaInfo
pieces *pieces pieceStates *pieceStates
peerPool *peerPool peerPool *peerPool
completeCh chan interface{} Path string
Path string
} }
func NewTorrent(peerID [20]byte, metaInfo *metainfo.MetaInfo) *Torrent { func NewTorrent(peerID [20]byte, metaInfo *metainfo.MetaInfo) *Torrent {
torrent := &Torrent{ torrent := &Torrent{
PeerID: peerID, PeerID: peerID,
MetaInfo: metaInfo, MetaInfo: metaInfo,
pieces: newPiecesState(len(metaInfo.Info.Pieces)), pieceStates: newPiecesState(metaInfo),
} }
return torrent return torrent
} }
@@ -33,57 +31,53 @@ func (t *Torrent) Download(path string) {
t.peerPool = newPeerPool(t) t.peerPool = newPeerPool(t)
t.peerPool.fetch() t.peerPool.fetch()
pieceQueueCh := make(chan interface{}, 256) pieceQueueCh := make(chan interface{}, 128)
defer close(pieceQueueCh) defer close(pieceQueueCh)
t.completeCh = make(chan interface{}) downloadedCh := make(chan interface{})
defer close(t.completeCh) defer close(downloadedCh)
for {
for i := 0; i < len(t.MetaInfo.Info.Pieces); i++ {
// 加入下载队列
pieceQueueCh <- nil pieceQueueCh <- nil
// 获取一个待下载的piece
index := t.pieces.getReady()
// 没有待下载的piece了
if index == -1 {
log.Debugf("no piece to download")
<-t.completeCh
// 下载完成
break
}
go func(index int) { go func(index int) {
peer := t.peerPool.get() t.pieceStates.setState(index, stateDownloading)
if peer == nil { // 一直下载到成功
time.Sleep(time.Second * 10) for {
t.pieces.setState(index, stateReady) peer := t.peerPool.get()
<-pieceQueueCh if peer == nil {
return time.Sleep(time.Second * 10)
} continue
pc := newPeerConn(t, peer) }
err := pc.ready() pc := newPeerConn(t, peer)
if err != nil { err := pc.ready()
t.pieces.setState(index, stateReady)
t.peerPool.unavailable(peer)
} else {
err = pc.downloadPiece(index)
if err != nil { if err != nil {
log.Debugf("piece %d download fail:%s", index, err.Error())
// 下载失败
t.pieces.setState(index, stateReady)
t.peerPool.unavailable(peer) t.peerPool.unavailable(peer)
if errors.Is(err, errPieceCheckFailed) {
// piece校验失败,需要重新下载过一遍,清空block记录
t.pieces.clearBlocks(index)
}
} else { } else {
// 下载完成 err = pc.downloadPiece(index)
t.pieces.setState(index, stateFinished) if err != nil {
t.peerPool.release(peer) log.Debugf("piece %d download fail:%s", index, err.Error())
log.Debugf("piece %d download success,total:%d,left:%d", index, t.pieces.size(), t.pieces.getLeft()) // 下载失败
// 检查是否所有piece下载完成 t.peerPool.unavailable(peer)
if t.pieces.isDone() { if errors.Is(err, errPieceCheckFailed) {
t.completeCh <- nil // piece校验失败,需要重新下载过一遍,清空block记录
t.pieceStates.clearBlocks(index)
}
continue
} else {
// 下载完成
t.pieceStates.setState(index, stateFinish)
t.peerPool.release(peer)
log.Debugf("piece %d download success,total:%d,left:%d", index, t.pieceStates.size(), t.pieceStates.getLeft())
if t.pieceStates.isDone() {
downloadedCh <- nil
}
break
} }
} }
} }
<-pieceQueueCh <-pieceQueueCh
}(index) }(i)
} }
<-downloadedCh
log.Debug("download end")
} }
+1 -2
View File
@@ -1,11 +1,10 @@
package torrent package torrent
import ( import (
"testing"
"github.com/monkeyWie/gopeed/protocol/bt/metainfo" "github.com/monkeyWie/gopeed/protocol/bt/metainfo"
"github.com/monkeyWie/gopeed/protocol/bt/peer" "github.com/monkeyWie/gopeed/protocol/bt/peer"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"testing"
) )
func init() { func init() {