1

ファイルを複数の部分に分割してダウンロードすることを想定している次のコードがあります。しかし、現在は画像でのみ機能します。tar ファイルなどの他のファイルをダウンロードしようとすると、出力が無効なファイルになります。

更新しました:

ファイルモードos.WriteAtの代わりに使用os.Writeされ、削除されました。os.O_APPEND

package main

import (
    "errors"
    "flag"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "strconv"
)

var file_url string
var workers int
var filename string

func init() {
    flag.StringVar(&file_url, "url", "", "URL of the file to download")
    flag.StringVar(&filename, "filename", "", "Name of downloaded file")
    flag.IntVar(&workers, "workers", 2, "Number of download workers")
}

func get_headers(url string) (map[string]string, error) {
    headers := make(map[string]string)
    resp, err := http.Head(url)
    if err != nil {
        return headers, err
    }

    if resp.StatusCode != 200 {
        return headers, errors.New(resp.Status)
    }

    for key, val := range resp.Header {
        headers[key] = val[0]
    }
    return headers, err
}

func download_chunk(url string, out string, start int, stop int) {
    client := new(http.Client)
    req, _ := http.NewRequest("GET", url, nil)
    req.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", start, stop))
    resp, _ := client.Do(req)

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatalln(err)
        return
    }

    file, err := os.OpenFile(out, os.O_WRONLY, 0600)
    if err != nil {
        if file, err = os.Create(out); err != nil {
            log.Fatalln(err)
            return
        }
    }
    defer file.Close()

    if _, err := file.WriteAt(body, int64(start)); err != nil {
        log.Fatalln(err)
        return
    }

    fmt.Println(fmt.Sprintf("Range %d-%d: %d", start, stop, resp.ContentLength))
}

func main() {
    flag.Parse()
    headers, err := get_headers(file_url)
    if err != nil {
        fmt.Println(err)
    } else {
        length, _ := strconv.Atoi(headers["Content-Length"])
        bytes_chunk := length / workers
        fmt.Println("file length: ", length)
        for i := 0; i < workers; i++ {
            start := i * bytes_chunk
            stop := start + (bytes_chunk - 1)
            go download_chunk(file_url, filename, start, stop)
        }
        var input string
        fmt.Scanln(&input)
    }
}

基本的には、ファイルの長さを読み取り、それをワーカーの数で割り、HTTP の Range ヘッダーを使用して各ファイルをダウンロードします。ダウンロード後、そのチャンクが書き込まれているファイル内の位置を探します。

4

2 に答える 2

3

上記のような多くのエラーを本当に無視する場合、コードはどのファイル タイプに対しても確実に動作するはずがありません。

ただし、コードに問題があることがわかります。O_APPEND と seek を混在させるのはおそらく間違いだと思います (このモードでは Seek を無視する必要があります)。代わりに(*os.File).WriteAtを使用することをお勧めします。

IIRC、O_APPEND は、すべての書き込みを [現在の] ファイルの終わりに強制的に発生させます。ただし、download_chunkファイル パーツの関数インスタンスは予測できない順序で実行される可能性があるため、ファイル パーツが "並べ替え" られます。その結果、ファイルが破損します。

于 2013-08-23T06:43:31.390 に答える
1

1.go ルーチンのシーケンスが不明。実行結果は次のようになります。

...

ファイル長:20902

範囲 10451 ~ 20901:10451

範囲 0~10450:10451

...

そのため、チャンクを追加することはできません。

2. 書き込みチャンク データに sys.Mutex が必要な場合

(私の英語は下手です、忘れてください)

于 2013-08-23T07:01:27.773 に答える