Skip to content

Commit

Permalink
引入rain torrent库解决性能问题 #3
Browse files Browse the repository at this point in the history
  • Loading branch information
ninehills committed Jan 28, 2022
1 parent 84355e8 commit cca52c4
Show file tree
Hide file tree
Showing 20 changed files with 1,429 additions and 1,223 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@
*.torrent.db*

dist/

.DS_Store
48 changes: 23 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# p2pfile - DHT-based P2P file distribution command line tools
# p2pfile - Simple P2P file distribution CLI

## 背景

Expand All @@ -10,7 +10,8 @@

### 设计限制

- 不支持 Tracker,只支持 DHT 网络,从而简化设计。
- DHT 网络中在这种环境下意义不大,所以不使用 DHT 网络,而是使用自带的集中 Tracker
- 在第一个测试版本使用纯 DHT 网络,发现其交换效率低于 Tracker.
- 不需要 Daemon 常驻进程,只需要单个二进制文件。
- 无加密设计
- 只支持单个文件分发,不支持文件夹分发。
Expand All @@ -24,7 +25,7 @@
## 命令行设计

```txt
DHT-based P2P file distribution command line tools. For example:
Simple P2P file distribution CLI. For example:
p2pfile serve <FILE_PATH1> <FILE_PATH2> ...
p2pfile download <MAGNET_URI> <MAGNET_URI2> ...
Expand Down Expand Up @@ -56,38 +57,35 @@ Use "p2pfile [command] --help" for more information about a command.

A. Magnet URI schema(使用了[BEP0009](http://www.bittorrent.org/beps/bep_0009.html) 扩展)

`magnet:?xt=urn:btih:<info-hash>&dn=<name>&x.pe=<peer-address>`
`magnet:?xt=urn:btih:<info-hash>&dn=<name>&tr=<tracker>&x.pe=<peer-address>`

- `info-hash`: 哈希值,用于标识文件
- `name`: 文件名[可选]
- `peer-address`: 做种机器的Peer地址,用于初始化 DHT 网络
- `name`: 文件名 [可选]
- `peer-address`: 做种机器的 Peer 地址,用于初始化 DHT 网络
- `tracker`: tracker 地址

一台机器并发做种或者下载
B. Tracker 高可用性

- 由于无后台进程,每个进程都是单独的客户端,均需要占用一个端口。
- 故端口需要支持随机分配,从而避免端口冲突。
- 随机分配方法:在端口段中随机选择端口,如果端口被占用,则重新随机选择。
- 随机分配存在当做种进程重启后,其新的端口号和之前不同。
- 方法1:将端口号持久化,当重新serve的时候,使用之前的端口号
- 方法2:重新做种的时候,需要传入magnet uri,从中解析出端口号。
- 考虑到分发文件只是一次性命令,暂时不考虑

B. 做种高可用性
C. 下载后持续做种

- 可以使用2+台机器同时做种,做种时增加参数:`--peers=<peer1>,<peer2>` 从而保证Magnet URI的高可用性。
- 考虑到一次性命令,故不考虑

C. 下载后持续做种
D. 任务中断恢复

- 增加参数 `--seeding`,指定下载之后持续做种。持续做种结束条件有多个,任意条件满足即停止做种。
- `--seeding-time=<time>`,指定持续做种的时间,单位为秒。默认为 60s。
- `--seeding-ratio=<ratio>`,指定持续做种的种子分享率,单位为百分比。默认为 1.0。
- 该参数推荐在大文件分发时使用,小文件分发没必要且不建议使用。
- 考虑到一次性命令,故不考虑,下载失败后需要重新下载

D. 信息存储:
## 后续计划

- 默认为 SQlite,会在当前目录下生成 `.*db.*` 文件。
- (后续支持) 支持更换存储后端,比如 bolt/sqlite/file etc.
1. resume download
2. tracker ha
3. multi file
4. download and upload speed limit
5. support set download directory

E. 库:
## 参考资料

- <https://github.com/anacrolix/torrent>: 主要使用.
- <https://github.com/anacrolix/torrent>: 第一版参考,因为下载速度较慢,放弃
- <https://github.com/cenkalti/rain>: 主要引用
- <https://gitlab.com/axet/libtorrent>: 后续参考实现 14: Local Peers Discovery / 19: WebSeeds.
Binary file added README.md.resume
Binary file not shown.
1 change: 1 addition & 0 deletions README.md.torrent
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d8:announce36:http://172.20.64.10:42070/1/announce13:creation datei1643352047e4:infod6:lengthi2963e4:name9:README.md12:piece lengthi32768e6:pieces20:�_m�fo�3v�{g���L�7:privatei0eee
Expand Down
38 changes: 3 additions & 35 deletions cmd/download.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package cmd

import (
"context"
"os"
"os/signal"

"github.com/spf13/cobra"
"github.com/spf13/viper"

Expand All @@ -19,45 +15,17 @@ func newDownloadCmd() *cobra.Command {
Short: "Download file from magnet uri.",
Long: `Download file from magnet uri. Usage:
p2pfile download <MAGNET_URI> <MAGNET_URI2> ...`,
p2pfile download <MAGNET_URI>`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// TODO: 写入这里才生效,需要改进
initLogger(viper.GetBool("debug"))
// TODO: support set download directory
downloader, err := libtorrent.NewTorrentServer(
"download", viper.GetString("ip"), viper.GetInt("port"), viper.GetString("port-range"),
viper.GetStringSlice("peers"), viper.GetFloat64("upload-limit"),
viper.GetFloat64("download-limit"), viper.GetBool("debug"),
args, []string{},
)
if err != nil {
log.Fatal("Failed to create downloader: ", err)
}
ctx := context.Background()

// trap Ctrl+C and call cancel on the context
ctx, cancel := context.WithCancel(ctx)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
defer func() {
signal.Stop(c)
cancel()
}()
go func() {
select {
case sig := <-c:
log.Errorf("Caught signal %v, shutting down...\n", sig)
cancel()
case <-ctx.Done():
}
}()
err = downloader.RunDownloader(ctx)
err := libtorrent.RunTorrentServer(args[0], viper.GetString("dir"), false, false)
if err != nil {
log.Fatal("Failed to run downloader: ", err)
log.Fatal("Failed to run torrent server: ", err)
}
},
}
downloadCmd.MarkFlagRequired("magnet")
return downloadCmd
}
28 changes: 12 additions & 16 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ var cfgFile string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "p2pfile",
Short: "DHT-based P2P file distribution command line tools",
Long: `DHT-based P2P file distribution command line tools. For example:
Short: "Simple P2P file distribution CLI",
Long: `Simple P2P file distribution CLI. For example:
p2pfile serve <FILE_PATH1> <FILE_PATH2> ...
p2pfile download <MAGNET_URI> <MAGNET_URI2> ...`,
p2pfile serve <FILE_PATH>
p2pfile download <MAGNET_URI>`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
Expand All @@ -40,20 +40,16 @@ func init() {
rootCmd.PersistentFlags().SortFlags = false

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.p2pfile.yaml)")
rootCmd.PersistentFlags().String("ip", "", "Set ip. (default: default route ip)")
rootCmd.PersistentFlags().Int("port", 0, "Set port. (default: random port in port-range, See --port-range)")
rootCmd.PersistentFlags().String("port-range", "42070-42099", "Set random port range. (default: 42070-42099)")
rootCmd.PersistentFlags().StringSlice("peers", []string{}, "Set bootstrap peers. (default: empty) (eg: --peers 10.1.1.1:2233,10.2.2.2:4567")
rootCmd.PersistentFlags().Float64("upload-limit", 0.0, "Set upload limit, MiB. (default: 0.0)")
rootCmd.PersistentFlags().Float64("download-limit", 0.0, "Set download limit, MiB. (default: 0.0)")
rootCmd.PersistentFlags().String("tracker-ip", "", "Set tracker ip. (default: default route ip)")
rootCmd.PersistentFlags().Int("tracker-port", 0, "Set tracker port. (default: random port in port-range, See --port-range)")
rootCmd.PersistentFlags().String("tracker-port-range", "42070-42099", "Set tracker random port range. (default: 42070-42099)")
rootCmd.PersistentFlags().String("dir", "", "Set download dir. (default: .)")
rootCmd.PersistentFlags().Bool("debug", false, "Debug mode.")

viper.BindPFlag("ip", rootCmd.PersistentFlags().Lookup("ip"))
viper.BindPFlag("port", rootCmd.PersistentFlags().Lookup("port"))
viper.BindPFlag("port-range", rootCmd.PersistentFlags().Lookup("port-range"))
viper.BindPFlag("peers", rootCmd.PersistentFlags().Lookup("peers"))
viper.BindPFlag("upload-limit", rootCmd.PersistentFlags().Lookup("upload-limit"))
viper.BindPFlag("download-limit", rootCmd.PersistentFlags().Lookup("download-limit"))
viper.BindPFlag("tracker-ip", rootCmd.PersistentFlags().Lookup("tracker-ip"))
viper.BindPFlag("tracker-port", rootCmd.PersistentFlags().Lookup("tracker-port"))
viper.BindPFlag("tracker-port-range", rootCmd.PersistentFlags().Lookup("tracker-port-range"))
viper.BindPFlag("dir", rootCmd.PersistentFlags().Lookup("dir"))
viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))

rootCmd.AddCommand(newServeCmd())
Expand Down
66 changes: 34 additions & 32 deletions cmd/serve.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
package cmd

import (
"context"
"os"
"os/signal"
"fmt"

"github.com/ninehills/p2pfile/pkg/libtorrent"
"github.com/ninehills/p2pfile/pkg/libtracker"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -15,47 +14,50 @@ func newServeCmd() *cobra.Command {
var serveCmd = &cobra.Command{
Use: "serve",
Short: "creates and seeds a torrent from filepaths.",
Long: `DHT-based P2P file distribution command line tools. Usage:
Long: `Simple P2P file distribution CLI. Usage:
p2pfile serve <FILE_PATH1> <FILE_PATH2> ...`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
// TODO: 写入这里才生效,需要改进
initLogger(viper.GetBool("debug"))
server, err := libtorrent.NewTorrentServer(
"serve", viper.GetString("ip"), viper.GetInt("port"), viper.GetString("port-range"),
viper.GetStringSlice("peers"), viper.GetFloat64("upload-limit"),
viper.GetFloat64("download-limit"), viper.GetBool("debug"),
[]string{}, args,
)
file := args[0]

// 1. Start tracker
trackerIP, err := libtorrent.GetPublicIP(viper.GetString("tracker-ip"))
if err != nil {
log.Fatal("Failed to create server: ", err)
log.Fatal("Failed to get public ip", err)
}

ctx := context.Background()

// trap Ctrl+C and call cancel on the context
ctx, cancel := context.WithCancel(ctx)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
defer func() {
signal.Stop(c)
cancel()
}()
go func() {
select {
case sig := <-c:
log.Errorf("Caught signal %v, shutting down...\n", sig)
cancel()
case <-ctx.Done():
trackerPort := viper.GetInt("tracker-port")
if trackerPort == 0 {
// TODO: 使用随机端口会导致 serve 服务重启后原 magnet uri 失效
trackerPort, err = libtorrent.GetAvailablePort(viper.GetString("tracker-port-range"))
if err != nil {
log.Fatalf("Couldn't get available port: %v", err)
} else {
log.Infof("Founded available port: %v", trackerPort)
}
}()
err = server.RunServer(ctx)
}
log.Infof("Start tracker: http://%s:%d/1/announce ", trackerIP, trackerPort)
go libtracker.RunTrackerServer(fmt.Sprintf(":%d", trackerPort))

// 2. make torrent
torrentFile := file + ".torrent"
files := []string{file}
trackers := []string{fmt.Sprintf("http://%s:%d/1/announce", trackerIP, trackerPort)}
log.Infof("Make torrent %s to %s", file, torrentFile)
magnet, err := libtorrent.CreateTorrent(files, torrentFile, "", "", false, 0, "", trackers, []string{})
if err != nil {
log.Fatal("Failed to create torrent: ", err)
}
log.Infof("Magnet: %s", magnet)
// 3. Start torrent uploader
err = libtorrent.RunTorrentServer(torrentFile, viper.GetString("dir"), true, false)
if err != nil {
log.Fatal("Failed to run server: ", err)
log.Fatal("Failed to run torrent server: ", err)
}

},
}
serveCmd.MarkFlagRequired("files")
return serveCmd
}
Loading

0 comments on commit cca52c4

Please sign in to comment.