流程分析
借助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)
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/51201.html
標籤:Go
上一篇:Mac Go 環境變數配置
下一篇:Go并發編程
