手撸了一个文件传输工具

在日常的开发与运维中,文件传输工具是不可或缺的利器。无论是跨服务器传递配置文件,还是快速从一台机器下载日志文件,一个高效、可靠且简单的文件传输工具能够显著提高工作效率。今天,我想分享我自己手撸一个文件传输工具的全过程,包括需求场景、技术点分析以及实现的编码过程。

为什么要搞这个?

在我的日常学习和工作中,经常遇到以下几个需求场景:

1)跨服务器的文件共享:因为我们目前有两台服务器,有时需要在服务器之间同步配置文件或共享资源。

2)简单的文件传输:对于某些单文件传输的任务,现有工具配置较复杂,使用成本较高。希望能有一个简易的工具,不依赖复杂的配置和额外的库。

3)文件列表管理与下载:需要方便地列出文件存储目录中的内容,并支持选择性下载某些文件。

技术点分析

为了满足上述需求,我需要实现一个功能完整的文件传输工具。以下是一些关键技术点和设计思路:

TCP通信

使用 TCP 协议构建文件传输工具,确保传输的可靠性。实现客户端和服务器的双向通信,用于文件传输、列表查询等功能。

文件传输机制

通过 TCP 套接字发送和接收文件内容。客户端在上传文件时需要传递文件元信息(文件名和大小),以便服务器正确创建文件。

命令解析

支持多种命令(如 LISTUPLOADDOWNLOAD),让工具更易扩展。

进度条展示

使用第三方库 pb 在终端展示传输进度条,提升用户体验。

工具开发过程

接下来,我将分享从零开始实现这个工具的完整过程。

首先的整体的机制:服务器监听一个固定的端口,接受客户端的 TCP 连接。根据客户端发送的命令执行不同的操作,在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命令代码实现

先定义命令参数和默认值,比如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}// 创建文件f, err := os.Create(fullPath)if err != nil {fmt.Println("Error creating file:", err)return}defer func() {_ = f.Close()}()// 创建进度条bar := pb.Start64(int64(fileSize))bar.Set(pb.Bytes, true)defer bar.Finish()// 读取数据并写入文件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 
后续会考虑支持的功能

1)文件列表功能:通过读取服务器的文件保存目录,列出所有文件。如使用 LIST 命令返回文件名列表。

2)文件下载功能:客户端通过 DOWNLOAD <filename> 命令请求文件。服务端确认文件存在后,先发送文件大小,然后传输文件内容。

文件传输客户端

客户端主要就是能够接收命令参数找到服务端、指定自己要上传的文件,然后就是支持文件上传。

参数定义:

server为将要上传的服务端地址,file为要进行上传的文件名次,action参数为客户端的操作,如LIST、DOWNLOAD等,后续会进行扩展。

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()}()// 发送文件元信息(文件名|文件大小)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}// 创建进度条bar := pb.Start64(fileInfo.Size())bar.Set(pb.Bytes, true)defer bar.Finish()// 发送文件数据proxyWriter := bar.NewProxyWriter(conn)if _, err = io.Copy(proxyWriter, f); err != nil {fmt.Println("Error sending file:", err)return}fmt.Println("File sent successfully!")
}

实现效果

1)工具编译

拿到代码后,我们需要在相关操作系统编译成可执行文件,需要go1.23以上的版本,命令:

go mod tidy go build

2)启动服务端

最简单的启动方式就是直接使用默认参数:

./FTransferor server

当然也可以指定参数,比如指定端口号为9910,文件的保存路径为当前目录下的yankaka目录,如果没有该目录会自动创建:

./FTransferor server --port 9910 --path yankaka

3)使用客户端

使用客户端就需要输入必要的参数了,因为TCP是点对点链接,客户端必须要有一个连接目标,下面就上传一个文件看下效果:

./FTransferor cli --server yankaka.chat:9910 --file go1.23.3.linux-amd64.tar.gz 

此时会显示一个进度条,就是文件上传的进度和上传速度:

服务端也会显示,并在上传成功后有提示:

上传完成后看下目标目录的相关文件是否存在:

发现是存在并且正常的,至此我们这款文件上传工具的基本功能就已经实现了!

收获与总结

通过手撸这个文件传输工具,我对TCP编程有了更深刻的理解,其实有些功能并不需要利用HTTP、RPC等上层协议进行实现,更别说各种各样的框架了,最简单的功能往往TCP协议就足够了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/482633.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java Springboot电子书阅读器APP且微信小程序

一、作品包含 源码数据库全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 微信开发者工具 数…

【AI系统】AI 编译器基本架构

AI 编译器基本架构 在上一篇文章中将 AI 编译器的发展大致分为了 3 个阶段&#xff0c;分别为 1&#xff09;朴素编译器、2&#xff09;专用编译器以及 3&#xff09;通用编译器。 本文作为上一篇文章 AI 编译器架构的一个延续&#xff0c;着重讨论 AI 编译器的通用架构。首先…

华为关键词覆盖应用市场ASO优化覆盖技巧

在我国的消费者群体当中&#xff0c;华为的品牌形象较高&#xff0c;且产品质量过硬&#xff0c;因此用户基数也大。与此同时&#xff0c;随着影响力的增大&#xff0c;华为不断向外扩张&#xff0c;也逐渐成为了海外市场的香饽饽。作为开发者和运营者&#xff0c;我们要认识到…

SuperMap GIS基础产品FAQ集锦(20241202)

一、SuperMap iDesktopX 问题1&#xff1a;请问一下&#xff0c;iDesktopX11.2.1如何修改启动界面 11.2.0 【解决办法】参考帮助文档的“自定义启动界面”内容&#xff1a;https://help.supermap.com/iDesktopX/zh/SpecialFeatures/Development/DevelopmentTutorial/UserCust…

Java基础访问修饰符全解析

一、Java 访问修饰符概述 Java 中的访问修饰符用于控制类、方法、变量和构造函数的可见性和访问权限&#xff0c;主要有四种&#xff1a;public、protected、default&#xff08;无修饰符&#xff09;和 private。 Java 的访问修饰符在编程中起着至关重要的作用&#xff0c;它…

浪潮X86服务器NF5280、8480、5468、5270使用inter VROC Raid key给NVME磁盘做阵列

Inter VROC技术简介 Intel Virtual RAID on CPU (Intel VROC) 简单来说就是用CPU的PCIE通道给NVME硬盘做Raid 更多信息可以访问官方支持页面 Raid Key 授权&#xff0c;即VROC SKU 授权主要有用的有2个标准和高级&#xff0c;仅Raid1的授权我暂时没见过。 标准 VROCSTANMOD …

【Pytorch】torch.view与torch.reshape的区别

文章目录 一. 简介&#xff1a;二. Pytorch中Tensor的存储方式2.1 Pytorch中张量存储的底层原理2.2 Pytorch张量步长(stride)属性 三. 对视图(view)的理解四. view()与reshape()的比较4.1 对view()的理解4.1.1 &#xff08;1&#xff09;如何理解满足条件 stride[i] stride[i1…

光伏电站设计排布前的准备

1、确定安装地点 地理位置&#xff1a;了解安装地点的经纬度&#xff0c;这对于确定太阳辐射角度和强度非常重要&#xff0c;海拔越高&#xff0c;阳光辐照就越高&#xff0c;比较适合安装光伏电站&#xff0c;根据地理位置还可以确定光伏板的安装倾角是多少&#xff0c;可以进…

5、防火墙一

防火墙的含义 firewalld&#xff1a;隔离功能 病毒防护&#xff1a; 1、入侵检测系统&#xff1a;在互联网访问的过程中&#xff0c;不阻断任何网络访问&#xff0c;也不会定位网络的威胁&#xff0c;提供告警和事后的监督&#xff0c;类似于监控。 2、入侵防御系统&#x…

代码随想录算法训练营第六十天|Day60 图论

Bellman_ford 队列优化算法&#xff08;又名SPFA&#xff09; https://www.programmercarl.com/kamacoder/0094.%E5%9F%8E%E5%B8%82%E9%97%B4%E8%B4%A7%E7%89%A9%E8%BF%90%E8%BE%93I-SPFA.html 本题我们来系统讲解 Bellman_ford 队列优化算法 &#xff0c;也叫SPFA算法&#xf…

详解LZ4文件解压缩问题

详解LZ4文件解压缩问题 一、LZ4文件解压缩方法1. 使用LZ4命令行工具2. 使用Python库3. 使用第三方工具4. 在线解压工具 二、常见问题及解决方法1. 解压显示文件损坏2. 解压后文件大小异常 三、总结 LZ4是一种快速的压缩算法&#xff0c;广泛应用于需要实时压缩和解压缩大文件的…

【Linux网络编程】第四弹---构建UDP服务器与字典翻译系统:源码结构与关键组件解析

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【Linux网络编程】 目录 1、UdpServer.hpp 1.1、函数对象声明 1.2、Server类基本结构 1.3、构造函数 1.4、Start() 2、Dict.hpp…

DBA面试题-1

面临失业&#xff0c;整理一下面试题&#xff0c;找下家继续搬砖 主要参考&#xff1a;https://www.csdn.net/?spm1001.2101.3001.4476 略有修改 一、mysql有哪些数据类型 1&#xff0c; 整形 tinyint,smallint,medumint,int,bigint&#xff1b;分别占用1字节、2字节、3字节…

vxe-table 树形表格序号的使用

vxe-table 树形结构支持多种方式的序号&#xff0c;可以及时带层级的序号&#xff0c;也可以是自增的序号。 官网&#xff1a;https://vxetable.cn 带层级序号 <template><div><vxe-grid v-bind"gridOptions"></vxe-grid></div> <…

精通.NET鉴权与授权

授权在.NET 中是指确定经过身份验证的用户是否有权访问特定资源或执行特定操作的过程。这就好比一个公司&#xff0c;身份验证(鉴权)是检查你是不是公司的员工&#xff0c;而授权则是看你这个员工有没有权限进入某个特定的办公室或者使用某台设备。 两个非常容易混淆的单词 鉴…

Spring Task和WebSocket使用

在现代 Web 应用中&#xff0c;WebSocket 作为一种全双工通信协议&#xff0c;为实时数据传输提供了强大的支持。若要确保 WebSocket 在生产环境中的稳定性和性能&#xff0c;使用 Nginx 作为反向代理服务器是一个明智的选择。本篇文章将带你了解如何在 Nginx 中配置 WebSocket…

机器学习任务功略

loss如果大&#xff0c;训练资料没有学好&#xff0c;此时有两个可能&#xff1a; 1.model bias太过简单&#xff08;找不到loss低的function&#xff09;。 解决办法&#xff1a;增加输入的feacture&#xff0c;设一个更大的model&#xff0c;也可以用deep learning增加弹性…

STL:相同Size大小的vector和list哪个占用空间多?

在C中&#xff0c;vector和list是两种不同的序列容器。vector底层是连续的内存&#xff0c;而list是非连续的&#xff0c;分散存储的。因此&#xff0c;vector占用的空间更多&#xff0c;因为它需要为存储的元素分配连续的内存空间。 具体占用多少空间&#xff0c;取决于它们分…

Windows 10电脑无声问题的全面解决方案

Windows 10操作系统以其强大的功能和用户友好的界面赢得了广大用户的青睐&#xff0c;但在使用过程中&#xff0c;有时会遇到电脑突然没有声音的问题。这一问题可能由多种原因引起&#xff0c;包括音频驱动程序问题、音频设置错误、系统更新冲突等。本文将详细介绍Windows 10无…

6.824/6.5840 Lab 1: Lab 3: Raft

漆昼中温柔的不像话 静守着他的遗憾啊 旧的摇椅吱吱呀呀停不下 风卷走了满院的落叶落花 ——暮色回响 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 在完成Lab之前&#xff0c;务必把论文多读几遍&#xff0c;力求完全理解Leader选举、log日志等过程。 …