当前位置: 首页 > news >正文

手撸了一个文件传输工具

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

为什么要搞这个?

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

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协议就足够了。

相关文章:

手撸了一个文件传输工具

在日常的开发与运维中&#xff0c;文件传输工具是不可或缺的利器。无论是跨服务器传递配置文件&#xff0c;还是快速从一台机器下载日志文件&#xff0c;一个高效、可靠且简单的文件传输工具能够显著提高工作效率。今天&#xff0c;我想分享我自己手撸一个文件传输工具的全过程…...

Java程序调kubernetes(k8s1.30.7)core API简单示例,并解决403权限验证问题,即何进行进行权限授权以及验证

简单记录问题 一、问题描述 希望通过Java程序使用Kubernetes提供的工具包实现对Kubernetes集群core API的调用&#xff0c;但是在高版本上遇见权限验证问题4xx。 <dependency><groupId>io.kubernetes</groupId><artifactId>client-java</artifact…...

java八股-Redis Stream和RocketMQ实现的解决方案

文章目录 Redis Stream方案&#xff1a;ShortLinkStatsSaveProducer.javaShortLinkStatsSaveConsumer.java RocketMQ方案ShortLinkStatsSaveProducer.javaShortLinkStatsSaveConsumer.java Redis Stream方案&#xff1a; ShortLinkStatsSaveProducer.java package com.nageoff…...

第29天 MCU入门

目录 MCU介绍 MCU的组成与作用 电子产品项目开发流程 硬件开发流程 常用元器件初步了解 硬件原理图与PCB板 常见电源符号和名称 电阻 电阻的分类 贴片电阻的封装说明&#xff1a; 色环电阻的计算 贴片电阻阻值计算 上拉电阻与下拉电阻 电容 电容的读数 二极管 LED 灯电路 钳位作…...

【Python网络爬虫笔记】6- 网络爬虫中的Requests库

一、概述 Requests 是一个用 Python 语言编写的、简洁且功能强大的 HTTP 库。它允许开发者方便地发送各种 HTTP 请求&#xff0c;如 GET、POST、PUT、DELETE 等&#xff0c;并且可以轻松地处理请求的响应。这个库在 Python 生态系统中被广泛使用&#xff0c;无论是简单的网页数…...

Linux网络_网络协议_网络传输_网络字节序

一.协议 1.概念 协议&#xff08;Protocol&#xff09; 是一组规则和约定&#xff0c;用于定义计算机网络中不同设备之间如何进行通信和数据交换。协议规定了数据的格式、传输方式、传输顺序等详细规则&#xff0c;确保不同设备和系统能够有效地互联互通。 在网络通信中&#…...

浅谈网络 | 应用层之流媒体与P2P协议

目录 流媒体名词系列视频的本质视频压缩编码过程如何在直播中看到帅哥美女&#xff1f;RTMP 协议 P2PP2P 文件下载种子文件 (.torrent)去中心化网络&#xff08;DHT&#xff09;哈希值与 DHT 网络DHT 网络是如何查找 流媒体 直播系统组成与协议 近几年直播比较火&#xff0c;…...

css vue vxe-text-ellipsis table 实现多行文本超出隐藏省略

分享 vxe-text-ellipsis table grid 多行文本溢出省略的用法 正常情况下如果需要使用文本超出隐藏&#xff0c;通过 css 就可以完成 overflow: hidden; text-overflow: ellipsis; white-space: nowrap;但是如果需要实现多行文本溢出&#xff0c;就很难实现里&#xff0c;谷歌…...

基于hexo框架的博客搭建流程

这篇博文讲一讲hexo博客的搭建及文章管理&#xff0c;也算是我对于暑假的一个交代 &#xff01;&#xff01;&#xff01;注意&#xff1a;下面的操作是基于你已经安装了node.js和git的前提下进行的&#xff0c;并且拥有github账号 创建一个blog目录 在磁盘任意位置创建一个…...

数据结构-简单排序

一.前提 二.冒泡排序 三.插入排序 #include<iostream> using namespace std; typedef int ElemengType; void Bubble_Sort(ElemengType A[], int N) {for (int p N - 1; p > 0; p--) {int flag 0;for (int i 0; i < p; i) {if (A[i] > A[i 1]) {swap(A[i], …...

三十一:HTTP多种重定向跳转方式的差异

在现代网站开发中&#xff0c;HTTP 重定向是一种常见的技术&#xff0c;用于将用户的请求从一个 URL 跳转到另一个 URL。重定向机制广泛应用于网站迁移、SEO 优化、以及内容管理系统中。不同的 HTTP 状态码代表不同的重定向方式&#xff0c;每种方式的行为和适用场景各有不同。…...

利用Python爬虫精准获取淘宝商品详情的深度解析

在数字化时代&#xff0c;数据的价值日益凸显&#xff0c;尤其是在电子商务领域。淘宝作为中国最大的电商平台之一&#xff0c;拥有海量的商品数据&#xff0c;对于研究市场趋势、分析消费者行为等具有重要意义。本文将详细介绍如何使用Python编写爬虫程序&#xff0c;精准获取…...

架构师的英文:Architect

中文版 软件架构师 的英文是 “Software Architect”。 Software: 软件Architect: 架构师&#xff0c;通常指的是设计和规划某种系统或结构的人。 Software Architect 通常负责软件系统的整体设计、技术选型、架构规划&#xff0c;确保系统的可扩展性、可维护性和高效性等。…...

数据结构 ——— 计数排序算法的实现

目录 计数排序算法的思想 计数排序算法的实现 计数排序算法的思想 遍历数组&#xff0c;找出数组中的最大值 max 和 最小值 min 最大值 max 减去最小值 min 再加 1 得出数组元素的范围 range 利用 range 的大小 malloc 一个 count 数组用来计数 再对 count 数组进行初始化…...

k8s搭建Istio环境,案例pod一直处在Init:CrashLoopBackOff

1 部署calico网络环境&#xff0c;网上去找k8s版本对应的calico的配置文件&#xff0c;k8s2.8.0我用的3.28 2 安装istio环境 curl -L https://istio.io/downloadIstio | sh - # 省略istioctl生效的步骤 source <(istioctl completion zsh) istioctl install --set profile…...

Jenkins升级到最新版本后无法启动

1. 场景还原 最近在web界面将jenkins升级到最新版本后&#xff0c;后台无法启动jenkins服务&#xff0c;服务状态如下&#xff1a; 运行jenkins命令提示invalid Java version jenkins --version jenkins: invalid Java version: java version "1.8.0_202" Java(TM)…...

用户界面创建一个新的运动类型

● 现在我们需要根据我们之前规划的架构步骤来实现在用户界面创建一个运动类型 ● 首先我们在要获取用户在表单中输入的数据 //从表单中获取数据const type inputType.value;const distance inputDistance.value;const duration inputDuration.value;● 然后针对与不同的运动…...

ubuntu防火墙入门(一)——设置服务、关闭端口

本机想通过git clone gitgithub.com:skumra/robotic-grasping.git下载代码&#xff0c;firewall-config中需要为当前区域的防火墙开启SSH服务吗 是的&#xff0c;如果你想通过 git clone gitgithub.com:skumra/robotic-grasping.git 使用 SSH 协议从 GitHub 下载代码&#xff0…...

分治算法——二分查找(c++)(详解)

大家好&#xff0c;今天进入一个实用算法&#xff1a;分治算法。 1.分治算法介绍 分治算法&#xff0c;大概就是将一个大问题拆解成若干个小问题&#xff0c;将小问题一一解决&#xff0c;大问题也就迎刃而解。它包含了多种算法&#xff0c;比如递归、递推等。这里就讲解一下其…...

Binder架构

一、架构 如上图&#xff0c;binder 分为用户层和驱动层两部分&#xff0c;用户层有客户端&#xff08;Client&#xff09;、服务端&#xff08;Server&#xff09;、服务管理&#xff08;ServiceManager&#xff09;。 从用户空间的角度&#xff0c;使用步骤如下&#xff08;…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

为什么要创建 Vue 实例

核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

绕过 Xcode?使用 Appuploader和主流工具实现 iOS 上架自动化

iOS 应用的发布流程一直是开发链路中最“苹果味”的环节&#xff1a;强依赖 Xcode、必须使用 macOS、各种证书和描述文件配置……对很多跨平台开发者来说&#xff0c;这一套流程并不友好。 特别是当你的项目主要在 Windows 或 Linux 下开发&#xff08;例如 Flutter、React Na…...