做網站制作云南網站seo服務
在日常的開發(fā)與運維中,文件傳輸工具是不可或缺的利器。無論是跨服務器傳遞配置文件,還是快速從一臺機器下載日志文件,一個高效、可靠且簡單的文件傳輸工具能夠顯著提高工作效率。今天,我想分享我自己手擼一個文件傳輸工具的全過程,包括需求場景、技術點分析以及實現(xiàn)的編碼過程。
為什么要搞這個?
在我的日常學習和工作中,經常遇到以下幾個需求場景:
1)跨服務器的文件共享:因為我們目前有兩臺服務器,有時需要在服務器之間同步配置文件或共享資源。
2)簡單的文件傳輸:對于某些單文件傳輸?shù)娜蝿?#xff0c;現(xiàn)有工具配置較復雜,使用成本較高。希望能有一個簡易的工具,不依賴復雜的配置和額外的庫。
3)文件列表管理與下載:需要方便地列出文件存儲目錄中的內容,并支持選擇性下載某些文件。
技術點分析
為了滿足上述需求,我需要實現(xiàn)一個功能完整的文件傳輸工具。以下是一些關鍵技術點和設計思路:
TCP通信
使用 TCP 協(xié)議構建文件傳輸工具,確保傳輸?shù)目煽啃?。實現(xiàn)客戶端和服務器的雙向通信,用于文件傳輸、列表查詢等功能。
文件傳輸機制
通過 TCP 套接字發(fā)送和接收文件內容??蛻舳嗽谏蟼魑募r需要傳遞文件元信息(文件名和大小),以便服務器正確創(chuàng)建文件。
命令解析
支持多種命令(如 LIST
、UPLOAD
和 DOWNLOAD
),讓工具更易擴展。
進度條展示
使用第三方庫 pb
在終端展示傳輸進度條,提升用戶體驗。
工具開發(fā)過程
接下來,我將分享從零開始實現(xiàn)這個工具的完整過程。
首先的整體的機制:服務器監(jiān)聽一個固定的端口,接受客戶端的 TCP 連接。根據(jù)客戶端發(fā)送的命令執(zhí)行不同的操作,在Upload操作時可以指定操作類型,比如查詢文件列表等。
文件傳輸服務端
需要支持命令啟動,而且在啟動時能夠指定端口號、文件保存的目錄等,所以就需要使用Go語言的cobra插件。
main.go文件
var rootCmd = &cobra.Command{Use: "",Run: func(cmd *cobra.Command, args []string) {fmt.Println("Running myapp...")},
}func main() {rootCmd.AddCommand(ClientCmd())rootCmd.AddCommand(ServerCmd())if err := rootCmd.Execute(); err != nil {panic(err)}
}
server命令代碼實現(xiàn)
先定義命令參數(shù)和默認值,比如port, webport
代表要啟動的TCP服務端口號和Web服務端口號,path, secret
代表文件的保存路徑和將來可能要做安全機制的密碼功能。
const (DefaultPathDir = "tmp"DefaultServerPort = 8088DefaultWebPort = 8089
)var (port, webport intpath, secret string
)
主要方法:
func ServerCmd() *cobra.Command {command := &cobra.Command{Use: "server",Run: func(cmd *cobra.Command, args []string) {quit := make(chan os.Signal, 1)signal.Notify(quit, os.Interrupt, syscall.SIGTERM)initCommand()go func() {runHttpServer()runServer(port, path)}()<-quit},}command.Flags().StringVarP(&path, "path", "", "", "path to serve")command.Flags().StringVarP(&secret, "secret", "", "", "path to serve")command.Flags().IntVarP(&port, "port", "", 0, "path to serve")command.Flags().IntVarP(&webport, "webport", "", 0, "path to serve")return command
}
支持TCP文件上傳的主要邏輯:
func runServer(port int, savePath string) {listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))if err != nil {fmt.Println("Error starting server:", err)return}defer func() {_ = listener.Close()}()fmt.Println("Server is listening on port", port)for {conn, err := listener.Accept()if err != nil {fmt.Println("Connection error:", err)continue}go handleConnection(conn, savePath)}
}func handleConnection(conn net.Conn, savePath string) {defer func() {_ = conn.Close()}()reader := bufio.NewReader(conn)// 讀取文件元信息:文件名和文件大小meta, err := reader.ReadString('\n')if err != nil {fmt.Println("Error reading file metadata:", err)return}meta = strings.TrimSpace(meta) // 清除換行符parts := strings.Split(meta, "|")if len(parts) != 2 {fmt.Println("Invalid metadata received")return}fileName := parts[0]fileSize := 0_, err = fmt.Sscanf(parts[1], "%d", &fileSize)if err != nil {fmt.Println("Error parsing file size:", err)return}// 確保保存路徑存在fullPath := filepath.Join(savePath, fileName)if err = os.MkdirAll(filepath.Dir(fullPath), 0755); err != nil {fmt.Println("Error creating directories:", err)return}// 創(chuàng)建文件f, err := os.Create(fullPath)if err != nil {fmt.Println("Error creating file:", err)return}defer func() {_ = f.Close()}()// 創(chuàng)建進度條bar := pb.Start64(int64(fileSize))bar.Set(pb.Bytes, true)defer bar.Finish()// 讀取數(shù)據(jù)并寫入文件proxyReader := bar.NewProxyReader(reader)if _, err = io.Copy(f, proxyReader); err != nil {fmt.Println("Error saving file:", err)return}fmt.Println("File received:", fullPath)
}
啟動服務端:
go build ./FTransferor server --path filepath --port 8081
后續(xù)會考慮支持的功能
1)文件列表功能:通過讀取服務器的文件保存目錄,列出所有文件。如使用 LIST
命令返回文件名列表。
2)文件下載功能:客戶端通過 DOWNLOAD <filename>
命令請求文件。服務端確認文件存在后,先發(fā)送文件大小,然后傳輸文件內容。
文件傳輸客戶端
客戶端主要就是能夠接收命令參數(shù)找到服務端、指定自己要上傳的文件,然后就是支持文件上傳。
參數(shù)定義:
server
為將要上傳的服務端地址,file
為要進行上傳的文件名次,action
參數(shù)為客戶端的操作,如LIST、DOWNLOAD等,后續(xù)會進行擴展。
var (server, file, action string
)
主要的client方法:
func ClientCmd() *cobra.Command {command := &cobra.Command{Use: "cli",Run: func(cmd *cobra.Command, args []string) {startClient(server, file)},}command.Flags().StringVarP(&server, "server", "", "", "path to serve")command.Flags().StringVarP(&file, "file", "", "", "path to serve")command.Flags().StringVarP(&action, "action", "", "", "path to serve")return command
}
文件上傳方法:
func startClient(serverAddr string, filePath string) {f, err := os.Open(filePath)if err != nil {fmt.Println("Error opening file:", err)return}defer func() {_ = f.Close()}()// 獲取文件信息fileInfo, err := f.Stat()if err != nil {fmt.Println("Error getting file info:", err)return}conn, err := net.Dial("tcp", serverAddr)if err != nil {fmt.Println("Error connecting to server:", err)return}defer func() {_ = conn.Close()}()// 發(fā)送文件元信息(文件名|文件大小)meta := fmt.Sprintf("%s|%d\n", fileInfo.Name(), fileInfo.Size())if _, err = conn.Write([]byte(meta)); err != nil {fmt.Println("Error sending metadata:", err)return}// 創(chuàng)建進度條bar := pb.Start64(fileInfo.Size())bar.Set(pb.Bytes, true)defer bar.Finish()// 發(fā)送文件數(shù)據(jù)proxyWriter := bar.NewProxyWriter(conn)if _, err = io.Copy(proxyWriter, f); err != nil {fmt.Println("Error sending file:", err)return}fmt.Println("File sent successfully!")
}
實現(xiàn)效果
1)工具編譯
拿到代碼后,我們需要在相關操作系統(tǒng)編譯成可執(zhí)行文件,需要go1.23以上的版本,命令:
go mod tidy go build
2)啟動服務端
最簡單的啟動方式就是直接使用默認參數(shù):
./FTransferor server
當然也可以指定參數(shù),比如指定端口號為9910,文件的保存路徑為當前目錄下的yankaka目錄,如果沒有該目錄會自動創(chuàng)建:
./FTransferor server --port 9910 --path yankaka
3)使用客戶端
使用客戶端就需要輸入必要的參數(shù)了,因為TCP是點對點鏈接,客戶端必須要有一個連接目標,下面就上傳一個文件看下效果:
./FTransferor cli --server yankaka.chat:9910 --file go1.23.3.linux-amd64.tar.gz
此時會顯示一個進度條,就是文件上傳的進度和上傳速度:
服務端也會顯示,并在上傳成功后有提示:
上傳完成后看下目標目錄的相關文件是否存在:
發(fā)現(xiàn)是存在并且正常的,至此我們這款文件上傳工具的基本功能就已經實現(xiàn)了!
收獲與總結
通過手擼這個文件傳輸工具,我對TCP編程有了更深刻的理解,其實有些功能并不需要利用HTTP、RPC等上層協(xié)議進行實現(xiàn),更別說各種各樣的框架了,最簡單的功能往往TCP協(xié)議就足夠了。