1. 程式人生 > >Go網路檔案傳輸

Go網路檔案傳輸

流程分析

藉助TCP完成檔案的傳輸,基本思路如下:

  1. 傳送方(客戶端)向服務端傳送檔名,服務端儲存該檔名。
  2. 接收方(服務端)向客戶端返回一個訊息ok,確認檔名儲存成功。
  3. 傳送方(客戶端)收到訊息後,開始向服務端傳送檔案資料。
  4. 接收方(服務端)讀取檔案內容,寫入到之前儲存好的檔案中。

由於檔案傳輸需要穩定可靠的連線,所以採用TCP方式完成網路檔案傳輸功能。

首先獲取檔名。藉助os包中的stat()函式來獲取檔案屬性資訊。在函式返回的檔案屬性中包含檔名和檔案大小。Stat引數name傳入的是檔案訪問的絕對路徑。FileInfo中的Name()函式可以將檔名單獨提取出來。

func Stat(name string) (fi FileInfo, err error)

Stat返回一個描述name指定的檔案物件的FileInfo。如果指定的檔案物件是一個符號連結,返回的FileInfo描述該符號連結指向的檔案的資訊,本函式會嘗試跳轉該連結。如果出錯,返回的錯誤值為*PathError型別。

我們通過原始碼可以得知FileInfo是一個介面,要實現這個介面就必須實現這個介面的如下所有方法

實現網路檔案傳輸實質上時藉助了本地檔案複製和TCP網路程式設計相關知識,可以先看看Go語言複製檔案和Go網路程式設計瞭解相關內容。

所以關於使用TCP實現檔案傳輸大致步驟可以歸結為如下步驟

接收端:

  1. 建立監聽 listener,程式結束時關閉。
  2. 阻塞等待客戶端連線 conn,程式結束時關閉conn。
  3. 讀取客戶端傳送檔名。儲存 fileName。
  4. 回發“ok”。
  5. 封裝函式 RecvFile 接收客戶端傳送的檔案內容。傳參 fileName 和 conn
  6. 按檔名 Create 檔案,結束時 Close
  7. 迴圈 Read 傳送端網路檔案內容,當讀到 0 說明檔案讀取完畢。
  8. 將讀到的內容原封不動Write到建立的檔案中

接收端程式碼:

package main

import (
    "fmt"
    "io"
    "net"
    "os"
)

func recvFile(conn net.Conn, fileName string) {
    //按照檔名建立新檔案
    file, err := os.Create(fileName)
    if err != nil {
        fmt.Printf("os.Create()函式執行錯誤,錯誤為:%v\n", err)
        return
    }
    defer file.Close()

    //從網路中讀資料,寫入本地檔案
    for {
        buf := make([]byte, 4096)
        n, err := conn.Read(buf)

        //寫入本地檔案,讀多少,寫多少
        file.Write(buf[:n])
        if err != nil {
            if err == io.EOF {
                fmt.Printf("接收檔案完成。\n")
            } else {
                fmt.Printf("conn.Read()方法執行出錯,錯誤為:%v\n", err)
            }
            return
        }
    }
}

func main() {

    //1.建立監聽socket
    listener, err := net.Listen("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Printf("net.Listen()函式執行錯誤,錯誤為:%v\n", err)
        return
    }
    defer listener.Close()

    //阻塞監聽
    conn, err := listener.Accept()
    if err != nil {
        fmt.Printf("listener.Accept()方法執行錯誤,錯誤為:%v\n", err)
        return
    }
    defer conn.Close()

    //檔名的長度不能超過1024個位元組
    buf := make([]byte, 4096)
    n, err := conn.Read(buf)
    if err != nil {
        fmt.Printf("conn.Read()方法執行錯誤,錯誤為:%v\n", err)
        return
    }
    fileName := string(buf[:n])

    //回寫ok給傳送端
    conn.Write([]byte("ok"))

    //獲取檔案內容
    recvFile(conn, fileName)
}

傳送端:

  1. 提示使用者使用命令列引數輸入檔名。接收檔名 filepath(含訪問路徑)
  2. 使用 os.Stat()獲取檔案屬性,得到純檔名 fileName(去除訪問路徑)
  3. 主動發起連線伺服器請求,結束時關閉連線。
  4. 傳送檔名到接收端 conn.Write()
  5. 讀取接收端回發的確認資料 conn.Read()
  6. 判斷是否為“ok”。如果是,封裝函式 SendFile() 傳送檔案內容。傳參 filePath 和 conn
  7. 只讀 Open 檔案, 結束時Close檔案
  8. 迴圈讀本地檔案,讀到 EOF,讀取完畢。
  9. 將讀到的內容原封不動 conn.Write 給接收端(伺服器)

傳送端程式碼:

package main

import (
    "fmt"
    "io"
    "net"
    "os"
)
func sendFile(conn net.Conn, filePath string) {
    //只讀開啟檔案
    file, err := os.Open(filePath)
    if err != nil {
        fmt.Printf("os.Open()函式執行出錯,錯誤為:%v\n", err)
        return
    }
    defer file.Close()

    buf := make([]byte, 4096)
    for {
        //從本地檔案中讀資料,寫給網路接收端。讀多少,寫多少
        n, err := file.Read(buf)
        if err != nil {
            if err == io.EOF {
                fmt.Printf("傳送檔案完畢\n")
            } else {
                fmt.Printf("file.Read()方法執行錯誤,錯誤為:%v\n", err)
            }
            return
        }
        //寫到網路socket中
        _, err = conn.Write(buf[:n])
    }
}

func main() {

    //獲取命令列引數
    list := os.Args

    if len(list) != 2 {
        fmt.Printf("格式為:go run xxx.go 檔名\n")
        return
    }

    //提取檔案的絕對路徑
    path := list[1]

    //獲取檔案屬性
    fileInfo, err := os.Stat(path)
    if err != nil {
        fmt.Printf("os.Stat()函式執行出錯,錯誤為:%v\n", err)
        return
    }

    //主動發起連線請求
    conn, err := net.Dial("tcp", "127.0.0.1:8000")
    if err != nil {
        fmt.Printf("net.Dial()函式執行出錯,錯誤為:%v\n", err)
        return
    }
    defer conn.Close()

    //傳送檔名給接收端
    _, err = conn.Write([]byte(fileInfo.Name()))

    //讀取伺服器回發資料
    buf := make([]byte, 4096)
    n, err := conn.Read(buf)
    if err != nil {
        fmt.Printf("conn.Read(buf)方法執行出錯,錯誤為:%v\n", err)
        return
    }

    if string(buf[:n]) == "ok" {
        //寫檔案內容給伺服器 -- 藉助conn
        sendFile(conn, path)
    }
}