Go網路檔案傳輸
阿新 • • 發佈:2020-01-07
流程分析
藉助TCP完成檔案的傳輸,基本思路如下:
- 傳送方(客戶端)向服務端傳送檔名,服務端儲存該檔名。
- 接收方(服務端)向客戶端返回一個訊息ok,確認檔名儲存成功。
- 傳送方(客戶端)收到訊息後,開始向服務端傳送檔案資料。
- 接收方(服務端)讀取檔案內容,寫入到之前儲存好的檔案中。
由於檔案傳輸需要穩定可靠的連線,所以採用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實現檔案傳輸大致步驟可以歸結為如下步驟
接收端:
- 建立監聽 listener,程式結束時關閉。
- 阻塞等待客戶端連線 conn,程式結束時關閉conn。
- 讀取客戶端傳送檔名。儲存 fileName。
- 回發“ok”。
- 封裝函式 RecvFile 接收客戶端傳送的檔案內容。傳參 fileName 和 conn
- 按檔名 Create 檔案,結束時 Close
- 迴圈 Read 傳送端網路檔案內容,當讀到 0 說明檔案讀取完畢。
- 將讀到的內容原封不動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) }
傳送端:
- 提示使用者使用命令列引數輸入檔名。接收檔名 filepath(含訪問路徑)
- 使用 os.Stat()獲取檔案屬性,得到純檔名 fileName(去除訪問路徑)
- 主動發起連線伺服器請求,結束時關閉連線。
- 傳送檔名到接收端 conn.Write()
- 讀取接收端回發的確認資料 conn.Read()
- 判斷是否為“ok”。如果是,封裝函式 SendFile() 傳送檔案內容。傳參 filePath 和 conn
- 只讀 Open 檔案, 結束時Close檔案
- 迴圈讀本地檔案,讀到 EOF,讀取完畢。
- 將讀到的內容原封不動 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)
}
}