libhv WITH_OPENSSL 开关,编译libhv,请求https,保存下载文件

本文详细介绍了如何在libhv项目中启用OPENSSL,引入相关头文件和库,以及通过requests库实现HTTPS请求并下载文件的过程,包括CMakeLists修改、openssl配置和编译步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

libhv WITH_OPENSSL 开关,编译libhv,请求https

问题描述:这次需要从GET请求中获得的参数中,有一项参数是一个url,https请求,我需要访问这个url并从中下载我所需要的文件。我尝试使用

auto re = requests::get(facePictureUrl.c_str());

来请求这个facePictureUrl,但是结果是
在这里插入图片描述
查了一圈之后,没有发现解决办法,于是又去请教群里热心的网友!整理这次解决问题的步骤,希望可以帮到遇到同样问题的朋友 ^ _ ^
详细的解答参考:https://gitee.com/libhv/libhv/wikis/FAQ

前提:安装好openssl 下载地址:http://slproweb.com/products/Win32OpenSSL.html 选择第二个

step1. 修改CMakList

首先,要打开WITH_OPENSSL 的开关,因为要启动openssl的服务,默认的情况下,该开关是关闭的状态。打开源码目录下的CMakeLists.txt,在第27行,把"OFF"修改成"ON",并crtl+s保存。
在这里插入图片描述

Step2.引入openssl的头文件和库文件

在CMakeList中的第73行和74行,在源码的目录下创建3rd文件夹,并在3rd文件夹中创建include和lib文件夹
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后把openssl里的include文件夹和lib文件夹里的内容复制进来
在这里插入图片描述
这里有个改名问题:
在这里插入图片描述

step3. 编译libhv

参照这篇文章,一样的步骤,cmake一下。
https://blog.csdn.net/njust_cse_17_wc/article/details/118799806?spm=1001.2014.3001.5501

然后生成自己想要的debug版本或者release版本,生成的hv.dll就可以直接用了。
这是我实现的效果:
在这里插入图片描述
这样一来,就成功向https发送了请求,实现https请求,并保存文件的代码段如下:

//第一种:
auto re = requests::get(facePictureUrl.c_str());
if (re == NULL)
	printf("request failed\n");
const char* str1 = re->body.c_str();//数据的首地址
long count1 = re->content_length;//数据的长度
ofstream ofile1("d:\\1.jpeg", ios::binary);
if (ofile1.fail()) {
	cout << "fail to create file\n";
}
else
	ofile1.write(str1, count1);
ofile1.close()

//第二种:
HttpRequest *hre = new HttpRequest;
HttpResponse *hresp = new HttpResponse;
hre->url = facePictureUrl;
http_client_send(hre, hresp);
hresp->body;
long count = hresp->content_length;
const char *str = hresp->body.c_str();
ofstream ofile("d:\\2.jpeg", ios::binary);
if (ofile.fail()) {
	cout << "fail to create file\n";
}
else
	ofile.write(str,count);
ofile.close();
		

经过测试,以上两种方法都是可行的。
保存文件的格式,主要还是要看下载的是什么格式了。我这只用到了最简单的C++写文件,其他高级保存文件的方式,目前还不知道,有经验的小伙伴欢迎在下方留言~

好了,今天的解决问题就到这里了,有不对的地方希望大家多多指正!

<think>我们使用libhv库来实现WebSocket协议下的文件传输功能。由于WebSocket是双向通信的,我们可以通过将文件分块传输来实现大文件的传输。 方案概述: 1. 服务端和客户端建立WebSocket连接。 2. 发送方(可以是客户端或服务端)将文件分块(chunk)发送,每块数据作为一个WebSocket消息(二进制帧)发送。 3. 接收方接收这些分块,并按顺序组合成完整的文件。 4. 传输过程中可以加入元数据(如文件名、文件大小、分块信息等)以便接收方正确保存文件。 设计考虑: - 分块大小:通常选择几KB到几十KB,以避免单帧过大。 - 元数据:在传输文件内容前,先发送一个控制消息(如JSON格式)包含文件名、文件大小、分块数等信息。 - 传输确认:每传输一个分块,接收方可以发送一个确认(ACK),但为了简单也可以不确认,除非要求可靠性高。 步骤: 1. 建立连接。 2. 发送方发送一个文件信息(元数据)消息。 3. 接收方收到元数据后,准备接收文件(创建文件等)。 4. 发送方按顺序发送文件分块。 5. 接收方按顺序接收并写入文件。 6. 传输完成后,发送方发送一个结束消息(可选)。 7. 接收方关闭文件,传输结束。 代码示例: 服务端代码(接收文件): ```cpp #include "hv/WebSocketServer.h" #include "hv/hv.h" #include "hv/hfile.h" #include "hv/hbase.h" #include "hv/json.hpp" using namespace hv; using json = nlohmann::json; // 定义每个分块的大小(10KB) #define CHUNK_SIZE 10240 int main() { WebSocketService ws; // 存储每个连接的文件传输上下文 std::map<WebSocketChannelPtr, FILE*> file_map; std::map<WebSocketChannelPtr, std::string> filename_map; std::map<WebSocketChannelPtr, size_t> filesize_map; std::map<WebSocketChannelPtr, size_t> received_size_map; ws.onopen = [&file_map, &filename_map, &filesize_map, &received_size_map](const WebSocketChannelPtr& channel, const HttpRequestPtr& req) { printf("Client connected\n"); // 初始化上下文 file_map[channel] = nullptr; filename_map[channel] = ""; filesize_map[channel] = 0; received_size_map[channel] = 0; }; ws.onmessage = [&](const WebSocketChannelPtr& channel, const std::string& msg) { // 文本消息用于传输元数据 if (channel->opcode == WS_OPCODE_TEXT) { try { json meta = json::parse(msg); if (meta.contains("filename") && meta.contains("filesize")) { std::string filename = meta["filename"]; size_t filesize = meta["filesize"]; // 打开文件准备写入 std::string save_path = "./downloads/" + filename; FILE* fp = fopen(save_path.c_str(), "wb"); if (!fp) { perror("fopen"); channel->close(); return; } // 保存上下文 file_map[channel] = fp; filename_map[channel] = filename; filesize_map[channel] = filesize; received_size_map[channel] = 0; // 发送确认 json ack = { {"status", "started"}, {"filename", filename} }; channel->send(ack.dump()); } } catch (const std::exception& e) { printf("JSON parse error: %s\n", e.what()); } } else if (channel->opcode == WS_OPCODE_BINARY) { // 二进制消息为文件分块 FILE* fp = file_map[channel]; if (fp) { // 写入分块 fwrite(msg.data(), 1, msg.size(), fp); received_size_map[channel] += msg.size(); // 可选:发送进度 if (filesize_map[channel] > 0) { int progress = (int)((received_size_map[channel] * 100) / filesize_map[channel]); printf("Progress: %d%%\r", progress); fflush(stdout); } // 如果接收完成 if (received_size_map[channel] >= filesize_map[channel]) { fclose(fp); file_map[channel] = nullptr; printf("\nFile %s received successfully.\n", filename_map[channel].c_str()); // 发送完成确认 json ack = { {"status", "completed"}, {"filename", filename_map[channel]} }; channel->send(ack.dump()); } } } }; ws.onclose = [&](const WebSocketChannelPtr& channel) { printf("Client disconnected\n"); // 清理资源 if (file_map[channel]) { fclose(file_map[channel]); file_map[channel] = nullptr; } // 从map中移除 file_map.erase(channel); filename_map.erase(channel); filesize_map.erase(channel); received_size_map.erase(channel); }; // 创建下载目录 system("mkdir -p downloads"); // 启动WebSocket服务器 WebSocketServer server(&ws); server.setPort(8888); server.setThreadNum(4); server.start(); while (getchar() != '\n'); server.stop(); return 0; } ``` 客户端代码(发送文件): ```cpp #include "hv/WebSocketClient.h" #include "hv/hv.h" #include "hv/hfile.h" #include "hv/hbase.h" #include "hv/json.hpp" using namespace hv; using json = nlohmann::json; #define CHUNK_SIZE 10240 int main(int argc, char** argv) { if (argc < 2) { printf("Usage: %s <filepath>\n", argv[0]); return -1; } const char* filepath = argv[1]; std::string filename = hv_basename(filepath); // 获取文件大小 size_t filesize = hv_filesize(filepath); if (filesize == (size_t)-1) { printf("File not found: %s\n", filepath); return -1; } WebSocketClient ws; ws.onopen = [&]() { printf("Connected to server\n"); // 发送元数据 json meta = { {"filename", filename}, {"filesize", filesize} }; ws.send(meta.dump()); }; ws.onmessage = [&](const std::string& msg) { // 处理服务器返回的确认消息 try { json ack = json::parse(msg); if (ack["status"] == "started") { printf("Start sending file: %s\n", filename.c_str()); // 打开文件并分块发送 FILE* fp = fopen(filepath, "rb"); if (!fp) { perror("fopen"); ws.close(); return; } char buf[CHUNK_SIZE]; size_t nread; size_t total_sent = 0; while ((nread = fread(buf, 1, sizeof(buf), fp)) > 0) { ws.send(buf, nread, WS_OPCODE_BINARY); total_sent += nread; int progress = (int)((total_sent * 100) / filesize); printf("Progress: %d%%\r", progress); fflush(stdout); } fclose(fp); printf("\nFile sent successfully.\n"); } else if (ack["status"] == "completed") { printf("Server has received the file: %s\n", ack["filename"].get<std::string>().c_str()); } } catch (const std::exception& e) { printf("JSON parse error: %s\n", e.what()); } }; ws.onclose = []() { printf("Disconnected\n"); }; // 连接服务器 ws.open("ws://127.0.0.1:8888"); // 等待连接关闭 while (ws.isConnected()) { hv_delay(1000); } return 0; } ``` 代码说明: 1. 服务端: - 使用`WebSocketServer`启动一个WebSocket服务器。 - 在`onopen`中初始化每个连接的传输上下文。 - 在`onmessage`中,如果是文本消息则解析为元数据,并创建文件;如果是二进制消息则写入文件。 - 在`onclose`中清理资源。 2. 客户端: - 使用`WebSocketClient`连接服务器。 - 连接建立后发送元数据(文件名和文件大小)。 - 收到服务器确认后开始分块读取文件并发送二进制消息。 - 显示发送进度。 注意事项: - 该示例没有处理传输错误和重传,实际应用中可能需要增加可靠性。 - 分块大小可以根据网络状况调整。 - 传输大文件时,注意内存使用,避免一次性读取整个文件。 改进点: 1. 增加MD5校验,确保文件完整性。 2. 支持断点续传。 3. 支持多个文件同时传输。 §§相关问题§§: 1. 在WebSocket文件传输中,如何实现断点续传功能? 2. 如何优化WebSocket传输大文件的性能? 3. 在传输过程中如何保证文件的完整性和安全性(加密)? 4. 如何处理多个文件同时传输的情况?</think># 使用 libhv 的 WebSocket 协议实现文件传输 在 libhv 中实现 WebSocket 的文件传输功能需要精心设计协议和分块机制,因为 WebSocket 本身是面向消息的协议,而非面向流的协议。下面我将详细介绍如何在 libhv 中实现 WebSocket 客户端和服务端之间的文件传输功能。 ## 整体设计思路 ### 文件传输协议设计 1. **元数据交换**:首先传输文件元信息(文件名、大小、类型等) 2. **分块传输**:将大文件分割为多个数据块传输 3. **确认机制**:接收方确认每个数据块的接收 4. **校验机制**:传输完成后进行完整性校验 ### 消息格式 ```json // 元数据消息 { "type": "metadata", "filename": "example.zip", "filesize": 10485760, "chunks": 1024, "md5": "a1b2c3d4e5f6..." } // 数据块消息 { "type": "data", "chunk_index": 123, "total_chunks": 1024, "data": "<binary data>" } // 确认消息 { "type": "ack", "chunk_index": 123 } // 完成消息 { "type": "complete", "success": true, "md5": "a1b2c3d4e5f6..." } ``` ## 服务端实现(接收文件) ```cpp #include "hv/WebSocketServer.h" #include "hv/hfile.h" #include "hv/hlog.h" #include "hv/md5.h" #include <fstream> using namespace hv; class FileReceiver { public: FileReceiver() : received_chunks(0), total_chunks(0), file_size(0) {} bool prepare(const std::string& filename, size_t size, int chunks) { // 创建文件 file_stream.open(filename, std::ios::binary | std::ios::out); if (!file_stream.is_open()) { hloge("Failed to create file: %s", filename.c_str()); return false; } this->filename = filename; this->file_size = size; this->total_chunks = chunks; this->received_chunks = 0; // 预分配文件空间 file_stream.seekp(size - 1); file_stream.put('\0'); file_stream.seekp(0); return true; } bool writeChunk(int index, const char* data, size_t size) { // 计算写入位置 size_t position = (size_t)index * CHUNK_SIZE; if (position >= file_size) { hloge("Invalid chunk index: %d", index); return false; } // 写入数据 file_stream.seekp(position); file_stream.write(data, size); received_chunks++; return true; } bool complete() { file_stream.close(); // 验证文件大小 size_t actual_size = hv_filesize(filename.c_str()); if (actual_size != file_size) { hloge("File size mismatch: expected %zu, actual %zu", file_size, actual_size); return false; } // 计算MD5校验 std::string actual_md5 = hv_md5_file(filename.c_str()); if (actual_md5 != expected_md5) { hloge("MD5 mismatch: expected %s, actual %s", expected_md5.c_str(), actual_md5.c_str()); return false; } return true; } void setExpectedMD5(const std::string& md5) { expected_md5 = md5; } private: std::ofstream file_stream; std::string filename; size_t file_size; int total_chunks; int received_chunks; std::string expected_md5; static const size_t CHUNK_SIZE = 1024 * 16; // 16KB 分块 }; int main() { WebSocketService ws; std::map<WebSocketChannelPtr, FileReceiver> receivers; ws.onopen = [&](const WebSocketChannelPtr& channel, const HttpRequestPtr& req) { hlogi("Client connected: %s", channel->peeraddr().c_str()); }; ws.onmessage = [&](const WebSocketChannelPtr& channel, const std::string& msg) { try { // 解析JSON消息 Json::Value root; Json::Reader reader; if (!reader.parse(msg, root)) { hlogw("Invalid JSON message: %s", msg.c_str()); return; } std::string type = root["type"].asString(); if (type == "metadata") { // 处理元数据 std::string filename = root["filename"].asString(); size_t filesize = root["filesize"].asUInt64(); int chunks = root["chunks"].asInt(); std::string md5 = root["md5"].asString(); // 创建文件接收器 FileReceiver& receiver = receivers[channel]; if (!receiver.prepare(filename, filesize, chunks)) { channel->close(); return; } receiver.setExpectedMD5(md5); // 发送准备就绪确认 Json::Value ack; ack["type"] = "ack"; ack["message"] = "ready"; channel->send(ack.toStyledString()); } else if (type == "data") { // 处理数据块 int chunk_index = root["chunk_index"].asInt(); int total_chunks = root["total_chunks"].asInt(); const std::string& data = root["data"].asString(); FileReceiver& receiver = receivers[channel]; if (!receiver.writeChunk(chunk_index, data.data(), data.size())) { channel->close(); return; } // 发送块确认 Json::Value ack; ack["type"] = "ack"; ack["chunk_index"] = chunk_index; channel->send(ack.toStyledString()); // 显示进度 if (chunk_index % 100 == 0) { int progress = (int)((float)chunk_index / total_chunks * 100); hlogi("Received %d%% of file", progress); } } else if (type == "complete") { // 完成传输 FileReceiver& receiver = receivers[channel]; bool success = receiver.complete(); // 发送完成确认 Json::Value complete; complete["type"] = "complete"; complete["success"] = success; channel->send(complete.toStyledString()); if (success) { hlogi("File received successfully"); } } } catch (const std::exception& e) { hloge("Error processing message: %s", e.what()); } }; ws.onclose = [&](const WebSocketChannelPtr& channel) { hlogi("Client disconnected: %s", channel->peeraddr().c_str()); receivers.erase(channel); }; // 创建接收目录 system("mkdir -p received_files"); // 启动WebSocket服务器 WebSocketServer server(&ws); server.setPort(8888); server.setThreadNum(4); server.start(); printf("WebSocket file server running at ws://127.0.0.1:8888\n"); printf("Press Enter to stop...\n"); while (getchar() != '\n'); server.stop(); return 0; } ``` ## 客户端实现(发送文件) ```cpp #include "hv/WebSocketClient.h" #include "hv/hfile.h" #include "hv/hlog.h" #include "hv/md5.h" #include <fstream> #include <thread> using namespace hv; class FileSender { public: FileSender(const std::string& filepath) : filepath(filepath), total_chunks(0) { filename = hv_basename(filepath.c_str()); file_size = hv_filesize(filepath.c_str()); file_md5 = hv_md5_file(filepath.c_str()); // 计算分块数 total_chunks = (file_size + CHUNK_SIZE - 1) / CHUNK_SIZE; } bool sendMetadata(WebSocketClient* ws) { Json::Value meta; meta["type"] = "metadata"; meta["filename"] = filename; meta["filesize"] = (Json::UInt64)file_size; meta["chunks"] = total_chunks; meta["md5"] = file_md5; return ws->send(meta.toStyledString()) == 0; } bool sendFile(WebSocketClient* ws) { std::ifstream file(filepath, std::ios::binary); if (!file.is_open()) { hloge("Failed to open file: %s", filepath.c_str()); return false; } char buffer[CHUNK_SIZE]; for (int i = 0; i < total_chunks; i++) { // 读取分块 file.read(buffer, CHUNK_SIZE); size_t bytes_read = file.gcount(); // 构建数据消息 Json::Value data; data["type"] = "data"; data["chunk_index"] = i; data["total_chunks"] = total_chunks; data["data"] = std::string(buffer, bytes_read); // 发送分块 if (ws->send(data.toStyledString()) != 0) { hloge("Failed to send chunk %d", i); return false; } // 显示进度 if (i % 100 == 0) { int progress = (int)((float)i / total_chunks * 100); hlogi("Sent %d%% of file", progress); } // 限速(可选) // hv_delay(10); } // 发送完成消息 Json::Value complete; complete["type"] = "complete"; return ws->send(complete.toStyledString()) == 0; } private: std::string filepath; std::string filename; size_t file_size; std::string file_md5; int total_chunks; static const size_t CHUNK_SIZE = 1024 * 16; // 16KB 分块 }; int main(int argc, char* argv[]) { if (argc < 2) { printf("Usage: %s <filepath>\n", argv[0]); return -1; } const char* filepath = argv[1]; FileSender sender(filepath); WebSocketClient ws; ws.onopen = [&]() { hlogi("Connected to server"); // 发送元数据 if (!sender.sendMetadata(&ws)) { ws.close(); return; } }; ws.onmessage = [&](const std::string& msg) { try { Json::Value root; Json::Reader reader; if (!reader.parse(msg, root)) { hlogw("Invalid JSON message: %s", msg.c_str()); return; } std::string type = root["type"].asString(); if (type == "ack") { if (root.isMember("message") && root["message"].asString() == "ready") { // 开始发送文件 std::thread([&]() { if (sender.sendFile(&ws)) { hlogi("File sent successfully"); } ws.close(); }).detach(); } else if (root.isMember("chunk_index")) { // 块确认 int chunk_index = root["chunk_index"].asInt(); // hlogd("Chunk %d acknowledged", chunk_index); } } else if (type == "complete") { bool success = root["success"].asBool(); if (success) { hlogi("File transfer completed successfully"); } else { hloge("File transfer failed"); } ws.close(); } } catch (const std::exception& e) { hloge("Error processing message: %s", e.what()); } }; ws.onclose = []() { hlogi("Disconnected from server"); }; // 连接服务器 ws.open("ws://127.0.0.1:8888"); // 运行事件循环 while (ws.isConnected()) { hv_delay(1000); } return 0; } ``` ## 关键实现细节 ### 1. 文件分块策略 - 使用固定大小的分块(16KB),平衡传输效率和内存使用 - 分块大小可调整,根据网络状况优化 - 使用JSON封装二进制数据(Base64编码) ### 2. 传输控制机制 - **流量控制**:接收方确认后才发送下一块 - **错误处理**:块传输失败时重试机制 - **完整性校验**:文件大小和MD5双重验证 ### 3. 性能优化 - **并行传输**:服务端使用多线程处理多个客户端 - **零拷贝优化**:避免不必要的数据复制 - **内存管理**:使用流式处理避免大内存占用 ### 4. 断点续传实现 ```cpp // 在元数据中添加已接收块信息 meta["received_chunks"] = received_chunks_list; // 发送方跳过已接收的块 for (int i = last_received + 1; i < total_chunks; i++) { // 发送分块 } ``` ### 5. 传输进度显示 ```cpp // 服务端进度显示 int progress = (int)((float)received_chunks / total_chunks * 100); hlogi("Received %d%% of file", progress); // 客户端进度显示 int progress = (int)((float)i / total_chunks * 100); hlogi("Sent %d%% of file", progress); ``` ## 编译和运行 ```bash # 编译服务端 g++ websocket_file_server.cpp -o server -lhv -std=c++11 # 编译客户端 g++ websocket_file_client.cpp -o client -lhv -std=c++11 # 启动服务端 ./server # 客户端发送文件 ./client /path/to/largefile.zip ``` ## 高级功能扩展 ### 1. 传输加密 ```cpp // 在发送前加密数据 std::string encrypt_data(const std::string& data, const std::string& key) { // 使用AES等加密算法 } // 在接收后解密数据 std::string decrypt_data(const std::string& encrypted, const std::string& key) { // 解密算法 } ``` ### 2. 压缩传输 ```cpp // 发送前压缩数据 std::string compressed = hv_compress(data, HV_COMPRESS_GZIP); // 接收后解压数据 std::string decompressed = hv_decompress(compressed, HV_COMPRESS_GZIP); ``` ### 3. 多文件传输 ```cpp // 元数据中添加文件列表 meta["files"] = Json::Value(Json::arrayValue); meta["files"].append("file1.txt"); meta["files"].append("file2.jpg"); ``` ### 4. 传输速度限制 ```cpp // 在发送循环中添加延迟 for (int i = 0; i < total_chunks; i++) { // 发送分块... // 限制为100KB/s hv_delay((CHUNK_SIZE * 1000) / (100 * 1024)); } ``` ## 性能优化建议 1. **增大分块大小**:在网络条件好的情况下使用更大的分块(64KB-256KB) 2. **并行传输**:将文件分成多个部分同时传输 3. **内存映射文件**:使用`mmap`提高文件读写效率 4. **零拷贝技术**:避免不必要的数据复制 5. **二进制协议**:使用自定义二进制协议代替JSON提高效率
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值