远程控制开发基础
5、主控端文件下发到被控端
1、课程目标
- 实现从控制端下载文件到被控端的功能
- 掌握文件分发协议设计
- 理解文件推送和远程存储机制
- 处理并发下载和资源管理
2、名词解释
| 术语 | 全称 | 解释 |
|---|---|---|
| Download | 下载 | 从服务器获取文件到客户端 |
| Push | 推送 | 主动发送文件到远程端 |
| Distribution | 分发 | 将文件分发给多个客户端 |
| Concurrent | 并发 | 同时处理多个下载任务 |
3、技术原理
1. 文件分发架构
服务端下发流程:
1. 控制端发送下载请求 (文件名)
2. 服务端确认文件存在并准备传输
3. 服务端分块发送文件数据
4. 客户端接收并验证每块数据
5. 客户端保存文件到指定位置
6. 双方确认传输结果
并发处理:
┌─────────────────────────────────────┐
│ 控制端 │
├─────────────────────────────────────┤
│ 下载任务1 ──→ 客户端1 │
│ 下载任务2 ──→ 客户端2 │
│ 下载任务3 ──→ 客户端3 │
└─────────────────────────────────────┘
2. 协议设计
下载命令流程:
CMD_DOWNLOAD_REQUEST (控制端→服务端)
- 文件名
- 目标路径
- 客户端ID
CMD_DOWNLOAD_START (服务端→客户端)
- 会话ID
- 文件大小
- 块大小
CMD_DOWNLOAD_DATA (服务端→客户端)
- 会话ID
- 块索引
- 块数据
- 校验和
CMD_DOWNLOAD_ACK (客户端→服务端)
- 会话ID
- 块索引
3、代码实现
1. 服务端文件分发
// server_download.cpp
// 服务端文件分发模块
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <vector>
#include <map>
#include <string>
#include <fstream>
#include <mutex>
#pragma comment(lib, "ws2_32.lib")
// 下载会话结构
typedef struct _DOWNLOAD_SESSION {
std::string fileName;
std::string fullPath;
ULONGLONG fileSize;
ULONGLONG sentSize;
DWORD sessionId;
std::ifstream fileStream;
DWORD chunkSize;
DWORD clientId;
time_t startTime;
} DOWNLOAD_SESSION, *PDOWNLOAD_SESSION;
// 全局下载会话管理
std::map<DWORD, DOWNLOAD_SESSION> g_downloadSessions;
std::mutex g_downloadMutex;
DWORD g_nextDownloadId = 1001;
// 文件分发命令
#define CMD_DOWNLOAD_REQUEST 0x2001 // 下载请求
#define CMD_DOWNLOAD_START 0x2002 // 开始下载
#define CMD_DOWNLOAD_DATA 0x2003 // 下载数据
#define CMD_DOWNLOAD_ACK 0x2004 // 确认接收
#define CMD_DOWNLOAD_COMPLETE 0x2005 // 下载完成
#define CMD_DOWNLOAD_ERROR 0x2006 // 下载错误
#pragma pack(push, 1)
// 下载请求包
typedef struct _DOWNLOAD_REQUEST {
DWORD fileNameLen;
DWORD targetPathLen;
DWORD clientId;
} DOWNLOAD_REQUEST, *PDOWNLOAD_REQUEST;
// 下载开始包
typedef struct _DOWNLOAD_START {
DWORD sessionId;
ULONGLONG fileSize;
DWORD chunkSize;
} DOWNLOAD_START, *PDOWNLOAD_START;
// 下载数据包头
typedef struct _DOWNLOAD_DATA_HEADER {
DWORD sessionId;
DWORD chunkIndex;
DWORD chunkSize;
DWORD checksum;
} DOWNLOAD_DATA_HEADER, *PDOWNLOAD_DATA_HEADER;
#pragma pack(pop)
// 检查文件是否存在
bool FileExists(const std::string& path) {
DWORD attrs = GetFileAttributesA(path.c_str());
return (attrs != INVALID_FILE_ATTRIBUTES) &&
!(attrs & FILE_ATTRIBUTE_DIRECTORY);
}
// 获取文件大小
ULONGLONG GetFileSize(const std::string& path) {
HANDLE hFile = CreateFileA(
path.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return 0;
}
LARGE_INTEGER size;
if (!GetFileSizeEx(hFile, &size)) {
CloseHandle(hFile);
return 0;
}
CloseHandle(hFile);
return size.QuadPart;
}
// 创建下载会话
DWORD CreateDownloadSession(const char* fileName, DWORD clientId) {
std::lock_guard<std::mutex> lock(g_downloadMutex);
// 检查文件是否存在
if (!FileExists(fileName)) {
printf("[-] File not found: %s\n", fileName);
return 0;
}
DWORD sessionId = g_nextDownloadId++;
DOWNLOAD_SESSION session = {0};
session.fileName = fileName;
session.fullPath = fileName;
session.fileSize = GetFileSize(fileName);
session.sessionId = sessionId;
session.clientId = clientId;
session.chunkSize = 8192;
session.startTime = time(NULL);
// 打开文件流
session.fileStream.open(fileName, std::ios::binary);
if (!session.fileStream.is_open()) {
printf("[-] Cannot open file: %s\n", fileName);
return 0;
}
g_downloadSessions[sessionId] = session;
printf("[+] Created download session %lu for %s (%llu bytes)\n",
sessionId, fileName, session.fileSize);
return sessionId;
}
// 处理下载请求
bool HandleDownloadRequest(SOCKET clientSock, DWORD clientId,
const DOWNLOAD_REQUEST* req,
const char* fileName,
const char* targetPath) {
printf("[Client %lu] Download request: %s -> %s\n",
clientId, fileName, targetPath);
// 创建下载会话
DWORD sessionId = CreateDownloadSession(fileName, clientId);
if (sessionId == 0) {
return false;
}
// 发送下载开始命令
DOWNLOAD_START startPacket = {0};
startPacket.sessionId = sessionId;
startPacket.fileSize = GetFileSize(fileName);
startPacket.chunkSize = 8192;
MSG_HEADER response;
response.magic = MAGIC_NUMBER;
response.cmdType = CMD_DOWNLOAD_START;
response.dataLen = sizeof(startPacket);
response.clientId = clientId;
send(clientSock, (char*)&response, sizeof(response), 0);
send(clientSock, (char*)&startPacket, sizeof(startPacket), 0);
return true;
}
// 发送文件数据块
bool SendFileChunk(SOCKET clientSock, DWORD clientId, DWORD sessionId, DWORD chunkIndex) {
std::lock_guard<std::mutex> lock(g_downloadMutex);
// 查找会话
if (g_downloadSessions.find(sessionId) == g_downloadSessions.end()) {
printf("[-] Invalid download session: %lu\n", sessionId);
return false;
}
DOWNLOAD_SESSION& session = g_downloadSessions[sessionId];
// 定位到块位置
session.fileStream.seekg(chunkIndex * session.chunkSize);
// 读取数据块
std::vector<BYTE> chunk(session.chunkSize);
session.fileStream.read((char*)chunk.data(), session.chunkSize);
DWORD bytesRead = (DWORD)session.fileStream.gcount();
if (bytesRead == 0) {
return false;
}
// 计算校验和
DWORD checksum = CalculateChecksum(chunk.data(), bytesRead);
// 发送数据包
DOWNLOAD_DATA_HEADER dataHeader = {0};
dataHeader.sessionId = sessionId;
dataHeader.chunkIndex = chunkIndex;
dataHeader.chunkSize = bytesRead;
dataHeader.checksum = checksum;
DWORD packetSize = sizeof(dataHeader) + bytesRead;
std::vector<BYTE> packet(packetSize);
memcpy(packet.data(), &dataHeader, sizeof(dataHeader));
memcpy(packet.data() + sizeof(dataHeader), chunk.data(), bytesRead);
MSG_HEADER dataMsg;
dataMsg.magic = MAGIC_NUMBER;
dataMsg.cmdType = CMD_DOWNLOAD_DATA;
dataMsg.dataLen = packetSize;
dataMsg.clientId = clientId;
send(clientSock, (char*)&dataMsg, sizeof(dataMsg), 0);
send(clientSock, (char*)packet.data(), packetSize, 0);
// 更新发送状态
session.sentSize += bytesRead;
// 显示进度
double progress = (double)session.sentSize / session.fileSize * 100;
printf("\r[Session %lu] Progress: %.1f%% (%llu/%llu)",
sessionId, progress, session.sentSize, session.fileSize);
fflush(stdout);
return true;
}
// 处理ACK确认
bool HandleDownloadAck(SOCKET clientSock, DWORD clientId,
DWORD sessionId, DWORD chunkIndex) {
// 这里可以记录已确认的块,用于重传控制
// 简化实现,直接发送下一个块
std::lock_guard<std::mutex> lock(g_downloadMutex);
if (g_downloadSessions.find(sessionId) == g_downloadSessions.end()) {
return false;
}
DOWNLOAD_SESSION& session = g_downloadSessions[sessionId];
// 检查是否还有更多数据
if (session.sentSize < session.fileSize) {
// 发送下一个块
DWORD nextChunk = chunkIndex + 1;
SendFileChunk(clientSock, clientId, sessionId, nextChunk);
} else {
// 发送完成信号
MSG_HEADER completeMsg;
completeMsg.magic = MAGIC_NUMBER;
completeMsg.cmdType = CMD_DOWNLOAD_COMPLETE;
completeMsg.dataLen = sizeof(DWORD);
completeMsg.clientId = clientId;
send(clientSock, (char*)&completeMsg, sizeof(completeMsg), 0);
send(clientSock, (char*)&sessionId, sizeof(sessionId), 0);
printf("\n[+] Download completed for session %lu\n", sessionId);
// 清理会话
session.fileStream.close();
g_downloadSessions.erase(sessionId);
}
return true;
}
// 处理下载完成
bool HandleDownloadComplete(SOCKET clientSock, DWORD clientId, DWORD sessionId) {
std::lock_guard<std::mutex> lock(g_downloadMutex);
if (g_downloadSessions.find(sessionId) == g_downloadSessions.end()) {
return false;
}
DOWNLOAD_SESSION& session = g_downloadSessions[sessionId];
printf("\n[+] Download completed: %s (session %lu)\n",
session.fileName.c_str(), sessionId);
// 清理会话
session.fileStream.close();
g_downloadSessions.erase(sessionId);
return true;
}
// 列出活动下载
void ListActiveDownloads() {
std::lock_guard<std::mutex> lock(g_downloadMutex);
printf("\n=== Active Download Sessions ===\n");
if (g_downloadSessions.empty()) {
printf("No active sessions\n");
return;
}
for (const auto& pair : g_downloadSessions) {
const DOWNLOAD_SESSION& session = pair.second;
double progress = (double)session.sentSize / session.fileSize * 100;
printf("Session %lu: %s -> Client %lu (%.1f%%)\n",
session.sessionId,
session.fileName.c_str(),
session.clientId,
progress);
}
printf("\n");
}
// 服务端集成函数
bool ServerHandleDownload(SOCKET clientSock, DWORD clientId,
DWORD cmdType, const BYTE* data, DWORD dataLen) {
switch (cmdType) {
case CMD_DOWNLOAD_REQUEST: {
if (dataLen < sizeof(DOWNLOAD_REQUEST)) return false;
PDOWNLOAD_REQUEST req = (PDOWNLOAD_REQUEST)data;
const char* fileName = (const char*)(data + sizeof(DOWNLOAD_REQUEST));
const char* targetPath = fileName + req->fileNameLen;
return HandleDownloadRequest(clientSock, clientId, req, fileName, targetPath);
}
case CMD_DOWNLOAD_ACK: {
if (dataLen < sizeof(DWORD) * 2) return false;
DWORD* ackData = (DWORD*)data;
DWORD sessionId = ackData[0];
DWORD chunkIndex = ackData[1];
return HandleDownloadAck(clientSock, clientId, sessionId, chunkIndex);
}
case CMD_DOWNLOAD_COMPLETE: {
if (dataLen < sizeof(DWORD)) return false;
DWORD sessionId = *(DWORD*)data;
return HandleDownloadComplete(clientSock, clientId, sessionId);
}
}
return false;
}
2. 客户端文件接收
// client_download.cpp
// 客户端文件接收模块
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <fstream>
#include <vector>
#include <string>
#pragma comment(lib, "ws2_32.lib")
extern SOCKET g_sock;
extern DWORD g_clientId;
// 当前下载会话
typedef struct _CURRENT_DOWNLOAD {
DWORD sessionId;
std::string targetPath;
ULONGLONG fileSize;
ULONGLONG receivedSize;
DWORD chunkSize;
std::ofstream fileStream;
bool isActive;
} CURRENT_DOWNLOAD, *PCURRENT_DOWNLOAD;
CURRENT_DOWNLOAD g_currentDownload = {0};
// 请求下载文件
bool RequestDownload(const char* serverPath, const char* localPath) {
printf("[*] Requesting download: %s -> %s\n", serverPath, localPath);
// 发送下载请求
DOWNLOAD_REQUEST req = {0};
req.fileNameLen = (DWORD)strlen(serverPath);
req.targetPathLen = (DWORD)strlen(localPath);
req.clientId = g_clientId;
DWORD totalSize = sizeof(req) + req.fileNameLen + req.targetPathLen;
std::vector<BYTE> requestData(totalSize);
memcpy(requestData.data(), &req, sizeof(req));
memcpy(requestData.data() + sizeof(req), serverPath, req.fileNameLen);
memcpy(requestData.data() + sizeof(req) + req.fileNameLen, localPath, req.targetPathLen);
MSG_HEADER header;
header.magic = MAGIC_NUMBER;
header.cmdType = CMD_DOWNLOAD_REQUEST;
header.dataLen = totalSize;
header.clientId = g_clientId;
send(g_sock, (char*)&header, sizeof(header), 0);
send(g_sock, (char*)requestData.data(), totalSize, 0);
printf("[+] Download request sent\n");
return true;
}
// 处理下载开始
bool HandleDownloadStart(const DOWNLOAD_START* start) {
printf("[+] Download starting (session %lu)\n", start->sessionId);
// 初始化下载会话
g_currentDownload.sessionId = start->sessionId;
g_currentDownload.fileSize = start->fileSize;
g_currentDownload.chunkSize = start->chunkSize;
g_currentDownload.receivedSize = 0;
g_currentDownload.isActive = true;
// 创建本地文件
g_currentDownload.fileStream.open(g_currentDownload.targetPath,
std::ios::binary | std::ios::out);
if (!g_currentDownload.fileStream.is_open()) {
printf("[-] Cannot create local file\n");
return false;
}
printf("[*] Ready to receive %llu bytes\n", start->fileSize);
// 发送第一个ACK
DWORD ackData[2] = {start->sessionId, 0}; // chunkIndex = 0
MSG_HEADER ack;
ack.magic = MAGIC_NUMBER;
ack.cmdType = CMD_DOWNLOAD_ACK;
ack.dataLen = sizeof(ackData);
ack.clientId = g_clientId;
send(g_sock, (char*)&ack, sizeof(ack), 0);
send(g_sock, (char*)ackData, sizeof(ackData), 0);
return true;
}
// 处理文件数据
bool HandleDownloadData(const DOWNLOAD_DATA_HEADER* header, const BYTE* data) {
if (!g_currentDownload.isActive || header->sessionId != g_currentDownload.sessionId) {
return false;
}
// 验证校验和
if (!VerifyChecksum(data, header->chunkSize, header->checksum)) {
printf("[-] Checksum error for chunk %lu\n", header->chunkIndex);
return false;
}
// 写入文件
g_currentDownload.fileStream.seekp(header->chunkIndex * g_currentDownload.chunkSize);
g_currentDownload.fileStream.write((char*)data, header->chunkSize);
// 更新状态
g_currentDownload.receivedSize += header->chunkSize;
// 显示进度
double progress = (double)g_currentDownload.receivedSize / g_currentDownload.fileSize * 100;
printf("\rProgress: %.1f%% (%llu/%llu)",
progress, g_currentDownload.receivedSize, g_currentDownload.fileSize);
fflush(stdout);
// 发送ACK
DWORD ackData[2] = {header->sessionId, header->chunkIndex};
MSG_HEADER ack;
ack.magic = MAGIC_NUMBER;
ack.cmdType = CMD_DOWNLOAD_ACK;
ack.dataLen = sizeof(ackData);
ack.clientId = g_clientId;
send(g_sock, (char*)&ack, sizeof(ack), 0);
send(g_sock, (char*)ackData, sizeof(ackData), 0);
return true;
}
// 处理下载完成
bool HandleDownloadComplete(DWORD sessionId) {
if (!g_currentDownload.isActive || sessionId != g_currentDownload.sessionId) {
return false;
}
// 关闭文件
g_currentDownload.fileStream.close();
printf("\n[+] Download completed: %s\n", g_currentDownload.targetPath.c_str());
// 重置状态
g_currentDownload.isActive = false;
g_currentDownload.sessionId = 0;
return true;
}
// 客户端下载处理函数
bool ClientHandleDownload(DWORD cmdType, const BYTE* data, DWORD dataLen) {
switch (cmdType) {
case CMD_DOWNLOAD_START: {
if (dataLen < sizeof(DOWNLOAD_START)) return false;
return HandleDownloadStart((PDOWNLOAD_START)data);
}
case CMD_DOWNLOAD_DATA: {
if (dataLen < sizeof(DOWNLOAD_DATA_HEADER)) return false;
PDOWNLOAD_DATA_HEADER header = (PDOWNLOAD_DATA_HEADER)data;
const BYTE* fileData = data + sizeof(DOWNLOAD_DATA_HEADER);
return HandleDownloadData(header, fileData);
}
case CMD_DOWNLOAD_COMPLETE: {
if (dataLen < sizeof(DWORD)) return false;
DWORD sessionId = *(DWORD*)data;
return HandleDownloadComplete(sessionId);
}
}
return false;
}
// 下载命令处理
void HandleDownloadCommand(const char* cmdLine) {
if (strncmp(cmdLine, "download ", 9) != 0) return;
const char* params = cmdLine + 9;
const char* spacePos = strchr(params, ' ');
if (spacePos) {
// download server_path local_path
std::string serverPath(params, spacePos - params);
std::string localPath(spacePos + 1);
RequestDownload(serverPath.c_str(), localPath.c_str());
} else {
// download server_path (使用相同文件名)
const char* fileName = strrchr(params, '/');
if (!fileName) fileName = strrchr(params, '\\');
if (!fileName) fileName = params;
else fileName++;
RequestDownload(params, fileName);
}
}
// 测试下载功能
void TestDownload() {
printf("=== File Download Test ===\n");
// 在服务端创建测试文件
std::ofstream testFile("server_test.txt");
testFile << "This is a test file for download functionality.\n";
testFile << "It contains multiple lines of text.\n";
testFile << "Used to verify the download mechanism.\n";
testFile.close();
printf("[*] Created server test file\n");
// 模拟下载请求
g_currentDownload.targetPath = "client_downloaded.txt";
RequestDownload("server_test.txt", "client_downloaded.txt");
printf("[*] Download test initiated\n");
}
3. 完整集成示例
// download_integration.cpp
// 文件下载完整集成示例
#include <iostream>
#include <thread>
#include <chrono>
// 模拟服务端下载处理
void SimulateServerDownload() {
printf("=== Simulated Server Download ===\n");
printf("Ready to serve download requests...\n");
// 这里应该是实际的服务端下载逻辑
// 等待客户端下载请求并处理
std::this_thread::sleep_for(std::chrono::seconds(3));
printf("[Server] Download simulation ready\n");
}
// 模拟客户端下载
void SimulateClientDownload() {
printf("=== Simulated Client Download ===\n");
// 模拟接收文件
printf("[Client] Preparing to receive file...\n");
ULONGLONG totalSize = 512 * 1024; // 512KB
ULONGLONG received = 0;
DWORD chunkSize = 8192;
while (received < totalSize) {
DWORD currentChunk = (DWORD)min(chunkSize, totalSize - received);
received += currentChunk;
double progress = (double)received / totalSize * 100;
printf("\r[Client] Download progress: %.1f%%", progress);
fflush(stdout);
std::this_thread::sleep_for(std::chrono::milliseconds(5)); // 模拟网络延迟
}
printf("\n[Client] Download completed\n");
}
int main() {
printf("========================================\n");
printf(" File Download System Simulation \n");
printf("========================================\n\n");
// 启动模拟服务端线程
std::thread serverThread(SimulateServerDownload);
// 稍微延迟后启动客户端
std::this_thread::sleep_for(std::chrono::milliseconds(100));
SimulateClientDownload();
// 等待服务端完成
serverThread.join();
printf("\n[*] File download system simulation completed\n");
return 0;
}
4、课后作业
4.1、作业1:实现并发下载
支持同时从多个客户端下载不同的文件。
4.2、作业2:添加文件校验
在下载完成后验证文件完整性(MD5/SHA1)。
4.3、作业3:实现断点续传下载
添加下载中断后能够从中断点继续下载的功能。