手撸了一个文件传输工具

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

为什么要搞这个?

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

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.mzph.cn/bicheng/62796.shtml

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

相关文章

Linux系统编程之进程创建

概述 在Linux系统中&#xff0c;通过创建新的进程&#xff0c;我们可以实现多任务处理、并发执行和资源隔离等功能。创建进程的主要方法为&#xff1a;fork、vfork、clone。下面&#xff0c;我们将分别进行介绍。 fork fork是最常用的创建新进程的方法。当一个进程调用fork时&a…

【人工智能】用Python实现卷积神经网络(CNN)进行图像分类:从零开始的深度学习教程

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 卷积神经网络(CNN)是处理图像分类任务的核心工具,它通过卷积操作和池化机制提取图像的特征并实现分类。本文将手把手教你如何使用 Python 和深度学习框架(PyTorch)从头实现一个 CNN 模型,应用于图像…

深入探讨NIO

目录 传统阻塞IO 非阻塞IO select() epoll 总结 传统阻塞IO 非阻塞IO IO多路复用select() IO多路复用epoll 传统阻塞IO 在传统的阻塞IO模型中&#xff0c;当一个线程执行到IO操作&#xff08;如读取数据&#xff09;时&#xff0c;如果数据尚未准备好&#xff0c;它会…

新手参加2025年CTF大赛——Web题目的基本解题流程

CTF&#xff08;Capture the Flag&#xff09;是网络安全比赛中的一种常见形式&#xff0c;参赛者需要通过破解题目、发现漏洞并获取flag&#xff08;标志&#xff09;来获得分数。 这些问题涉及多个领域&#xff0c;如逆向工程、Web安全、密码学、二进制漏洞、取证分析等。CTF…

1Panel 自建邮局 - Docker Mailserver

本文首发于 Anyeの小站&#xff0c;点击链接 访问体验更佳 前言 首先发一段劝退说辞&#xff1a;我相信点进本文的人自建邮局的目的更多地是为了能用自己的域名邮箱&#xff0c;收发邮件&#xff1f; 仅收不发&#xff0c;推荐使用 https://www.cloudflare.com/zh-cn/develop…

QT-thread2种方式选择的优劣对比

1.第一种方式&#xff1a;使用 QObject 的 moveToThread() QObjectQthread class MessageWriter : public QObject {Q_OBJECT public slots:void writeDataToFile(); };threadMsgExchange new QThread();MessageWriter *writer new MessageWriter();writer->moveToThread…

【Maven】功能和核心概念

1. 什么是Maven 1.1 Maven的概念 Maven 是 Apache 软件基金会组织维护的一款自动化构建工具&#xff0c;专注服务于 Java 平台的项目构建和依赖管理。 1.2 为什么要使用Maven&#xff1f; 在项目开发中&#xff0c;我们需要引用各种 jar 包&#xff0c;引用的 jar 包可能有…

Go运行Grule引擎实现计费规则管理

Go运行Grule引擎实现计费规则管理 github位置: https://github.com/hyperjumptech/grule-rule-engine # 安装grule模块 go get -u github.com/hyperjumptech/grule-rule-engineGrule的示例代码 示例位置: https://github.com/hyperjumptech/grule-rule-engine/tree/master/e…

企业网站面临的爬虫攻击及安全防护策略

在当今数字化时代&#xff0c;企业网站不仅是展示企业形象的窗口&#xff0c;更是进行商业活动的重要平台。然而&#xff0c;企业网站在日常运营中面临着多种类型的爬虫攻击&#xff0c;这些攻击不仅会对网站的正常访问造成影响&#xff0c;还可能窃取敏感数据&#xff0c;给企…

Hive on Spark 的Pre-commit 测试

什么是 Pre-Commit 测试&#xff1f; Pre-Commit 测试是一种提交代码到主分支或共享代码库之前运行的一系列自动化测试&#xff0c;用于捕获代码中的潜在问题自动运行的测试流程。其目的是确保新提交的代码不会引入错误&#xff0c;破坏现有功能或降低代码质量。对于大型项目如…

android shader gl_Position是几个分量

在Android的OpenGL ES中&#xff0c;gl_Position是顶点着色器&#xff08;Vertex Shader&#xff09;的一个内置输出变量&#xff0c;它用于指定顶点在裁剪空间&#xff08;Clip Space&#xff09;中的位置。gl_Position是一个四维向量&#xff08;4-component vector&#xff…

【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(6)

1.问题描述&#xff1a; 推送通知到手机&#xff0c;怎么配置拉起应用指定的页面&#xff1f; 解决方案&#xff1a; 1、如果点击通知栏打开默认Ability的话&#xff0c; actionType可以设置为0&#xff0c; 同时可以在.clickAction.data中&#xff0c;指定待跳转的page页面…

vue3 + vite + antdv 项目中自定义图标

前言&#xff1a; 去iconfont-阿里巴巴矢量图标库 下载自己需要的icon图标&#xff0c;下载格式为svg&#xff1b;项目中在存放静态资源的文件夹下 assets 创建一个存放svg格式的图片的文件夹。 步骤&#xff1a; 1、安装vite-plugin-svg-icons npm i vite-plugin-svg-icons …

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本

安装SQL Server 2022提示需要Microsoft .NET Framework 4.7.2 或更高版本。 原因是&#xff1a;当前操作系统版本为Windows Server 2016 Standard版本&#xff0c;其自带的Microsoft .NET Framework 版本为4.6太低&#xff0c;不满足要求。 根据报错的提示&#xff0c;点击链接…

学习笔记039——SpringBoot整合Redis

文章目录 1、Redis 基本操作Redis 默认有 16 个数据库&#xff0c;使用的是第 0 个&#xff0c;切换数据库添加数据/修改数据查询数据批量添加批量查询删除数据查询所有的 key清除当前数据库清除所有数据库查看 key 是否存在设置有效期查看有效期 2、Redis 数据类型String追加字…

基于yolov8、yolov5的铝材缺陷检测识别系统(含UI界面、训练好的模型、Python代码、数据集)

摘要&#xff1a;铝材缺陷检测在现代工业生产和质量管理中具有重要意义&#xff0c;不仅能帮助企业实时监控铝材质量&#xff0c;还为智能化生产系统提供了可靠的数据支撑。本文介绍了一款基于YOLOv8、YOLOv5等深度学习框架的铝材缺陷检测模型&#xff0c;该模型使用了大量包含…

如何在 VPS 上使用 Git 设置自动部署

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 介绍 要了解 Git 的基本知识以及如何安装&#xff0c;请参考介绍教程。 本文将教你如何在部署应用程序时使用 Git。虽然有许多使用 Gi…

Goland或Idea启动报错

Goland或Idea启动不了 报错如图&#xff1a; 原因&#xff1a;破解导致 解决方案 环境变量中有关Goland的全部删除

ceph手动部署

ceph手动部署 一、 节点规划 主机名IP地址角色ceph01.example.com172.18.0.10/24mon、mgr、osd、mds、rgwceph02.example.com172.18.0.20/24mon、mgr、osd、mds、rgwceph03.example.com172.18.0.30/24mon、mgr、osd、mds、rgw 操作系统版本&#xff1a; Rocky Linux release …

C#基础之方法

文章目录 1 方法1.1 定义方法1.2 参数传递1.2.1 按值传递参数1.2.2 按引用传递参数1.2.3 按输出传递参数1.2.4 可变参数 params1.2.5 具名参数1.2.6 可选参数 1.3 匿名方法1.3.1 Lambda 表达式1.3.1.1 定义1.3.1.2 常用类型1.3.1.3 Lambda 表达式与 LINQ1.3.1.4 Lambda 表达式的…