远程控制开发基础

5、主控端文件下发到被控端

1、课程目标

  1. 实现从控制端下载文件到被控端的功能
  2. 掌握文件分发协议设计
  3. 理解文件推送和远程存储机制
  4. 处理并发下载和资源管理

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:实现断点续传下载

添加下载中断后能够从中断点继续下载的功能。