做網(wǎng)站制作云南網(wǎng)站seo服務(wù)
在日常的開發(fā)與運(yùn)維中,文件傳輸工具是不可或缺的利器。無論是跨服務(wù)器傳遞配置文件,還是快速從一臺(tái)機(jī)器下載日志文件,一個(gè)高效、可靠且簡單的文件傳輸工具能夠顯著提高工作效率。今天,我想分享我自己手?jǐn)]一個(gè)文件傳輸工具的全過程,包括需求場景、技術(shù)點(diǎn)分析以及實(shí)現(xiàn)的編碼過程。
為什么要搞這個(gè)?
在我的日常學(xué)習(xí)和工作中,經(jīng)常遇到以下幾個(gè)需求場景:
1)跨服務(wù)器的文件共享:因?yàn)槲覀兡壳坝袃膳_(tái)服務(wù)器,有時(shí)需要在服務(wù)器之間同步配置文件或共享資源。
2)簡單的文件傳輸:對(duì)于某些單文件傳輸?shù)娜蝿?wù),現(xiàn)有工具配置較復(fù)雜,使用成本較高。希望能有一個(gè)簡易的工具,不依賴復(fù)雜的配置和額外的庫。
3)文件列表管理與下載:需要方便地列出文件存儲(chǔ)目錄中的內(nèi)容,并支持選擇性下載某些文件。
技術(shù)點(diǎn)分析
為了滿足上述需求,我需要實(shí)現(xiàn)一個(gè)功能完整的文件傳輸工具。以下是一些關(guān)鍵技術(shù)點(diǎn)和設(shè)計(jì)思路:
TCP通信
使用 TCP 協(xié)議構(gòu)建文件傳輸工具,確保傳輸?shù)目煽啃?。?shí)現(xiàn)客戶端和服務(wù)器的雙向通信,用于文件傳輸、列表查詢等功能。
文件傳輸機(jī)制
通過 TCP 套接字發(fā)送和接收文件內(nèi)容??蛻舳嗽谏蟼魑募r(shí)需要傳遞文件元信息(文件名和大小),以便服務(wù)器正確創(chuàng)建文件。
命令解析
支持多種命令(如 LIST
、UPLOAD
和 DOWNLOAD
),讓工具更易擴(kuò)展。
進(jìn)度條展示
使用第三方庫 pb
在終端展示傳輸進(jìn)度條,提升用戶體驗(yàn)。
工具開發(fā)過程
接下來,我將分享從零開始實(shí)現(xiàn)這個(gè)工具的完整過程。
首先的整體的機(jī)制:服務(wù)器監(jiān)聽一個(gè)固定的端口,接受客戶端的 TCP 連接。根據(jù)客戶端發(fā)送的命令執(zhí)行不同的操作,在Upload操作時(shí)可以指定操作類型,比如查詢文件列表等。
文件傳輸服務(wù)端
需要支持命令啟動(dòng),而且在啟動(dòng)時(shí)能夠指定端口號(hào)、文件保存的目錄等,所以就需要使用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命令代碼實(shí)現(xiàn)
先定義命令參數(shù)和默認(rèn)值,比如port, webport
代表要啟動(dòng)的TCP服務(wù)端口號(hào)和Web服務(wù)端口號(hào),path, secret
代表文件的保存路徑和將來可能要做安全機(jī)制的密碼功能。
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)建進(jìn)度條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)
}
啟動(dòng)服務(wù)端:
go build ./FTransferor server --path filepath --port 8081
后續(xù)會(huì)考慮支持的功能
1)文件列表功能:通過讀取服務(wù)器的文件保存目錄,列出所有文件。如使用 LIST
命令返回文件名列表。
2)文件下載功能:客戶端通過 DOWNLOAD <filename>
命令請(qǐng)求文件。服務(wù)端確認(rèn)文件存在后,先發(fā)送文件大小,然后傳輸文件內(nèi)容。
文件傳輸客戶端
客戶端主要就是能夠接收命令參數(shù)找到服務(wù)端、指定自己要上傳的文件,然后就是支持文件上傳。
參數(shù)定義:
server
為將要上傳的服務(wù)端地址,file
為要進(jìn)行上傳的文件名次,action
參數(shù)為客戶端的操作,如LIST、DOWNLOAD等,后續(xù)會(huì)進(jìn)行擴(kuò)展。
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)建進(jìn)度條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!")
}
實(shí)現(xiàn)效果
1)工具編譯
拿到代碼后,我們需要在相關(guān)操作系統(tǒng)編譯成可執(zhí)行文件,需要go1.23以上的版本,命令:
go mod tidy go build
2)啟動(dòng)服務(wù)端
最簡單的啟動(dòng)方式就是直接使用默認(rèn)參數(shù):
./FTransferor server
當(dāng)然也可以指定參數(shù),比如指定端口號(hào)為9910,文件的保存路徑為當(dāng)前目錄下的yankaka目錄,如果沒有該目錄會(huì)自動(dòng)創(chuàng)建:
./FTransferor server --port 9910 --path yankaka
3)使用客戶端
使用客戶端就需要輸入必要的參數(shù)了,因?yàn)門CP是點(diǎn)對(duì)點(diǎn)鏈接,客戶端必須要有一個(gè)連接目標(biāo),下面就上傳一個(gè)文件看下效果:
./FTransferor cli --server yankaka.chat:9910 --file go1.23.3.linux-amd64.tar.gz
此時(shí)會(huì)顯示一個(gè)進(jìn)度條,就是文件上傳的進(jìn)度和上傳速度:
服務(wù)端也會(huì)顯示,并在上傳成功后有提示:
上傳完成后看下目標(biāo)目錄的相關(guān)文件是否存在:
發(fā)現(xiàn)是存在并且正常的,至此我們這款文件上傳工具的基本功能就已經(jīng)實(shí)現(xiàn)了!
收獲與總結(jié)
通過手?jǐn)]這個(gè)文件傳輸工具,我對(duì)TCP編程有了更深刻的理解,其實(shí)有些功能并不需要利用HTTP、RPC等上層協(xié)議進(jìn)行實(shí)現(xiàn),更別說各種各樣的框架了,最簡單的功能往往TCP協(xié)議就足夠了。