在C++网络编程中,实现支持断点挂起和续传的异步下载任务,传统方案多依赖回调或者状态机,代码逻辑分散且难以维护。C++20引入的协程特性,允许我们以近乎同步的写法实现异步逻辑,大幅简化这类场景的开发流程。下面我们将逐步拆解实现过程。

核心实现思路
整个下载任务的核心逻辑分为几个部分:协程任务封装、HTTP范围请求处理、下载进度持久化、任务挂起与恢复。协程负责将异步的socket读写操作转换为可暂停的逻辑流,断点功能通过记录已下载的字节偏移量实现,挂起则是通过协程的暂停机制保留当前执行状态。
协程任务基础封装
首先我们需要封装一个基础的协程任务类型,用于支持异步操作的暂停与恢复。这里以简单的异步任务为例,内部保存延续回调用于恢复执行。
#include <coroutine>
#include <functional>
#include <cstdint>
// 基础协程任务类型
struct AsyncTask {
struct promise_type {
AsyncTask get_return_object() {
return AsyncTask{std::coroutine_handle<promise_type>::from_promise(*this)};
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
// 保存挂起后的恢复回调
std::function<void()> resume_cb;
};
std::coroutine_handle<promise_type> handle;
explicit AsyncTask(std::coroutine_handle<promise_type> h) : handle(h) {}
// 挂起任务,保存恢复回调
void suspend(std::function<void()> cb) {
handle.promise().resume_cb = cb;
handle.destroy();
}
// 恢复任务执行
void resume() {
if (handle && !handle.done()) {
handle.resume();
}
}
~AsyncTask() {
if (handle) {
handle.destroy();
}
}
};
HTTP范围请求与断点处理
断点续传依赖HTTP协议的Range请求头,通过指定下载的起始偏移量,从服务器获取未下载的部分数据。我们需要实现一个简单的HTTP客户端逻辑,发送范围请求并解析响应。
#include <string>
#include <vector>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
// 简单的HTTP范围请求工具
class HttpRangeClient {
public:
HttpRangeClient(const std::string& host, uint16_t port) : host_(host), port_(port), sock_(-1) {}
// 建立TCP连接
bool connect() {
sock_ = socket(AF_INET, SOCK_STREAM, 0);
if (sock_ < 0) return false;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port_);
inet_pton(AF_INET, host_.c_str(), &addr.sin_addr);
if (::connect(sock_, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
close(sock_);
sock_ = -1;
return false;
}
return true;
}
// 发送范围请求,返回响应数据
std::vector<uint8_t> request_range(const std::string& path, int64_t start, int64_t end) {
std::string req = "GET " + path + " HTTP/1.1rn";
req += "Host: " + host_ + "rn";
if (end >= start) {
req += "Range: bytes=" + std::to_string(start) + "-" + std::to_string(end) + "rn";
}
req += "Connection: closernrn";
send(sock_, req.c_str(), req.size(), 0);
std::vector<uint8_t> resp;
char buf[1024];
int n;
while ((n = recv(sock_, buf, sizeof(buf), 0)) > 0) {
resp.insert(resp.end(), buf, buf + n);
}
return resp;
}
~HttpRangeClient() {
if (sock_ >= 0) {
close(sock_);
}
}
private:
std::string host_;
uint16_t port_;
int sock_;
};
带断点挂起的下载任务实现
接下来结合协程和HTTP范围请求,实现支持断点挂起和续传的下载任务。我们会记录已下载的字节数,挂起时保存偏移量,恢复时从该偏移量继续请求。
#include <fstream>
#include <atomic>
#include <thread>
#include <chrono>
// 下载任务状态
enum class DownloadState {
RUNNING,
SUSPENDED,
FINISHED
};
class BreakpointDownloadTask {
public:
BreakpointDownloadTask(const std::string& url, const std::string& save_path)
: url_(url), save_path_(save_path), state_(DownloadState::RUNNING), downloaded_bytes_(0) {
// 解析URL,这里简化处理,假设url格式为 http://host:port/path
size_t host_start = url.find("://") + 3;
size_t path_start = url.find('/', host_start);
host_ = url.substr(host_start, path_start - host_start);
path_ = url.substr(path_start);
// 默认端口80
port_ = 80;
size_t port_pos = host_.find(':');
if (port_pos != std::string::npos) {
port_ = std::stoi(host_.substr(port_pos + 1));
host_ = host_.substr(0, port_pos);
}
}
// 启动下载协程
AsyncTask start() {
co_await download_coroutine();
}
// 挂起任务
void suspend() {
if (state_ == DownloadState::RUNNING) {
state_ = DownloadState::SUSPENDED;
// 保存当前下载偏移量到文件
save_progress();
if (task_handle_) {
task_handle_.suspend([this]() {
// 挂起后的回调,这里仅标记状态,恢复时调用resume即可
});
}
}
}
// 恢复任务
void resume() {
if (state_ == DownloadState::SUSPENDED) {
state_ = DownloadState::RUNNING;
// 读取之前保存的下载偏移量
load_progress();
if (task_handle_) {
task_handle_.resume();
}
}
}
DownloadState get_state() const { return state_; }
int64_t get_downloaded_bytes() const { return downloaded_bytes_; }
private:
// 协程下载逻辑
AsyncTask download_coroutine() {
HttpRangeClient client(host_, port_);
if (!client.connect()) {
std::cerr << "连接服务器失败" << std::endl;
co_return;
}
// 打开保存文件,以追加模式写入
std::ofstream out_file(save_path_, std::ios::binary | std::ios::app);
if (!out_file) {
std::cerr << "打开文件失败" << std::endl;
co_return;
}
// 假设每次请求下载1024字节,实际可根据需求调整
const int64_t block_size = 1024;
while (state_ == DownloadState::RUNNING) {
// 发起范围请求,从已下载偏移量开始
auto data = client.request_range(path_, downloaded_bytes_, downloaded_bytes_ + block_size - 1);
// 简化处理,跳过HTTP响应头,实际需解析Content-Range等字段
// 这里仅演示逻辑,实际解析需要更完整的HTTP响应处理
size_t header_end = std::search(data.begin(), data.end(),
std::string("rnrn").begin(), std::string("rnrn").end()) - data.begin();
if (header_end != data.size()) {
header_end += 4;
std::vector<uint8_t> body(data.begin() + header_end, data.end());
out_file.write(reinterpret_cast<const char*>(body.data()), body.size());
downloaded_bytes_ += body.size();
std::cout << "已下载:" << downloaded_bytes_ << " 字节" << std::endl;
}
// 模拟异步等待,实际可结合IO多路复用实现
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
out_file.close();
if (state_ == DownloadState::RUNNING) {
state_ = DownloadState::FINISHED;
std::cout << "下载完成" << std::endl;
}
co_return;
}
// 保存下载进度到文件
void save_progress() {
std::ofstream prog_file(save_path_ + ".progress");
if (prog_file) {
prog_file << downloaded_bytes_;
}
}
// 加载下载进度
void load_progress() {
std::ifstream prog_file(save_path_ + ".progress");
if (prog_file) {
prog_file >> downloaded_bytes_;
}
}
std::string url_;
std::string host_;
std::string path_;
uint16_t port_;
std::string save_path_;
std::atomic<DownloadState> state_;
int64_t downloaded_bytes_;
AsyncTask task_handle_;
};
使用示例
下面是使用该下载任务的示例代码,演示启动、挂起、恢复的流程。
int main() {
BreakpointDownloadTask task("http://ipipp.com/test.zip", "./test.zip");
auto async_task = task.start();
// 模拟运行一段时间后挂起
std::this_thread::sleep_for(std::chrono::seconds(2));
task.suspend();
std::cout << "任务已挂起,已下载字节:" << task.get_downloaded_bytes() << std::endl;
// 模拟挂起一段时间后恢复
std::this_thread::sleep_for(std::chrono::seconds(3));
task.resume();
std::cout << "任务已恢复" << std::endl;
// 等待任务完成
while (task.get_state() != DownloadState::FINISHED) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
注意事项
- 示例中的HTTP解析部分做了简化,实际生产环境需要完整解析响应头,处理状态码、Content-Range、重定向等场景。
- 协程的挂起和恢复需要结合实际的IO模型,比如可以结合epoll等IO多路复用机制,在socket可读可写时才恢复协程,避免无效等待。
- 断点进度文件需要处理并发读写问题,多任务场景下需要增加文件锁或者独立的进度管理模块。
- 下载的文件完整性校验可以额外增加MD5或者SHA校验逻辑,确保下载的文件没有损坏。
总结
通过C++协程实现支持断点挂起的异步下载任务,能够将原本分散的异步回调逻辑转换为线性的代码流,提升代码的可读性和可维护性。核心思路是利用协程的暂停恢复能力保存任务状态,结合HTTP范围请求实现断点续传,再通过状态管理实现任务的挂起与恢复。开发者可以根据实际需求扩展该方案,比如增加多线程分片下载、错误重试、下载速度限制等功能,适配更复杂的业务场景。
C++coroutineasync_downloadbreakpoint_resume修改时间:2026-06-19 15:00:26