那天下午三点,我正盯着屏幕上一个进度条,它像便秘一样卡在 87%,然后——“咔哒”,没了。不是电脑死机,是网断了。那个 150MB 的压缩包,或者说是那份该死的客户交付物,在上传到 S3 的时候给我表演了一个原地消失。
我第一次重试,进度条走到 40% 又断了。 第二次,我在 92% 的时候看到那个该死的红色叉号。 第三次,我没敢点重试,因为我知道再断一次,我就得在老板走过来之前写一份“为什么我浪费了公司带宽”的解释信了。
那一刻,我学到的不是技术,是生存。
为什么 150MB 会这么难搞?
先别急着骂运营商。150MB 说大不大,说小不小。在千兆光纤下,它本来只需要几秒钟。但如果你的网络链路本身就不稳,或者中间经过了一些质量堪忧的中转节点,这个文件就会变成一个“半传半断”的尴尬存在。
我第一次断网时,以为只是抖动。第二次,我开始怀疑是不是房东的宽带被隔壁蹭网了。第三次断网后,我看着任务管理器里那个已经停止响应的上传进程,心里清楚:靠那个默认的浏览器上传组件或者 Windows 自带的复制粘贴,是不可能完成这个任务了。
我要的不是“再试一次”,我要的是“断点续传”,以及“即使断了也能静默恢复”的能力。
第一步:切断噪音,建立“隐形”通道
老板最烦的是什么?不是网速慢,而是你在他背后叹气和盯着屏幕皱眉的样子。所以,我的第一个动作不是重新上传,而是关闭所有会弹出通知的界面。
浏览器上传窗口?关掉。 网盘客户端的同步提示?退出。 系统托盘里那个跳来跳去的图标?右键隐藏。
我要做的是在后台跑一个脚本,让它像个隐形人一样工作。这时候,命令行工具 curl 或者 rsync 就成了我的救命稻草,而不是那些花里胡哨的图形界面软件。
假设我在用 Linux 或者 Mac,或者 Windows 上的 WSL(Windows Subsystem for Linux),我会打开终端,输入这样一行命令:
curl -T localfile.zip --retry 999 --retry-delay 5 -# -o /dev/null https://upload.example.com/filename.zip
这行命令里的门道很多,我来拆解给你看,就像给小朋友讲为什么红绿灯不能闯一样:
-T localfile.zip:告诉 curl,“我要上传这个本地文件”。--retry 999:这是关键。意思是“如果失败了,就重试 999 次”。只要你还想传完,它就永远不死心。--retry-delay 5:每次失败后,等 5 秒再试。这 5 秒不是浪费,是让网络喘息,也让老板觉得你只是在“思考下一步”。-#:显示一个简单的进度条,比那些闪烁的光标看起来更“专业”和“稳定”。-o /dev/null:如果不加这个,curl 会把服务器的响应也打印出来,屏幕乱跳很显眼。丢到黑洞里,世界清净了。
如果你是在 Windows 原生环境下,没有 curl 也没关系,Python 是你的秘密武器。写一个极简的脚本:
import requests
import time
file_path = '客户交付物_最终版_v3.zip'
url = 'https://upload.example.com/filename.zip'
headers = {'Expect': ''} # 防止 curl 发送 Expect: 100-continue 导致某些服务器卡住
def upload_with_resume():
bytes_uploaded = 0
with open(file_path, 'rb') as f:
while True:
try:
# 如果之前传过一部分,服务器支持 Range 请求的话,这里告诉它从哪继续
if bytes_uploaded > 0:
headers['Content-Range'] = f'bytes {bytes_uploaded}-*/{os.path.getsize(file_path)}'
with open(file_path, 'rb') as f:
# 跳过已经上传的字节
f.seek(bytes_uploaded)
response = requests.post(url, data=f, headers=headers, timeout=60)
# 这里需要根据你的服务器返回码判断是否成功
if response.status_code in [200, 201, 204]:
print(f"上传成功!耗时 {time.time() - start_time} 秒")
return True
else:
print(f"服务器报错 {response.status_code},5秒后重试...")
except requests.exceptions.RequestException as e:
print(f"网络抖动,{e},5秒后重试...")
# 关键:统计已经上传了多少字节,以便下次断点续传
# 注意:简单的 requests.post 不自动支持断点续传,这里需要更复杂的逻辑
# 但对于大多数支持分片上传的 API,你需要计算偏移量
bytes_uploaded = f.tell() if 'f' in locals() else bytes_uploaded
time.sleep(5)
if __name__ == "__main__":
start_time = time.time()
upload_with_resume()
等一下,这段代码有个小陷阱。标准的 requests.post 其实并不直接支持像 FTP 那样的“断点续传”,除非目标服务器明确支持 HTTP 的 Content-Range 头。如果服务器不支持,我写的 Content-Range 可能会被忽略,导致从头开始。
所以,更稳健的做法是检查服务器是否支持 HEAD 请求来获取已上传的大小,或者直接使用支持断点续传的库,比如 aws-cli(如果是传 AWS S3)或者 rclone。
第二步:如果老板真的走过来了
这是最紧张的时刻。屏幕黑着,只有终端在跑。你看起来像是在开会,或者在专注地读文档。
如果老板问:“在干嘛?” 你不能说:“我在重试一个破文件上传。” 你要说:“在核对一下云端的同步状态,之前有点延迟,现在正在强制刷新。”
这句话术的高明之处在于:
- 专业化:“同步状态”、“强制刷新”都是职场黑话,听起来很高端。
- 合理化:“之前有点延迟”解释了为什么你可能刚才盯着屏幕发呆。
- 结果导向:你是在“核对”,不是在“等待”。
这时候,你的终端屏幕上应该显示的是:
% Total % Received % Xferd Average Speed Time... 95% 142M 0 0 2.1M 0 0:00:07 0:00:06 2.1M
看起来就像是在进行某种高强度的数据处理。实际上,它只是在 95% 的地方卡住了,然后在第 96% 的时候,网络恢复了。
第三步:如何确保“不让发现”的核心技巧
除了技术上的断点续传,还有几个心理层面的技巧:
1. 不要最大化窗口 把终端窗口缩小,放在屏幕的一个角落,下面盖一层浏览器或者 Excel 表格。这样从老板的视角看,你似乎是在看下面的表格,而上面的终端只是“背景噪音”。
2. 利用“忙”的假象 如果上传很慢,打开一个无关紧要的文档,假装在阅读。上传软件会在后台继续跑。即使断网了,只要脚本写得够好,它会在网络恢复的第一毫秒立即重新连接。
3. 检查上传日志
如果老板真的质疑你为什么这么久,你可以说:“我在查看上传日志,确认没有数据损坏。”然后指着终端里那一行行滚动的 % Total % Received 证明你的“专业性”。
第四步:终极方案——使用 rsync 或 rclone
如果文件很重要,且服务器支持 SSH 或 SFTP,rsync 是神器。它不仅断点续传,而且只传输变化的部分。
rsync -avz --progress localfile.zip user@server:/path/to/destination/
-a:归档模式,保留权限和时间戳。-v:详细模式。-z:压缩传输,对于 150MB 的文件,压缩能加速不少。--progress:显示进度。
如果 rsync 断了,再运行一次同样的命令,它会从断开的地方继续,而不是重新开始。这就像你吃了一半的披萨,不用重买一个,只需把剩下的吃完。
总结:从这次经历中学到什么
断网三次后,我意识到:
- 不要相信图形界面的“耐心”:大多数 GUI 上传工具在断网后会显示“连接失败”,然后让你手动重试。这很蠢。
- 命令行是最后的堡垒:一旦你掌握了
curl、rsync或简单的 Python 脚本,你就拥有了控制力。 - 职场生存 > 技术本身:如何解释你的行为,如何展示你的“工作状态”,有时比文件本身更快上传更重要。
现在,那个文件已经传完了。我喝了一口凉掉的咖啡,老板路过我的工位,看了一眼我屏幕上滚动的代码,说:“辛苦了。”
我笑了笑,说:“没事,只是同步了一下。”
这就是成年人的体面。
