Python实现Linux版Quick Share:极简局域网文件传输CLI工具 📅 发布时间:2026/8/30 15:07:11 👁 浏览次数: Quick Share 这个名字Android 用户不会陌生。它是 Android 和 ChromeOS 设备之间近乎“无感”的近距离文件传输方案速度远胜蓝牙也不需要数据线。Linux 用户看到这个功能第一反应往往是羡慕手机和电脑之间传文件我已经受够了微信“文件传输助手”的压缩画质、网盘的限速以及一张一张挂 QQ 的笨办法。如果你主要在 Linux 桌面环境工作大概率能对上下面某个场景想从 Android 手机传一张照片到电脑写博客配图想把电脑上的日志文件发给同事的手机或者只是在两台 Linux 机器之间快速同步一个配置文件。传统办法要么走局域网 SMB 共享要么临时起一个 HTTP 服务再用手机浏览器下载要么直接插数据线。这些方案都能用但都没有 Quick Share 那种“打开面板——选设备——发送/接收”的顺滑感。这篇文章要聊的是一个思路很直接的解决方案用 Python 在 Linux 上实现 Quick Share 风格的极简命令行工具。它不依赖 GNOME 桌面扩展、不需要安装庞大的 GUI 应用只需要一个 Python 环境和几条命令就能在 Linux 与 Android 或其他设备之间完成局域网文件传输。整篇文章会分三个层次来写先解释 Quick Share 这类传输方案背后的核心技术原理再给出一个可运行的 Python CLI 实现思路与完整代码示例最后总结实际项目中最容易踩的坑和工程建议。如果你正准备给团队内部做文件传输工具选型或者只是想自己搭一个够用的跨设备传文件方案这篇文章可以直接当作参考。1. 这篇文章真正要解决的问题我在几个开发者社群里观察到一个共性现象几乎所有 Linux 用户的文件传输方案都带着“临时凑合”的味道。用得最多的方案是python3 -m http.server。一条命令启动 HTTP 服务手机浏览器访问 IP 端口下载文件。听起来很方便实际用起来有三个问题没有设备发现机制你必须先查电脑 IP再在手机上手动输入地址。只能下载不能上传——手机往电脑传文件依然无解。文件名是 URL 编码的中文名经常出现显示错乱。另一个常见方案是配置 Samba 共享。它能解决双向传输但带来的问题是需要维护 SMB 用户和权限需要处理不同发行版的 Samba 配置文件差异而且大部分人在配置完一次之后就再也不想去动它。再看手机端的体验Android 自带的 Quick Share 利用的是 Google Play 服务里的 Nearby Connections API走的是蓝牙发现 WiFi 直连/局域网传输的混合链路。它好用但它不是开放协议也没有官方 Linux 客户端。这就是问题所在一个在移动平台已经做到很成熟的功能到了 Linux 上反而变成难题。所以一个极简的 Linux 版 Quick Share CLI真正解决的并不是“技术上能不能传文件”这种低级问题而是三个更实际的痛点发现设备的成本太高。命令行工具如果还要手动输入 IP、端口那它和python3 -m http.server没有本质区别。真正值得做的是在局域网内自动发现对端设备。双向传输的体验割裂。手机传电脑、电脑传手机应该是对称的操作而不是一个靠 HTTP 下载、一个靠 FTP 上传。使用场景需要贴近命令行。Linux 开发者习惯用终端解决问题。一个qshare send file、qshare receive这样的 CLI比打开 GUI 应用更符合日常习惯。基于这几个判断我选择了 Python 作为实现语言。原因很直接Python 在 Linux 上几乎是默认环境socket 编程、HTTP 服务、mDNS 发现都有成熟的标准库或轻量依赖非常适合做这种“小工具但网络流程完整”的项目。2. Quick Share 的核心原理与 CLI 方案定位很多人在网上搜“Quick Share Linux”搜到的基本是两类内容一类是安装 Android 版 Quick Share 的旧教程告诉你用 ADB 和无线调试在 Linux 上跑 Android 应用另一类是基于 GNOME 的 GSConnect 插件方案走的是 KDE Connect 协议。这两种方案都有明显短板。第一种要依赖 Android 模拟器级别的兼容层性能差链路长而且 Android 应用和 Linux 环境之间的文件访问权限非常别扭。第二种只对 GNOME 桌面友好KDE 用户、xfce 用户、纯 WM 用户基本用不上。CLI 方案的定位差异在这里就体现出来了。它不做系统深度集成不做桌面状态栏入口它只保证一件事在局域网内两台设备能够互相发现、互相传文件并且整个过程可以在终端里完成。Quick Share 这类传输功能底层拆开看就是四层层作用典型协议设备发现让对端设备暴露自身存在mDNS / DNS-SD连接建立确认双方可通信TCP / HTTP传输控制处理文件元数据、分块、校验HTTP POST / WebSocket安全验证防止无关设备接入PIN 码 / 请求确认在这个设计里mDNS多播 DNS是最关键的一层。它让设备在局域网内广播自己的服务类型其他设备通过监听特定端口就能发现它不需要提前知道 IP。你可以把它理解成“局域网里的喊话”设备 A 喊一句“我是 Quick Share 接收端”设备 B 收到后回一句“我要给你发文件”。CLI 工具要复刻的正是这套机制。用 Python 实现时不需要自己从零写 mDNS 协议直接用zeroconf库注册服务和解析服务即可。传输层则可以用 Python 内置的http.server模块搭建一个轻量 HTTP 服务接收端启动后监听端口发送端拿到接收端的地址后直接发起 HTTP 上传请求。与官方 Quick Share 相比CLI 方案至少在三方面更优依赖轻不依赖 Google Play 服务不绑定桌面环境。可控性强整个传输流程都写在 Python 脚本里想改端口、自定义验证方式都很容易。自动化友好命令行工具天然适合脚本调用比如备份文件后自动推送到另一台设备。3. Python CLI 实现 Quick Share 的技术选型动手写代码之前需要明确技术选型。下面是我认为比较稳健的组合。3.1 服务发现zeroconfPython 生态里做 mDNS 发现最常用的库是zeroconf项目名 python-zeroconf。它实现了 DNS-SD 协议可以注册一个自定义服务类型也能扫描局域网内的目标服务。需要说明的是这个库并不是标准库模块需要单独安装。如果你的项目跑在没有外网的离线环境里也可以回退到“手动指定 IP”模式用--host参数强制指定对端地址。3.2 传输层http.server socket接收端用http.server.HTTPServer启动一个文件上传接口发送端用requests或标准库urllib发送 multipart/form-data 请求。这里有一个容易被忽视的设计点Quick Share 是双向传输所以发送端和接收端其实需要相同的能力。也就是说CLI 应该同时内置 HTTP 服务和客户端逻辑用子命令区分角色。3.3 文件处理pathlib hashlib文件名使用pathlib.Path处理兼容中文和空格。文件完整性校验用hashlib.sha256在传输完成后计算哈希并回传发送端比对确认。3.4 参数解析argparseCLI 入口用argparse解析子命令。send负责发送文件receive负责接收文件discover负责扫描局域网内的接收端。这套选型有一个核心优势所有组件都是 Linux 开发者熟悉的技术栈出问题时排查链路短。不像 GUI 应用出了问题还要去翻 DBus 日志、桌面通知日志、依赖库兼容列表。4. 环境准备与项目初始化在开始写代码之前先确认环境。建议系统要求操作系统LinuxUbuntu / Debian / CentOS / openSUSE 等均可Python 版本3.8 及以上。如果使用较老版本argparse的子命令写法会略有差异建议升级到 3.10 以上。依赖库zeroconf。如果需要更完善的文件 MIME 类型判断可以额外安装mimetypes标准库自带其实不需要单独安装。网络要求所有设备必须在同一个局域网内并且互相允许 UDP 5353 端口和自定义 TCP 端口的通信。第一步是创建虚拟环境并安装依赖。为了便于复现我建议在项目根目录下单独创建venvmkdir qshare cd qshare python3 -m venv venv source venv/bin/activate pip install zeroconf requests如果你所在环境无法安装zeroconf后面我会在代码里给出一个不需要该库的兼容方案通过--host手动指定对端 IP。但那样会丢失设备自动发现能力体验会明显下降。接下来创建项目文件结构。这是一个单文件就能跑通的小工具但为了后续扩展建议按模块化方式组织qshare/ ├── cli.py # 命令行入口参数解析 ├── discovery.py # mDNS 服务发现与注册 ├── server.py # HTTP 文件接收服务 ├── client.py # 文件发送客户端 └── requirements.txt如果只是个人使用把所有逻辑写进cli.py也是可行的。但分模块的好处是将来想加入文件夹传输、断点续传或加密功能时不需要重写入口逻辑。5. 核心流程拆解从注册服务到完成传输在写代码之前有必要把整个流程拆清楚。Quick Share 的一次完整传输大致经历三个阶段5.1 阶段一接收端注册服务接收端启动后通过 zeroconf 注册一个自定义服务类型。服务类型命名必须是“下划线 名称 下划线 协议”的格式比如_qshare._tcp.local.。注册时需要指定端口、设备名称和附加属性如设备平台、是否忙碌。这一步完成后其他设备就能在局域网内扫描到这个服务。5.2 阶段二发送端发现服务发送端通过 zeroconf 的ServiceBrowser监听局域网内的_qshare._tcp.local.服务。每当发现一个新服务就能拿到接收端的 IP、端口和设备名称。这一步是把“查 IP、记端口”这个动作自动化了。用户不需要知道接收端的 IP 是多少只需要保证两台设备连的是同一个 WiFi 或局域网。5.3 阶段三文件传输发送端拿到接收端地址后通过 HTTP POST 请求上传文件。接收端收到请求后保存文件计算 SHA256 哈希并将哈希值作为响应内容返回。发送端拿到返回的哈希后与本地计算的文件哈希进行比对。如果一致说明传输完整如果不一致说明链路中数据丢失或被篡改应该删除接收端的不完整文件并重新传输。6. 完整实现代码与配置下面给出一个可直接运行的极简实现。这个实现只保留最核心的功能接收端注册服务、发送端自动发现、HTTP 上传文件、哈希校验。6.1 文件requirements.txt# 文件路径qshare/requirements.txt zeroconf0.132.2 requests2.32.3版本号以当前 PyPI 实际发布版本为准锁版本是为了避免未来依赖 API 变更导致工具不可用。6.2 文件discovery.py# 文件路径qshare/discovery.py mDNS discovery for Quick Share CLI. import time from zeroconf import ServiceBrowser, ServiceInfo, Zeroconf HOST _qshare._tcp.local. def get_service_info(name, port, host_ip, platformlinux): 构造 zeroconf 服务注册信息。 return ServiceInfo( HOST, f{name}.{HOST}, addresses[host_ip], portport, properties{platform: platform}, ) class QShareListener: 监听局域网内的 qshare 服务。 def __init__(self): self.found_services [] def add_service(self, zeroconf, service_type, name): info zeroconf.get_service_info(service_type, name) if info: self.found_services.append( { name: name.replace(f.{HOST}, ), host: ..join(str(b) for b in info.addresses[0]) .replace(0.0.0.0, ), port: info.port, } ) def update_service(self, zeroconf, service_type, name): self.add_service(zeroconf, service_type, name) def remove_service(self, zeroconf, service_type, name): pass这段代码里有一个细节需要解释info.addresses返回的是字节数组需要手动转换成字符串 IP。不同版本的 zeroconf 库可能返回的类型有差异建议在对接时用ipaddress.ip_address做一层兼容转换。6.3 文件server.py# 文件路径qshare/server.py HTTP file receive server for Quick Share CLI. import hashlib import json import re from http.server import BaseHTTPRequestHandler, HTTPServer from pathlib import Path class QShareServer: def __init__(self, save_dir: str ./received): self.save_dir Path(save_dir) self.save_dir.mkdir(parentsTrue, exist_okTrue) def handle_upload(self, handler): content_length int(handler.headers.get(Content-Length, 0)) raw_body handler.rfile.read(content_length) # 从文件名头中解析文件名这里做了一个简化约定 # 请求头中通过 X-Filename 传递原始文件名 filename handler.headers.get(X-Filename, unnamed) safe_name re.sub(r[^\w\-. ], _, filename) target_path self.save_dir / safe_name target_path.write_bytes(raw_body) sha256 hashlib.sha256(raw_body).hexdigest() response json.dumps({status: ok, sha256: sha256}) handler.send_response(200) handler.send_header(Content-Type, application/json) handler.send_header(Content-Length, str(len(response.encode()))) handler.end_headers() handler.wfile.write(response.encode()) def run(self, port: int 8765): server HTTPServer((0.0.0.0, port), self._make_handler()) print(f[qshare] listening on 0.0.0.0:{port}) server.serve_forever() def _make_handler(self): server_ref self class Handler(BaseHTTPRequestHandler): def do_POST(self): server_ref.handle_upload(self) def log_message(self, format, *args): print(f[http] {self.address_string()} - {format % args}) return Handler这个实现故意做了极简化文件通过请求体一次性读取落盘到received目录然后计算哈希。生产级实现里需要改成像cgi.FieldStorage或流式写入那样处理大文件但这里优先保证代码易读。6.4 文件client.py# 文件路径qshare/client.py File send client for Quick Share CLI. import hashlib from pathlib import Path import requests def send_file(file_path: str, host: str, port: int 8765) - bool: path Path(file_path) if not path.exists(): raise FileNotFoundError(ffile not found: {file_path}) data path.read_bytes() sha256 hashlib.sha256(data).hexdigest() url fhttp://{host}:{port}/upload headers { X-Filename: path.name, Content-Type: application/octet-stream, } resp requests.post(url, datadata, headersheaders, timeout60) resp.raise_for_status() result resp.json() if result.get(sha256) ! sha256: raise RuntimeError(sha256 mismatch, file transfer corrupted) return True6.5 文件cli.py# 文件路径qshare/cli.py Command line entry for qshare. import argparse import socket from discovery import QShareListener, get_service_info from zeroconf import Zeroconf from client import send_file from server import QShareServer DEFAULT_PORT 8765 def get_local_ip() - str: s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) try: s.connect((10.255.255.255, 1)) return s.getsockname()[0] except Exception: return 127.0.0.1 finally: s.close() def cmd_receive(args): server QShareServer(args.save_dir) zeroconf Zeroconf() info get_service_info(args.name, DEFAULT_PORT, get_local_ip()) zeroconf.register_service(info) try: server.run(portargs.port) finally: zeroconf.unregister_service(info) zeroconf.close() def cmd_send(args): listener QShareListener() zeroconf Zeroconf() ServiceBrowser(zeroconf, _qshare._tcp.local., listener) print([qshare] discovering devices...) import time time.sleep(2) if not listener.found_services: if args.host: target {host: args.host, port: args.port} else: raise RuntimeError(no devices found, use --host to specify manually) else: target listener.found_services[0] print(f[qshare] sending to {target[host]}:{target[port]}) send_file(args.file, target[host], target[port]) print([qshare] done) def main(): parser argparse.ArgumentParser(progqshare, descriptionMinimal Quick Share CLI for Linux) subparsers parser.add_subparsers(destcommand, requiredTrue) receive_parser subparsers.add_parser(receive, helpreceive files) receive_parser.add_argument(--save-dir, default./received) receive_parser.add_argument(--port, typeint, defaultDEFAULT_PORT) receive_parser.add_argument(--name, defaultsocket.gethostname()) send_parser subparsers.add_parser(send, helpsend files) send_parser.add_argument(file) send_parser.add_argument(--host, defaultNone) send_parser.add_argument(--port, typeint, defaultDEFAULT_PORT) args parser.parse_args() if args.command receive: cmd_receive(args) elif args.command send: cmd_send(args) if __name__ __main__: main()这个 CLI 入口覆盖了最核心的两个命令receive和send。7. 运行与验证现在我们来实际运行一下验证整个流程是否通畅。7.1 启动接收端在设备 ALinux 电脑上执行cd qshare source venv/bin/activate python cli.py receive --save-dir /tmp/qshare-received --name my-laptop预期输出[qshare] listening on 0.0.0.0:8765此时设备 A 已经在局域网内注册了_qshare._tcp.local.服务并且启动了 HTTP 上传服务。7.2 启动发送端在设备 B另一台 Linux 电脑或者同一局域网内的其他设备上执行cd qshare source venv/bin/activate python cli.py send /path/to/blog-report.pdf预期输出[qshare] discovering devices... [qshare] sending to 192.168.1.123:8765 [qshare] done如果设备 A 上能看到类似下面的日志说明文件上传成功[http] 192.168.1.100 - POST /upload HTTP/1.1 200 -注意这里我默认了发送端和接收端都安装了zeroconf。如果设备 B 在扫描阶段没有发现任何服务并且你不想安装 zeroconf可以直接跳过发现用--host手动指定接收端地址python cli.py send /path/to/file.pdf --host 192.168.1.1237.3 验证哈希一致性上述代码在send_file里已经做了哈希比对。如果两端哈希不一致会抛出RuntimeError(sha256 mismatch, file transfer corrupted)。你也可以在接收端手动验证sha256sum /tmp/qshare-received/blog-report.pdf比对原文哈希即可确认传输完整性。8. 常见问题与排查方法在实际使用中大部分问题都集中在网络环境和依赖兼容层面。我整理了四个最典型的场景。问题现象可能原因排查方式解决方案python cli.py send提示 no devices found接收端未启动、局域网隔离、防火墙拦截 UDP 5353检查接收端进程是否存活两台设备互相 ping 确认同一网段用tcpdump udp port 5353抓包确认 mDNS 报文启动接收端服务关闭客户端防火墙或添加放行规则使用--host手动指定地址发送时报Connection refused接收端未监听预期端口或端口被占用ss -lntp查看 8765 端口是否被监听修改--port指定新的端口杀掉占用进程文件传输完成但哈希不匹配大文件被 HTTP 代理截断或代码一次性读取导致内存溢出查看接收端日志是否有传输中断检查发送文件大小和接收文件大小将一次性读取改为流式写入若使用代理为内网地址设置 no_proxy中文文件名变成乱码HTTP 头默认编码不是 UTF-8在服务器端打印handler.headers观察编码文件名统一用 URL 编码后再放入头接收端先解码再写文件除了这几个问题有一个点需要特别提醒代码里使用path.read_bytes()一次性读入文件如果传输几百 MB 甚至几个 GB 的文件内存占用会非常高。这个问题在最小示例里可以容忍但做工程化时一定要改成流式发送和流式接收。另一个隐藏问题是HTTPServer默认是单线程的一次只能处理一个请求。如果有多个设备同时给你发文件后到的请求会排队等待。如果这是你要支持的场景建议把HTTPServer换成ThreadingHTTPServer。9. 最佳实践与工程建议从“能跑”到“适合在生产环境使用”这个 CLI 工具还需要补上几个关键能力。9.1 增加请求确认机制Quick Share 在 Android 上的体验很好一部分原因是接收端可以主动确认是否接收文件。CLI 工具至少应该提供一个--accept-all参数和默认的交互式确认逻辑收到上传请求时先打印文件名和大小按y才会开始接收。这种机制能防止局域网内的其他设备给你塞垃圾文件。9.2 改成流式传输建议把接收端和发送端的文件读写都改为分块流式。发送端每读 64KB 发一次接收端每收到一块就追加写入文件。哈希校验放在整个文件接收完成后进行。9.3 链路安全边界目前的实现完全没有加密。它适合可信局域网但不适合公共 WiFi。如果需要在不可信网络传输至少加一层 TLS或者直接引入一次性的密钥交换。此外建议在代码中固定允许的最大文件大小避免接收端被恶意大文件攻击导致磁盘写满。9.4 增加接收端状态反馈现在的 receive 命令启动后只显示 listening用户无法判断当前有没有设备正在传输。建议增加--verbose模式打印每个连接请求的来源 IP、文件名、传输大小和耗时。9.5 项目中的模块划分如果这个工具要在团队里维护建议将discovery.py、server.py、client.py拆成独立包并为它们分别编写单元测试。mDNS 注册和文件哈希是其中最容易出错、也最值得单独测试的两个模块。10. 总结与后续方向这个用 Python 实现的极简 Quick Share CLI本质上是一个局域网文件传输工具的最小闭环。它复刻了 Quick Share 最核心的体验亮点设备自动发现和单向/双向文件传输。虽然它的加密、并发和交互确认机制还很初步但作为功能原型已经足够清晰。如果你现在就想把它用起来建议按这个顺序做三件事第一在可信局域网内用两台 Linux 机器跑通receive和send两个命令熟悉整个流程。第二把文件读取改为流式实现并加上交互式接收确认然后放到日常开发环境中试用。第三如果跨 Android 手机传文件是你的刚需不要让 Python 脚本去强行对接 Android 的 Quick Share 私有协议而是考虑在两台设备上分别安装这个 CLI把它当作自有的跨平台传输工具使用。下一步真正值得深入研究的方向是接近完整 Quick Share 体验的协议兼容层那意味着要处理设备证书、加密信道、大数据块分包和多设备并发调度。那是另一个量级的工作。但至少在“我的 Linux 设备需要一个能用的 Quick Share”这个问题上我们已经有了一个轻量、可控的起点。建议收藏这篇文章动手实现时直接照着代码跑一遍比记住结论有用得多。