C++回调:利用Sink

Sink的本质是利用C++的封装、继承、多态的面向对象来实现,从实现角度来说,更优于函数指针回调;

 

 1 // cbBysink.cpp : Defines the entry point for the console application.
 2 //
 3 
 4 #include "stdafx.h"
 5 #include "cbBysink.h"
 6 
 7 /************************************************************************/
 8 /*                上层回调函数                                            */
 9 /************************************************************************/
10 
11 class CMyWork : public baseCallBack
12 {
13 public:
14     CMyWork()
15     {
16         // 注册回调
17         CWork::registercallback(this);
18     }
19     // 回调注册实现
20     void CallbackFunction(int a, int b)
21     {
22         cout << "a = " << a << ",b = " << b << "\n" << endl;
23         return;
24     }
25     // 触发回调
26     void makefunction(int a, int b)
27     {
28         CWork::makecallback(a, b);
29         return;
30     }
31 protected:
32 private:
33 };
34 
35 int main(int argc, char* argv[])
36 {
37     CMyWork c_mywork;
38 
39     // 触发的时候不需要进行动态注册
40     c_mywork.makefunction(5, 6);
41     
42     return 0;
43 }
 1 #include <WINDOWS.H>
 2 #include <IOSTREAM>
 3 using namespace std;
 4 /************************************************************************/
 5 /*                下层回调定义                                            */
 6 /************************************************************************/
 7 
 8 class baseCallBack
 9 {
10 public:
11     virtual void CallbackFunction(int a, int b){};        // 虚函数,提供上层实现
12 protected:
13 private:
14 };
15 
16 class CWork
17 {
18 public:
19     static void registercallback(baseCallBack *pbCallback);
20     static void makecallback(int a, int b);
21 protected:
22 private:
23     static baseCallBack *m_pbCallback;
24 };
25 
26 baseCallBack *CWork::m_pbCallback = NULL;
27 
28 // 提供注册函数
29 void CWork::registercallback(baseCallBack *pbCallback)
30 {
31     if (pbCallback != NULL)
32     {
33         m_pbCallback = pbCallback;
34     }
35 }
36 
37 // 提供触发接口
38 void CWork::makecallback(int a, int b)
39 {
40     m_pbCallback->CallbackFunction(a, b);
41 }

转载于:https://www.cnblogs.com/davidsguo008/p/3638059.html

<think>我们遇到了一个编译错误,提示没有与参数列表匹配的`httplib::Response::set_content_provider`重载函数。参数类型为`(long, lambda [](size_t offset, httplib::DataSink &sink)->bool)`,而对象类型是`httplib::Response`。 在cpp-httplib库中,`set_content_provider`有几个重载版本。根据错误信息,我们传递了两个参数:一个`long`类型的`chunk_size`和一个lambda表达式。lambda表达式的签名为`(size_t offset, httplib::DataSink &sink) -> bool`。 然而,查看cpp-httplib的文档,我们发现`set_content_provider`用于流式传输数据时,有几种不同的使用方式。其中,当我们使用两个参数时,第一个参数是`size_t`类型的内容长度,第二个参数是一个可调用对象,该可调用对象接受两个参数:`size_t offset`和`DataSink &sink`。 但是,错误信息中我们的第一个参数是`long`类型,而库函数期望的是`size_t`类型。因此,我们需要将`chunk_size`转换为`size_t`类型。 另外,我们还需要注意,在lambda表达式中,我们使用了`file`和`chunk_size`的捕获。这里,我们通过值捕获了`chunk_size`(注意它是`long`类型,但在lambda内部使用时应与`size_t`兼容)。同时,我们通过移动捕获了`file`(因为文件流不能复制,只能移动)。 但是,错误提示的是参数类型不匹配,所以主要问题可能是类型转换。我们尝试将`chunk_size`转换为`size_t`。 此外,我们还需要注意,在lambda表达式中,我们并没有使用`offset`参数。在流式传输中,`offset`表示客户端已经接收到的字节偏移量(用于断点续传),但在这里我们的lambda忽略了它,而是从头开始读取。这可能会导致问题,因为分块传输可能要求从指定位置开始读取。 实际上,在范围请求中,我们已经通过`seekg`设置了文件指针的位置,所以我们在lambda中应该从当前位置开始读取,而不是使用`offset`。但是,我们也可以利用`offset`来调整读取位置,但这里我们不需要,因为我们已经定位了文件指针。 然而,我们需要注意的是,在多个分块请求同时进行时,我们不能在lambda中依赖文件指针的当前位置,因为文件指针是共享的?实际上,我们通过移动捕获了`file`,所以每个请求都有自己的文件流对象,因此是安全的。 修改建议: 1. 将`chunk_size`转换为`size_t`类型。 2. 在lambda中,我们并没有使用`offset`,但这是允许的,因为我们的文件指针已经定位到正确的位置。但是,我们需要注意,在写入数据之前,不应该改变文件指针的位置。 但是,根据cpp-httplib的文档,`set_content_provider`的第二个参数(即ContentProvider)的类型应该是: `std::function<bool(size_t offset, DataSink &sink)>` 所以,我们的lambda在参数类型上是匹配的。 所以,问题可能在于第一个参数的类型。我们尝试将`chunk_size`转换为`size_t`。 修改后的代码: ```cpp res.set_content_provider( static_cast<size_t>(chunk_size), // 转换为size_t [file = std::move(file), chunk_size](size_t offset, httplib::DataSink &sink) { // 注意:这里我们忽略了offset,因为我们已经在文件流的正确位置了 std::vector<char> buf(4096); auto remaining = static_cast<size_t>(chunk_size); // 将remaining转换为size_t while (remaining > 0) { auto to_read = std::min(buf.size(), remaining); file.read(buf.data(), to_read); auto bytes_read = file.gcount(); if (bytes_read <= 0) { break; // 读取失败或EOF } sink.write(buf.data(), static_cast<size_t>(bytes_read)); remaining -= static_cast<size_t>(bytes_read); } sink.done(); return true; } ); ``` 但是,我们注意到在lambda中,我们并没有使用`offset`。实际上,在范围请求中,我们可能不需要使用`offset`,因为我们已经将文件指针定位到了正确的位置。但是,如果客户端请求的是不同的范围,那么每次请求都会创建一个新的文件流,并且已经定位到正确的起始位置,所以这样是安全的。 然而,还有一种可能是,客户端可能会分多次请求同一个范围?但实际上,每次请求都是独立的,所以没有问题。 另外,我们还需要注意,如果文件在读取过程中被修改,我们可能读取到不一致的数据,但这不是当前要解决的问题。 如果上述修改后仍然有编译错误,那么可能是lambda的捕获或内部实现与`set_content_provider`的预期不匹配。我们需要检查cpp-httplib的版本,因为不同版本可能有不同的接口。 在较新的版本中,`set_content_provider`可能还有第三个参数(用于清理资源),例如: `set_content_provider(size_t size, ContentProvider provider, ContentProviderWithoutRange resource_releaser)` 但我们这里使用的是两个参数,所以需要确认库的版本。 如果我们的库版本是0.7以上,那么两个参数的重载是存在的。我们可以查看库的头文件确认。 如果问题仍然存在,我们可以尝试使用另一种重载:传递三个参数(包括内容长度、内容提供者、资源释放函数)。我们可以在lambda中捕获文件流,并在资源释放函数中关闭文件。 例如: ```cpp res.set_content_provider( static_cast<size_t>(chunk_size), // 内容提供者 [file = std::move(file), chunk_size](size_t offset, DataSink &sink) mutable { // ... 同上 ... }, // 资源释放函数 [](bool success) { /* 这里可以关闭文件,但我们已经用移动捕获,文件会在lambda析构时关闭 */ } ); ``` 但是,注意在原始代码中,我们通过移动捕获将文件流移动到lambda中,当lambda被销毁时,文件流会自动关闭。所以,我们不需要显式的资源释放函数。 不过,为了兼容性,我们可以尝试使用三个参数的形式,将资源释放函数设为空操作(或者关闭文件,但文件流在lambda析构时会自动关闭,所以我们可以不写)。 但是,查看错误信息,我们使用的是两个参数,而编译器提示没有匹配的两个参数重载,那么可能是库的版本问题。在旧版本中,可能没有两个参数的重载,或者参数类型不同。 因此,我们需要根据实际使用的cpp-httplib版本调整。 假设我们使用的是0.7版本以上,那么两个参数的重载应该是存在的。所以,我们首先尝试类型转换。 如果转换后仍然有错误,请检查库的版本,并查看头文件中的函数声明。 如果无法解决,我们可以尝试使用另一种设置内容提供者的方法:使用`ContentProvider`和`ContentProviderWithoutRange`,但需要更复杂的设置。 不过,根据错误信息,我们的lambda类型是匹配的,所以问题很可能只是第一个参数的类型为`long`,而库期望`size_t`。 因此,请尝试将`chunk_size`转换为`size_t`,然后重新编译。 如果还有问题,请提供更多错误信息或库的版本信息。 注意:在lambda中,我们使用`file.read`,如果读取失败,我们跳出循环并调用`sink.done()`。这应该可以正常结束传输。 另外,我们还需要设置状态码为206,并设置Content-Range头部,因为使用内容提供者并不会自动设置这些。 所以,在设置内容提供者之前,我们应该设置: ```cpp res.status = 206; res.set_header("Content-Range", "bytes " + std::to_string(start) + "-" + std::to_string(start + chunk_size - 1) + "/" + std::to_string(file_size)); ``` 注意:这里我们使用的是`chunk_size`,而不是实际读取的字节数,因为内容提供者会确保读取`chunk_size`字节(除非文件被截断)。如果文件被截断,我们的内容提供者会提前结束,这样客户端会收到不完整的数据,但这是不可避免的。 综上所述,我们修改两个地方: 1. 将`chunk_size`转换为`size_t`。 2. 设置状态码和Content-Range头部。 最终代码片段: ```cpp // 设置范围请求的响应 res.status = 206; res.set_header("Content-Length", std::to_string(chunk_size)); // 可选,因为内容提供者已经指定了大小 res.set_header("Content-Range", "bytes " + std::to_string(start) + "-" + std::to_string(start + chunk_size - 1) + "/" + std::to_string(file_size)); res.set_content_provider( static_cast<size_t>(chunk_size), [file = std::move(file), chunk_size](size_t offset, httplib::DataSink &sink) mutable { std::vector<char> buf(4096); size_t remaining = static_cast<size_t>(chunk_size); while (remaining > 0) { auto to_read = std::min(buf.size(), remaining); file.read(buf.data(), to_read); auto bytes_read = file.gcount(); if (bytes_read <= 0) { break; } sink.write(buf.data(), static_cast<size_t>(bytes_read)); remaining -= static_cast<size_t>(bytes_read); } sink.done(); return true; } ); ``` 注意:在lambda中,我们使用了`mutable`,因为文件流在读取过程中会改变状态。 如果仍然有编译错误,请检查是否遗漏了`mutable`关键字(因为我们需要修改文件流的状态)。 如果问题依旧,请提供更详细的错误信息。 由于我们无法直接测试,以上是基于常见问题的推断。如果问题仍然存在,可能需要查看cpp-httplib的具体实现。 另外,在读取循环中,我们假设每次读取都能读到数据,直到读完。如果文件被截断,我们提前结束,这样客户端会收到不完整的数据,但这是合理的。 最后,我们返回true表示成功(即使提前结束,我们也认为成功,因为已经尽力读取了)。 希望这样能解决问题。 §§相关问题§§ 1. 在C++中,如何正确使用lambda表达式捕获只能移动(move-only)的类型? 2. cpp-httplib库中`set_content_provider`方法的不同重载形式有哪些? 3. 在HTTP分块传输过程中,如何处理文件被外部修改导致的大小变化? 4. 为什么在lambda表达式后需要添加`mutable`关键字?在什么情况下可以省略? 5. 当使用`std::ifstream`读取文件时,如何优化大文件的读取性能?</think>### 错误分析与解决方案 您遇到的编译错误是由于 **`set_content_provider` 方法签名不匹配** 导致的。cpp-httplib 库中该方法有多个重载版本,而您使用的 lambda 签名与库期望的签名不一致。以下是详细分析和解决方案: ### 错误原因分析 1. **方法重载不匹配**: - `set_content_provider` 有两种主要重载: ```cpp // 版本1:带偏移量的内容提供者 void set_content_provider(size_t size, ContentProviderWithOffset provider); // 版本2:不带偏移量的内容提供者(更常用) void set_content_provider(size_t size, ContentProvider provider, ContentProviderResourceReleaser releaser = nullptr); ``` - 您的 lambda `[](size_t offset, httplib::DataSink &sink)->bool` 匹配的是第一个带偏移量的版本 2. **实际需要的是无偏移量版本**: - 在文件传输场景中,通常使用**不带偏移量**的版本 - 正确签名应为:`[](httplib::DataSink &sink)->bool` ### 修复方案 #### 修改内容提供者签名(推荐) ```cpp res.set_content_provider( static_cast<size_t>(chunk_size), // 明确转换为size_t [file = std::move(file), chunk_size](httplib::DataSink &sink) { std::vector<char> buf(4096); size_t remaining = static_cast<size_t>(chunk_size); while (remaining > 0) { size_t to_read = std::min(buf.size(), remaining); file.read(buf.data(), to_read); size_t bytes_read = static_cast<size_t>(file.gcount()); if (bytes_read == 0) break; // 读取结束 if (!sink.write(buf.data(), bytes_read)) { return false; // 写入失败 } remaining -= bytes_read; } sink.done(); return true; } ); ``` #### 完整范围请求处理示例 ```cpp bool handle_range_request(httplib::Response& res, const std::string& file_path, long start, long end) { auto file_size = get_file_size(file_path); if (file_size <= 0) return false; std::ifstream file(file_path, std::ios::binary); if (!file) return false; long chunk_size = end - start + 1; file.seekg(start); // 设置HTTP响应头 res.status = 206; res.set_header("Content-Range", "bytes " + std::to_string(start) + "-" + std::to_string(end) + "/" + std::to_string(file_size)); // 使用正确签名的内容提供者 res.set_content_provider( static_cast<size_t>(chunk_size), [file = std::move(file), chunk_size](httplib::DataSink &sink) mutable { std::vector<char> buffer(8192); // 8KB缓冲区 size_t remaining = static_cast<size_t>(chunk_size); while (remaining > 0) { size_t read_size = std::min(buffer.size(), remaining); file.read(buffer.data(), read_size); size_t bytes_read = static_cast<size_t>(file.gcount()); if (bytes_read == 0) break; if (!sink.write(buffer.data(), bytes_read)) return false; remaining -= bytes_read; } sink.done(); return true; } ); return true; } ``` ### 关键修改点 1. **修正lambda签名**: - 从 `(size_t offset, httplib::DataSink &sink)` 改为 `(httplib::DataSink &sink)` - 添加 `mutable` 关键字允许修改捕获的文件流 2. **类型安全转换**: - 使用 `static_cast<size_t>()` 确保类型匹配 - 所有与文件大小相关的操作都使用 `size_t` 3. **错误处理增强**: - 检查 `file.gcount() == 0` 提前终止读取 - 处理 `sink.write()` 可能返回的失败情况 4. **资源管理优化**: - 使用 `std::move(file)` 转移文件流所有权 - 文件流会在lambda析构时自动关闭 ### 备选方案:使用带偏移量的版本 如果您需要支持随机访问(如断点续传),可以使用带偏移量的版本: ```cpp res.set_content_provider( file_size, // 总文件大小 [file = std::move(file)](size_t offset, httplib::DataSink &sink) { file.seekg(offset); // 定位到请求偏移量 std::vector<char> buf(8192); while (!sink.is_writable() && !file.eof()) { file.read(buf.data(), buf.size()); size_t bytes_read = static_cast<size_t>(file.gcount()); if (bytes_read == 0) break; if (!sink.write(buf.data(), bytes_read)) return false; } return true; }, [](bool success) { /* 资源清理回调 */ } // 可选 ); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值