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)
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM