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 00000000..bad833c4 Binary files /dev/null and b/down/bt/testdata/Game.of.Thrones.S08E05.720p.WEB.H264-MEMENTO.torrent differ 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=