天天看點

go語言基于tcp協定的網絡檔案傳輸

go語言基于tcp協定的網絡檔案傳輸

流程分析

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

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

由于檔案傳輸需要穩定可靠的連接配接,是以采用TCP方式完成網絡檔案傳輸功能。

go語言基于tcp協定的網絡檔案傳輸

首先擷取檔案名。借助os包中的stat()函數來擷取檔案屬性資訊。在函數傳回的檔案屬性中包含檔案名和檔案大小。Stat參數name傳入的是檔案通路的絕對路徑。FileInfo中的Name()函數可以将檔案名單獨提取出來。

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

Stat傳回一個描述name指定的檔案對象的FileInfo。如果指定的檔案對象是一個符号連結,傳回的FileInfo描述該符号連結指向的檔案的資訊,本函數會嘗試跳轉該連結。如果出錯,傳回的錯誤值為*PathError類型。

我們通過源碼可以得知FileInfo是一個接口,要實作這個接口就必須實作這個接口的如下所有方法

go語言基于tcp協定的網絡檔案傳輸

實作網絡檔案傳輸實質上時借助了本地檔案複制和TCP網絡程式設計相關知識,可以先看看Go語言複制檔案和Go網絡程式設計了解相關内容。

是以關于使用TCP實作檔案傳輸大緻步驟可以歸結為如下步驟

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

繼續閱讀