From 03fbdcda604329c002c95adec5f0b7edd16251a5 Mon Sep 17 00:00:00 2001 From: liwei Date: Mon, 27 May 2019 19:18:28 +0800 Subject: [PATCH] BitTorrent peer write test --- README.md | 5 +- cmd/bt.go | 13 +- cmd/test.go | 23 +- cmd/udpclient.go | 21 -- cmd/udpserver.go | 27 -- common/buffer.go | 41 +++ down/bt/errors.go | 25 ++ down/bt/peer.go | 258 ++++++++++++++++++ down/bt/peer_test.go | 157 +++++++++++ ...rones.S08E05.720p.WEB.H264-MEMENTO.torrent | Bin 0 -> 28480 bytes down/bt/tracker.go | 223 ++++++++------- down/http/down.go | 8 +- down/http/down_test.go | 26 +- go.mod | 5 +- go.sum | 5 - 15 files changed, 649 insertions(+), 188 deletions(-) delete mode 100644 cmd/udpclient.go delete mode 100644 cmd/udpserver.go create mode 100644 common/buffer.go create mode 100644 down/bt/errors.go create mode 100644 down/bt/peer.go create mode 100644 down/bt/peer_test.go create mode 100644 down/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent diff --git a/README.md b/README.md index 52157021..68616575 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ ## gopeed -A fast download client,support HTTP&P2P. \ No newline at end of file +A fast download client,support HTTP&P2P. + +## 参考 +[https://wiki.theory.org/index.php/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29](https://wiki.theory.org/index.php/BitTorrentSpecification#Peer_wire_protocol_.28TCP.29) \ No newline at end of file diff --git a/cmd/bt.go b/cmd/bt.go index c63f6d61..6d964247 100644 --- a/cmd/bt.go +++ b/cmd/bt.go @@ -6,16 +6,5 @@ import ( func main() { metaInfo, _ := bt.ParseFromFile("e:/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") - // metaInfo, _ := down.ParseFromFile("e:/test.torrent") - if len(metaInfo.AnnounceList) > 0 { - for _, announceArr := range metaInfo.AnnounceList { - if len(announceArr) > 0 { - for _, announce := range announceArr { - bt.DoTracker(announce, metaInfo) - } - } - } - } else { - bt.DoTracker(metaInfo.Announce, metaInfo) - } + metaInfo.Tracker() } diff --git a/cmd/test.go b/cmd/test.go index 84a67158..533add54 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -1,9 +1,24 @@ package main -import "fmt" +import ( + "fmt" + "io" + "net" +) func main() { - var b = -1 - var n = uint32(b) - fmt.Println(n) + data := [68]byte{0x13, 0x42, 0x69, 0x74, 0x54, 0x6f, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x05, 0x2c, 0x37, 0xb2, 0x23, 0xe3, 0xaf, 0x73, 0xe7, 0xb0, 0xe7, 0xa0, 0x05, 0x03, 0x07, 0xe6, 0x61, 0xcf, 0x94, 0xe7, 0x0d, 0x2d, 0x55, 0x54, 0x33, 0x35, 0x35, 0x53, 0x2d, 0xaf, 0xb0, 0xec, 0xdc, 0xbf, 0xd1, 0x02, 0x93, 0xd6, 0xf4, 0x9b, 0x24} + conn, err := net.Dial("tcp", "58.179.218.77:47474") + if err != nil { + fmt.Println(err) + } + defer conn.Close() + conn.Write(data[:]) + + var read [68]byte + _, err = io.ReadFull(conn, read[:]) + if err != nil { + fmt.Println(err) + } + fmt.Println(read) } diff --git a/cmd/udpclient.go b/cmd/udpclient.go deleted file mode 100644 index 63f074ae..00000000 --- a/cmd/udpclient.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -import ( - "fmt" - "net" -) - -func main() { - ip := net.ParseIP("127.0.0.1") - srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0} - dstAddr := &net.UDPAddr{IP: ip, Port: 9981} - conn, err := net.DialUDP("udp", srcAddr, dstAddr) - if err != nil { - fmt.Println(err) - } - defer conn.Close() - conn.Write([]byte("hello")) - conn.Write([]byte("test")) - conn.Read([]byte{}) - fmt.Printf("<%s>\n", conn.RemoteAddr()) -} diff --git a/cmd/udpserver.go b/cmd/udpserver.go deleted file mode 100644 index 28682959..00000000 --- a/cmd/udpserver.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - "net" -) - -func main() { - conn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9981}) - if err != nil { - fmt.Println(err) - return - } - fmt.Printf("Local: <%s> \n", conn.LocalAddr().String()) - data := make([]byte, 1024) - for { - n, remoteAddr, err := conn.ReadFromUDP(data) - if err != nil { - fmt.Printf("error during read: %s", err) - } - fmt.Printf("<%s> %s\n", remoteAddr, data[:n]) - _, err = conn.WriteToUDP([]byte("world"), remoteAddr) - if err != nil { - fmt.Printf(err.Error()) - } - } -} diff --git a/common/buffer.go b/common/buffer.go new file mode 100644 index 00000000..ce8514aa --- /dev/null +++ b/common/buffer.go @@ -0,0 +1,41 @@ +package main + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +type User struct { + Age uint32 + Sex byte + Name [20]byte +} + +type Buffer struct { + buf []byte + limit int +} + +func NewBuffer(buf []byte) *Buffer { + return &Buffer{ + buf: buf, + limit: 0, + } +} + +func main() { + user := &User{1, 2, [20]byte{}} + buf := new(bytes.Buffer) + binary.Write(buf, binary.LittleEndian, user) + fmt.Println(buf.Bytes()) +} + +/*func (buffer Buffer) ReadByte() (byte, error) { + +} + +func (buffer Buffer) ReadByte() (byte, error)() { + +} +*/ diff --git a/down/bt/errors.go b/down/bt/errors.go new file mode 100644 index 00000000..daf1a72c --- /dev/null +++ b/down/bt/errors.go @@ -0,0 +1,25 @@ +package bt + +import ( + "fmt" +) + +type ErrorCode int + +const ( + ErrTimeout ErrorCode = -1 + ErrResponse ErrorCode = -2 +) + +type TrackerError struct { + Code ErrorCode + Err error +} + +func (te *TrackerError) Error() string { + return fmt.Sprintf("%s:%d", te.Err.Error(), te.Code) +} + +func NewTrackerError(code ErrorCode, err error) *TrackerError { + return &TrackerError{code, err} +} diff --git a/down/bt/peer.go b/down/bt/peer.go new file mode 100644 index 00000000..329e6adf --- /dev/null +++ b/down/bt/peer.go @@ -0,0 +1,258 @@ +package bt + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + "net" + "strconv" +) + +type MessageType byte + +const ( + ProtocolIdentifier = "BitTorrent protocol" + ProtocolIdentifierLength = 0x13 + + Choke MessageType = iota + Unchoke + Interested + NotInterested + Have + Bitfield + Request + Piece + Cancel +) + +type Peer struct { + IP uint32 + Port uint16 +} + +func (peer *Peer) Address() string { + bts := make([]byte, 4) + binary.BigEndian.PutUint32(bts, peer.IP) + return net.IP(bts).String() + ":" + strconv.Itoa(int(peer.Port)) +} + +type Handshake struct { + Pstrlen byte + Pstr [19]byte + Reserved [8]byte + InfoHash [20]byte + PeerID [20]byte +} + +type Message struct { + Length uint32 + Type byte + Payload []byte +} + +func NewHandshake(reserved [8]byte, infoHash [20]byte, peerID [20]byte) *Handshake { + var arr [ProtocolIdentifierLength]byte + copy(arr[:], ProtocolIdentifier) + return &Handshake{ + Pstrlen: ProtocolIdentifierLength, + Pstr: arr, + Reserved: reserved, + InfoHash: infoHash, + PeerID: peerID, + } +} + +func (handshake *Handshake) encode() ([]byte, error) { + writer := new(bytes.Buffer) + err := binary.Write(writer, binary.BigEndian, handshake) + if err != nil { + return nil, err + } + return writer.Bytes(), nil +} + +func (handshake *Handshake) decode(buf []byte) error { + reader := bytes.NewReader(buf) + err := binary.Read(reader, binary.BigEndian, handshake) + if err != nil { + return err + } + return nil +} + +func (peer *Peer) DoDownload(metaInfo *MetaInfo, peerId [20]byte) error { + if peer.Port == 0 { + return fmt.Errorf("error port %d", peer.Port) + } + conn, err := net.Dial("tcp", peer.Address()) + if err != nil { + return err + } + defer conn.Close() + err = doHandshake(peer, metaInfo, peerId, conn) + if err != nil { + return err + } + + scanner := bufio.NewScanner(conn) + scanner.Split(splitMessage) + for scanner.Scan() { + buf := scanner.Bytes() + message := decodeMessage(buf) + // keep-alive message + if message.Length == 0 { + + } else { + switch MessageType(buf[4]) { + case Choke: + break + case Unchoke: + break + case Interested: + break + case NotInterested: + break + case Have: + break + case Bitfield: + break + case Request: + break + case Piece: + break + case Cancel: + break + } + } + } + + /*var ( + // This client is choking the peer + amChocking = true + // This client is interested in the peer + amInterested = false + // Peer is choking this client + peerChocking = true + // Peer is interested in this client + peerInterested = false + ) + + // Receive message + var cacheBuf []byte + buf := make([]byte, 8192) + for { + scanner := bufio.NewScanner(conn) + n, err := conn.Read(buf) + if n > 0 { + if len(cacheBuf) > 0 { + buf = append(cacheBuf, buf...) + } + msg, err := decodeMessage(buf, n) + if err != nil { + if err == NMD { + cacheBuf = make([]byte, n) + copy(cacheBuf, buf[0:n]) + } + return err + } + + msgLength := binary.BigEndian.Uint32(buf) + // keep-alive message + if msgLength == 0 { + + } else { + switch MessageType(buf[4]) { + case Choke: + peerChocking = true + break + case Unchoke: + peerChocking = false + break + case Interested: + peerInterested = true + break + case NotInterested: + peerInterested = false + break + case Have: + break + case Bitfield: + break + case Request: + break + case Piece: + break + case Cancel: + break + + } + + } + + } + if err != nil { + if err == io.EOF { + return nil + } + return err + } + }*/ + return nil +} + +// Handshake of Peer wire protocol +// Per https://wiki.theory.org/index.php/BitTorrentSpecification#Handshake +func doHandshake(peer *Peer, metaInfo *MetaInfo, peerId [20]byte, conn net.Conn) error { + reserved := [8]byte{} + reserved[5] = 0x10 + reserved[6] = 0x0 + reserved[7] = 0x5 + handshakeReq := NewHandshake(reserved, metaInfo.InfoHash, peerId) + buf, err := handshakeReq.encode() + if err != nil { + return err + } + conn.Write(buf) + + var read [68]byte + _, err = io.ReadFull(conn, read[:]) + if err != nil { + return err + } + handshakeRes := new(Handshake) + err = handshakeRes.decode(read[:]) + if err != nil { + return err + } + if handshakeRes.InfoHash != handshakeReq.InfoHash { + return err + } + return nil +} + +func splitMessage(data []byte, atEOF bool) (advance int, token []byte, err error) { + if !atEOF && len(data) > 4 { + length := int(binary.BigEndian.Uint32(data)) + if len(data)-4 >= length { + return length + 4, data[:length+4], nil + } + } + return +} + +func decodeMessage(buf []byte) *Message { + msg := &Message{} + msg.Length = binary.BigEndian.Uint32(buf) + // keep-alive message + if msg.Length > 0 { + msg.Type = buf[4] + msg.Payload = buf[5:] + } + return msg +} + +func doBitfield(peer *Peer, metaInfo *MetaInfo, conn net.Conn) { + +} diff --git a/down/bt/peer_test.go b/down/bt/peer_test.go new file mode 100644 index 00000000..75e13a6a --- /dev/null +++ b/down/bt/peer_test.go @@ -0,0 +1,157 @@ +package bt + +import ( + "encoding/binary" + "fmt" + "log" + "testing" +) + +func TestDoDownload(t *testing.T) { + metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") + if err != nil { + log.Fatal(err) + } + + peers, err := metaInfo.Tracker() + if err != nil { + log.Fatal(err) + } + + testPeer := peers[0] + fmt.Println(testPeer.Address()) + + peerId, err := GenPeerID() + if err != nil { + log.Fatal(err) + } + + type fields struct { + IP uint32 + Port uint16 + } + type args struct { + metaInfo *MetaInfo + peerId [20]byte + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + "GOTS08E05", + fields{ + testPeer.IP, + testPeer.Port, + }, + args{ + metaInfo, + peerId, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + peer := &Peer{ + IP: tt.fields.IP, + Port: tt.fields.Port, + } + if err := peer.DoDownload(tt.args.metaInfo, tt.args.peerId); (err != nil) != tt.wantErr { + t.Errorf("Peer.DoDownload() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestFindPeer(t *testing.T) { + metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") + if err != nil { + log.Fatal(err) + } + + peers, err := metaInfo.Tracker() + if err != nil { + log.Fatal(err) + } + + // peerId, err := GenPeerID() + peerId, err := [20]byte{0x2d, 0x55, 0x54, 0x33, 0x35, 0x35, 0x53, 0x2d, 0xaf, 0xb0, 0xec, 0xdc, 0xbf, 0xd1, 0x02, 0x93, 0xd6, 0xf4, 0x9b, 0x24}, nil + if err != nil { + log.Fatal(err) + } + ch := make(chan Peer) + for i, peer := range peers { + go func(i int, peer Peer) { + fmt.Println(peer.Address()) + err := peer.DoDownload(metaInfo, peerId) + if err == nil { + ch <- peer + } else { + fmt.Printf("%d:%v\n", i, err) + } + }(i, peer) + } + peer := <-ch + fmt.Println(peer.Address()) +} + +func TestDoDownloadSpecialPeer(t *testing.T) { + metaInfo, err := ParseFromFile("testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent") + if err != nil { + log.Fatal(err) + } + + // 101.188.129.156:14475 + testPeer := Peer{ + binary.BigEndian.Uint32([]byte{101, 188, 129, 156}), + 14475, + } + fmt.Println(testPeer.Address()) + + peerId, err := GenPeerID() + if err != nil { + log.Fatal(err) + } + + type fields struct { + IP uint32 + Port uint16 + } + type args struct { + metaInfo *MetaInfo + peerId [20]byte + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + "GOTS08E05", + fields{ + testPeer.IP, + testPeer.Port, + }, + args{ + metaInfo, + peerId, + }, + false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + peer := &Peer{ + IP: tt.fields.IP, + Port: tt.fields.Port, + } + if err := peer.DoDownload(tt.args.metaInfo, tt.args.peerId); (err != nil) != tt.wantErr { + t.Errorf("Peer.DoDownload() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/down/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent b/down/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent new file mode 100644 index 0000000000000000000000000000000000000000..bad833c442db145ffd50852177723918d36a0eef GIT binary patch literal 28480 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 literal 0 HcmV?d00001 diff --git a/down/bt/tracker.go b/down/bt/tracker.go index b276409b..21b098dc 100644 --- a/down/bt/tracker.go +++ b/down/bt/tracker.go @@ -32,7 +32,7 @@ const ( 8 32-bit integer action 0 // connect 12 32-bit integer transaction_id 16 - Detail see https://www.libtorrent.org/udp_tracker_protocol.html#connecting + Per https://www.libtorrent.org/udp_tracker_protocol.html#connecting */ type UdpConnectRequest struct { ProtocolId uint64 @@ -93,7 +93,7 @@ func NewUdpConnectResponse(buf []byte) *UdpConnectResponse { 92 32-bit integer num_want -1 // default 96 16-bit integer port 98 - Detail see https://www.libtorrent.org/udp_tracker_protocol.html#announcing + Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing */ type UdpAnnounceRequest struct { ConnectionId uint64 @@ -147,6 +147,7 @@ func (req *UdpAnnounceRequest) encode() []byte { 20 + 6 * n 32-bit integer IP address 24 + 6 * n 16-bit integer TCP port 20 + 6 * N + Per https://www.libtorrent.org/udp_tracker_protocol.html#announcing */ type UdpAnnounceResponse struct { Action uint32 @@ -154,8 +155,7 @@ type UdpAnnounceResponse struct { Interval uint32 Leechers uint32 Seeders uint32 - IP []uint32 - Port []uint16 + Peers []Peer } func NewUdpAnnounceResponse(buf []byte) *UdpAnnounceResponse { @@ -167,48 +167,33 @@ func NewUdpAnnounceResponse(buf []byte) *UdpAnnounceResponse { Seeders: binary.BigEndian.Uint32(buf[16:20]), } count := (len(buf) - 20) / 6 - response.IP = make([]uint32, count, count) - response.Port = make([]uint16, count, count) + response.Peers = make([]Peer, count, count) for i := 0; i < count; i++ { - begin := 20 + 6*i - response.IP[i] = binary.BigEndian.Uint32(buf[begin : begin+4]) - response.Port[i] = binary.BigEndian.Uint16(buf[begin+4 : begin+6]) + ipBegin := 20 + 6*i + portBegin := ipBegin + 4 + response.Peers[i] = Peer{ + IP: binary.BigEndian.Uint32(buf[ipBegin:portBegin]), + Port: binary.BigEndian.Uint16(buf[portBegin : portBegin+2]), + } } return response } -type UdpTrackerContent struct { - URL *url.URL - MetaInfo *MetaInfo - Conn *net.UDPConn - ConnectRequest *UdpConnectRequest - ConnectResponse *UdpConnectResponse - AnnounceRequest *UdpAnnounceRequest - AnnounceResponse *UdpAnnounceResponse -} - -func NewUdpTrackerContent(url *url.URL, metaInfo *MetaInfo) *UdpTrackerContent { - return &UdpTrackerContent{ - URL: url, - MetaInfo: metaInfo, - } -} - -func (content *UdpTrackerContent) connect() error { - host, portStr, err := net.SplitHostPort(content.URL.Host) +func dial(trackerUrl *url.URL) (*net.UDPConn, error) { + host, portStr, err := net.SplitHostPort(trackerUrl.Host) if err != nil { fmt.Println(err) - return err + return nil, err } port, err := strconv.ParseInt(portStr, 10, 32) if err != nil { fmt.Println(err) - return err + return nil, err } - ip, err := lookupIP(host, content.URL.Scheme) + ip, err := lookupIP(host, trackerUrl.Scheme) if err != nil { fmt.Println(err) - return err + return nil, err } srcAddr := &net.UDPAddr{IP: net.IPv4zero, Port: 0} @@ -216,87 +201,99 @@ func (content *UdpTrackerContent) connect() error { IP: ip, Port: int(port), } - conn, err := net.DialUDP(content.URL.Scheme, srcAddr, dstAddr) - if err != nil { - fmt.Println(err) - return err - } - // Connect - request := NewUdpConnectRequest() - var response *UdpConnectResponse - for n := 0; n < udpConnectRetries; n++ { - buf := request.Encode() - conn.Write(buf) - // 15 * 2 ^ n seconds - timeout := udpConnectTimeout * math.Pow(2, float64(n)) - conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) - len, err := conn.Read(buf) - if len == 16 && err == nil { - response = NewUdpConnectResponse(buf) - break - } - } - if response == nil { - conn.Close() - return errors.New("UDP tracker connect error") - } - if response.Action != udpActionConnect || request.TransactionId != response.TransactionId { - conn.Close() - return errors.New("UDP tracker connect response error") - } - content.Conn = conn - content.ConnectRequest = request - content.ConnectResponse = response - return nil + return net.DialUDP(trackerUrl.Scheme, srcAddr, dstAddr) } -func (content *UdpTrackerContent) announce() error { - request := NewUdpAnnounceRequest(content.ConnectResponse.ConnectionId) - request.InfoHash = content.MetaInfo.InfoHash - peerID, err := genPeerID() +func connect(conn *net.UDPConn, timeout int64) (response *UdpConnectResponse, err error) { + request := NewUdpConnectRequest() + buf := request.Encode() + conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) + conn.Write(buf) + len, err := conn.Read(buf) + if err != nil { + return + } + if len != 16 { + err = NewTrackerError(ErrResponse, errors.New("invalid response")) + return + } + response = NewUdpConnectResponse(buf) + return +} + +func announce(conn *net.UDPConn, timeout int64, connectionId uint64, metaInfo *MetaInfo) (response *UdpAnnounceResponse, err error) { + request := NewUdpAnnounceRequest(connectionId) + request.InfoHash = metaInfo.InfoHash + peerID, err := GenPeerID() if err != nil { fmt.Println(err) - return err + return } request.PeerID = peerID request.Downloaded = 0 request.Uploaded = 0 - request.Left = content.MetaInfo.GetTotalSize() - request.Downloaded + request.Left = metaInfo.GetTotalSize() - request.Downloaded request.Event = 0 request.IP = 0 request.Key = 0 request.NumWant = 50 request.Port = 6882 encode := request.encode() - content.Conn.Write(encode) + conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(timeout))) + conn.Write(encode) buf := make([]byte, 512) - len, err := content.Conn.Read(buf) + len, err := conn.Read(buf) if err != nil { fmt.Println(err) - return err + return } - response := NewUdpAnnounceResponse(buf[:len]) - fmt.Println(response) - return nil + response = NewUdpAnnounceResponse(buf[:len]) + return } -func DoTracker(announce string, metaInfo *MetaInfo) { +func (metaInfo *MetaInfo) Tracker() (peers []Peer, err error) { + if len(metaInfo.AnnounceList) > 0 { + for _, announceArr := range metaInfo.AnnounceList { + if len(announceArr) > 0 { + for _, announce := range announceArr { + peers, err = doTracker(announce, metaInfo) + if err == nil { + return + } + } + } + } + } else { + peers, err = doTracker(metaInfo.Announce, metaInfo) + if err == nil { + return + } + } + return +} + +func doTracker(announce string, metaInfo *MetaInfo) (peers []Peer, err error) { if announce != "" { url, _ := url.Parse(announce) switch url.Scheme { case "http", "https": // TODO UDP test break - httpTracker(url, metaInfo) + return httpTracker(url, metaInfo) case "udp", "udp4", "udp6": - udpTracker(url, metaInfo) + return udpTracker(url, metaInfo) + default: + return nil, errors.New("unsupported protocol") } } + return nil, errors.New("empty announce") } -func genPeerID() ([20]byte, error) { - peerId := [20]byte{'G', 'P', '0', '0', '1'} - _, err := rand.Read(peerId[5:]) +// 生成Peer ID,规则为前三位固定字母(-GP)+SemVer(xyz),后面随机生成 +// 参考:https://wiki.theory.org/index.php/BitTorrentSpecification#peer_id +func GenPeerID() ([20]byte, error) { + peerId := [20]byte{'-', 'G', 'P', '0', '0', '1'} + _, err := rand.Read(peerId[6:]) if err != nil { return peerId, err } @@ -304,13 +301,12 @@ func genPeerID() ([20]byte, error) { } // http://bittorrent.org/beps/bep_0003.html#trackers -func httpTracker(url *url.URL, metaInfo *MetaInfo) { +func httpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) { query := url.Query() query.Add("info_hash", string(metaInfo.InfoHash[:])) // Generate peer ID - peerID, err := genPeerID() + peerID, err := GenPeerID() if err != nil { - fmt.Println(err) return } query.Add("peer_id", string(peerID[:])) @@ -321,39 +317,76 @@ func httpTracker(url *url.URL, metaInfo *MetaInfo) { url.RawQuery = query.Encode() request, err := http.NewRequest("GET", url.String(), nil) if err != nil { - fmt.Println(err) return } var httpClient = &http.Client{ Timeout: time.Second * time.Duration(15), } request.Header.Set("User-Agent", "gopeed/0.0.1") - fmt.Println("request:" + url.String()) response, err := httpClient.Do(request) if err != nil { - fmt.Println(err) return } defer response.Body.Close() resp, err := ioutil.ReadAll(response.Body) if err != nil { if err != io.EOF { - fmt.Println(err) return } } - fmt.Println(string(resp)) + // TODO Parse HTTP tracker response + fmt.Println(resp) + return nil, nil } // http://bittorrent.org/beps/bep_0015.html -func udpTracker(url *url.URL, metaInfo *MetaInfo) { - content := NewUdpTrackerContent(url, metaInfo) - err := content.connect() +func udpTracker(url *url.URL, metaInfo *MetaInfo) (peers []Peer, err error) { + conn, err := dial(url) if err != nil { - fmt.Println(err) return } - content.announce() + + announceResponse, err := func() (announceResponse *UdpAnnounceResponse, err error) { + var connectResponse *UdpConnectResponse + // 0:connect 1:announce + state := 0 + // 访问超时最多重试8次,当有请求成功则将重试次数重置为0 + for n := 0; n < udpConnectRetries; n++ { + // 15 * 2 ^ n seconds + timeout := int64(udpConnectTimeout * math.Pow(2, float64(n))) + switch state { + case 0: + connectResponse, err = connect(conn, timeout) + if err != nil { + if oe, ok := err.(*net.OpError); ok && oe.Timeout() { + break + } else { + return + } + } + n = -1 + state = 1 + break + case 1: + announceResponse, err = announce(conn, timeout, connectResponse.ConnectionId, metaInfo) + if err != nil { + if oe, ok := err.(*net.OpError); ok && oe.Timeout() { + break + } else { + return + } + } + return + } + } + return + }() + + if err != nil { + return + } + + return announceResponse.Peers, nil } func lookupIP(host string, scheme string) (ip net.IP, err error) { diff --git a/down/http/down.go b/down/http/down.go index 25b21bf0..2b405b2a 100644 --- a/down/http/down.go +++ b/down/http/down.go @@ -33,7 +33,7 @@ func Resolve(request *Request) (*Response, error) { } defer response.Body.Close() if response.StatusCode != 200 && response.StatusCode != 206 { - return nil, fmt.Errorf("Response status error:%d", response.StatusCode) + return nil, fmt.Errorf("response status error:%d", response.StatusCode) } ret := &Response{} // Get file name by "Content-Disposition" @@ -165,10 +165,10 @@ func downChunk(request *Request, file *os.File, start int64, end int64, waitGrou buf := make([]byte, 8192) writeIndex := int64(start) for { - len, err := httpResponse.Body.Read(buf) - if len > 0 { + n, err := httpResponse.Body.Read(buf) + if n > 0 { fileLock.Lock() - writeSize, err := file.WriteAt(buf[0:len], writeIndex) + writeSize, err := file.WriteAt(buf[0:n], writeIndex) fileLock.Unlock() if err != nil { fmt.Println(err) diff --git a/down/http/down_test.go b/down/http/down_test.go index ea8c11e8..5654f8c8 100644 --- a/down/http/down_test.go +++ b/down/http/down_test.go @@ -104,28 +104,24 @@ func TestResolve(t *testing.T) { } func TestTemp(t *testing.T) { - f, err := os.Create("e:/data/test.data") + f, err := os.Create("e:/testbt/test.data") if err != nil { log.Fatal(err) } - s := time.Now().UnixNano() - if err := f.Truncate(1024 * 1024 * 1024 * 1); err != nil { + if err := f.Truncate(1024 * 1024 * 512); err != nil { log.Fatal(err) } - fmt.Printf("use %d\n", time.Now().UnixNano()-s) + bts := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9} + s := time.Now().UnixNano() + f.Write(bts) + fmt.Printf("write from 0 use: %d\n", time.Now().UnixNano()-s) s = time.Now().UnixNano() - f.Write([]byte{1}) - fmt.Printf("use %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Seek(1024*1024*512, 0) - fmt.Printf("use %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Write([]byte{2}) - fmt.Printf("use %d\n", time.Now().UnixNano()-s) - s = time.Now().UnixNano() - f.Write([]byte{3}) - fmt.Printf("use %d\n", time.Now().UnixNano()-s) + f.Seek(1024*1024*128, 0) + f.Write(bts) + fmt.Printf("write from 128M use %d\n", time.Now().UnixNano()-s) s = time.Now().UnixNano() + f.Write(bts) + fmt.Printf("write from 128M+9b use %d\n", time.Now().UnixNano()-s) } func fileMd5(filePath string) string { diff --git a/go.mod b/go.mod index 8e78fd94..eb0b6446 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,4 @@ module gopeed go 1.12 -require ( - github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e - golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 -) +require github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e diff --git a/go.sum b/go.sum index a5c4caa9..7aa90f02 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,2 @@ github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e h1:KMs6SK8iDSR1+ZzOK10L5wGPpWDByyvOe5nrqk51g2U= github.com/marksamman/bencode v0.0.0-20150821143521-dc84f26e086e/go.mod h1:+AHfJo5+69p+fjvMJTmYajNP9rFBHQcaTDFmuXRRATI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09 h1:KaQtG+aDELoNmXYas3TVkGNYRuq8JQ1aa7LJt8EXVyo= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=