远程控制开发基础

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

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