远程控制开发基础

4、被控端文件取回服务端

1、课程目标

  1. 实现从被控端上传文件到控制端的功能
  2. 掌握文件传输协议设计
  3. 理解分块传输和断点续传机制
  4. 处理大文件传输的内存管理

2、名词解释

术语 全称 解释
Upload 上传 从客户端发送文件到服务器
Chunk 文件传输的基本单位
Fragmentation 分片 将大文件分割成小块传输
Checksum 校验和 验证数据完整性的数值

3、技术原理

1. 文件传输架构

客户端上传流程:
1. 客户端发送上传请求 (文件名、大小)
2. 服务端确认并分配资源
3. 客户端分块发送文件数据
4. 服务端接收并验证每块数据
5. 传输完成后服务端合并文件
6. 双方确认传输结果

数据包结构:
┌─────────────────────────────────────┐
│  包头 (固定大小)                    │
├─────────────────────────────────────┤
│  文件ID                             │
│  块序号                            │
│  块大小                            │
│  校验和                            │
├─────────────────────────────────────┤
│  数据块 (可变大小)                  │
└─────────────────────────────────────┘

2. 错误处理机制

传输错误处理:
1. 网络中断 -> 重新连接并续传
2. 数据损坏 -> 请求重传该块
3. 磁盘满 -> 暂停传输并报告错误
4. 权限不足 -> 跳过文件并记录日志

断点续传:
1. 记录已传输的块信息
2. 重新连接后协商续传点
3. 从断点继续传输

3、代码实现

1. 服务端文件接收

// server_upload.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 _UPLOAD_SESSION {
    std::string fileName;
    std::string fullPath;
    ULONGLONG fileSize;
    ULONGLONG receivedSize;
    DWORD sessionId;
    std::ofstream fileStream;
    std::vector<bool> receivedChunks;
    DWORD chunkSize;
    time_t startTime;
} UPLOAD_SESSION, *PUPLOAD_SESSION;

// 全局上传会话管理
std::map<DWORD, UPLOAD_SESSION> g_uploadSessions;
std::mutex g_sessionMutex;
DWORD g_nextSessionId = 1;

// 文件传输命令
#define CMD_UPLOAD_REQUEST   0x1001  // 上传请求
#define CMD_UPLOAD_START     0x1002  // 开始上传
#define CMD_UPLOAD_DATA      0x1003  // 上传数据
#define CMD_UPLOAD_ACK       0x1004  // 确认接收
#define CMD_UPLOAD_COMPLETE  0x1005  // 上传完成
#define CMD_UPLOAD_ERROR     0x1006  // 上传错误

#pragma pack(push, 1)
// 上传请求包
typedef struct _UPLOAD_REQUEST {
    DWORD fileNameLen;
    ULONGLONG fileSize;
    DWORD chunkSize;
} UPLOAD_REQUEST, *PUPLOAD_REQUEST;

// 上传数据包头
typedef struct _UPLOAD_DATA_HEADER {
    DWORD sessionId;
    DWORD chunkIndex;
    DWORD chunkSize;
    DWORD checksum;  // 简单校验和
} UPLOAD_DATA_HEADER, *PUPLOAD_DATA_HEADER;
#pragma pack(pop)

// 计算校验和
DWORD CalculateChecksum(const BYTE* data, DWORD size) {
    DWORD sum = 0;
    for (DWORD i = 0; i < size; i++) {
        sum += data[i];
    }
    return sum;
}

// 验证校验和
bool VerifyChecksum(const BYTE* data, DWORD size, DWORD checksum) {
    return CalculateChecksum(data, size) == checksum;
}

// 创建上传会话
DWORD CreateUploadSession(const char* fileName, ULONGLONG fileSize, DWORD chunkSize) {
    std::lock_guard<std::mutex> lock(g_sessionMutex);
    
    DWORD sessionId = g_nextSessionId++;
    
    UPLOAD_SESSION session = {0};
    session.fileName = fileName;
    session.fileSize = fileSize;
    session.chunkSize = chunkSize;
    session.sessionId = sessionId;
    session.startTime = time(NULL);
    
    // 创建文件路径 (实际应用中应该更安全)
    session.fullPath = "uploads\\" + session.fileName;
    
    // 创建目录
    CreateDirectoryA("uploads", NULL);
    
    // 打开文件流
    session.fileStream.open(session.fullPath, std::ios::binary | std::ios::out);
    if (!session.fileStream.is_open()) {
        return 0;
    }
    
    // 初始化块状态
    DWORD totalChunks = (DWORD)((fileSize + chunkSize - 1) / chunkSize);
    session.receivedChunks.resize(totalChunks, false);
    
    g_uploadSessions[sessionId] = session;
    
    printf("[+] Created upload session %lu for %s (%llu bytes)\n",
        sessionId, fileName, fileSize);
    
    return sessionId;
}

// 处理上传请求
bool HandleUploadRequest(SOCKET clientSock, DWORD clientId, 
                       const UPLOAD_REQUEST* req, const char* fileName) {
    printf("[Client %lu] Upload request: %s (%llu bytes)\n",
        clientId, fileName, req->fileSize);
    
    // 创建会话
    DWORD sessionId = CreateUploadSession(fileName, req->fileSize, req->chunkSize);
    if (sessionId == 0) {
        return false;
    }
    
    // 发送开始上传命令
    MSG_HEADER response;
    response.magic = MAGIC_NUMBER;
    response.cmdType = CMD_UPLOAD_START;
    response.dataLen = sizeof(DWORD);
    response.clientId = clientId;
    
    send(clientSock, (char*)&response, sizeof(response), 0);
    send(clientSock, (char*)&sessionId, sizeof(sessionId), 0);
    
    return true;
}

// 处理上传数据
bool HandleUploadData(SOCKET clientSock, DWORD clientId, 
                     const UPLOAD_DATA_HEADER* header, 
                     const BYTE* data) {
    std::lock_guard<std::mutex> lock(g_sessionMutex);
    
    // 查找会话
    if (g_uploadSessions.find(header->sessionId) == g_uploadSessions.end()) {
        printf("[-] Invalid session ID: %lu\n", header->sessionId);
        return false;
    }
    
    UPLOAD_SESSION& session = g_uploadSessions[header->sessionId];
    
    // 验证校验和
    if (!VerifyChecksum(data, header->chunkSize, header->checksum)) {
        printf("[-] Checksum mismatch for chunk %lu\n", header->chunkIndex);
        
        // 请求重传
        MSG_HEADER response;
        response.magic = MAGIC_NUMBER;
        response.cmdType = CMD_UPLOAD_ERROR;
        response.dataLen = sizeof(DWORD) * 2;
        response.clientId = clientId;
        
        send(clientSock, (char*)&response, sizeof(response), 0);
        
        DWORD errorData[2] = {header->sessionId, header->chunkIndex};
        send(clientSock, (char*)errorData, sizeof(errorData), 0);
        
        return false;
    }
    
    // 写入文件
    session.fileStream.seekp(header->chunkIndex * session.chunkSize);
    session.fileStream.write((char*)data, header->chunkSize);
    
    // 更新状态
    session.receivedChunks[header->chunkIndex] = true;
    session.receivedSize += header->chunkSize;
    
    // 发送确认
    MSG_HEADER ack;
    ack.magic = MAGIC_NUMBER;
    ack.cmdType = CMD_UPLOAD_ACK;
    ack.dataLen = sizeof(DWORD) * 2;
    ack.clientId = clientId;
    
    send(clientSock, (char*)&ack, sizeof(ack), 0);
    
    DWORD ackData[2] = {header->sessionId, header->chunkIndex};
    send(clientSock, (char*)ackData, sizeof(ackData), 0);
    
    // 显示进度
    double progress = (double)session.receivedSize / session.fileSize * 100;
    printf("\r[Session %lu] Progress: %.1f%% (%llu/%llu)", 
        session.sessionId, progress, session.receivedSize, session.fileSize);
    fflush(stdout);
    
    return true;
}

// 处理上传完成
bool HandleUploadComplete(SOCKET clientSock, DWORD clientId, DWORD sessionId) {
    std::lock_guard<std::mutex> lock(g_sessionMutex);
    
    if (g_uploadSessions.find(sessionId) == g_uploadSessions.end()) {
        return false;
    }
    
    UPLOAD_SESSION& session = g_uploadSessions[sessionId];
    
    // 关闭文件流
    session.fileStream.close();
    
    // 验证完整性
    if (session.receivedSize == session.fileSize) {
        printf("\n[+] Upload completed: %s\n", session.fullPath.c_str());
        
        // 发送完成确认
        MSG_HEADER response;
        response.magic = MAGIC_NUMBER;
        response.cmdType = CMD_UPLOAD_COMPLETE;
        response.dataLen = sizeof(DWORD);
        response.clientId = clientId;
        
        send(clientSock, (char*)&response, sizeof(response), 0);
        send(clientSock, (char*)&sessionId, sizeof(sessionId), 0);
    } else {
        printf("\n[-] Upload incomplete: %llu/%llu bytes received\n",
            session.receivedSize, session.fileSize);
    }
    
    // 清理会话
    g_uploadSessions.erase(sessionId);
    
    return true;
}

// 列出正在进行的上传
void ListActiveUploads() {
    std::lock_guard<std::mutex> lock(g_sessionMutex);
    
    printf("\n=== Active Upload Sessions ===\n");
    if (g_uploadSessions.empty()) {
        printf("No active sessions\n");
        return;
    }
    
    for (const auto& pair : g_uploadSessions) {
        const UPLOAD_SESSION& session = pair.second;
        double progress = (double)session.receivedSize / session.fileSize * 100;
        
        printf("Session %lu: %s (%.1f%%)\n",
            session.sessionId,
            session.fileName.c_str(),
            progress);
    }
    printf("\n");
}

// 服务端集成函数
bool ServerHandleUpload(SOCKET clientSock, DWORD clientId, 
                       DWORD cmdType, const BYTE* data, DWORD dataLen) {
    switch (cmdType) {
        case CMD_UPLOAD_REQUEST: {
            if (dataLen < sizeof(UPLOAD_REQUEST)) return false;
            
            PUPLOAD_REQUEST req = (PUPLOAD_REQUEST)data;
            const char* fileName = (const char*)(data + sizeof(UPLOAD_REQUEST));
            
            return HandleUploadRequest(clientSock, clientId, req, fileName);
        }
        
        case CMD_UPLOAD_DATA: {
            if (dataLen < sizeof(UPLOAD_DATA_HEADER)) return false;
            
            PUPLOAD_DATA_HEADER header = (PUPLOAD_DATA_HEADER)data;
            const BYTE* fileData = data + sizeof(UPLOAD_DATA_HEADER);
            DWORD fileDataSize = dataLen - sizeof(UPLOAD_DATA_HEADER);
            
            return HandleUploadData(clientSock, clientId, header, fileData);
        }
        
        case CMD_UPLOAD_COMPLETE: {
            if (dataLen < sizeof(DWORD)) return false;
            DWORD sessionId = *(DWORD*)data;
            return HandleUploadComplete(clientSock, clientId, sessionId);
        }
    }
    
    return false;
}

2. 客户端文件上传

// client_upload.cpp
// 客户端文件上传模块

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <fstream>
#include <vector>

#pragma comment(lib, "ws2_32.lib")

extern SOCKET g_sock;
extern DWORD g_clientId;

#define DEFAULT_CHUNK_SIZE  8192

// 上传文件
bool UploadFile(const char* localPath, const char* remoteName = NULL) {
    // 获取文件名
    std::string fileName = remoteName ? remoteName : 
        strrchr(localPath, '\\') ? strrchr(localPath, '\\') + 1 :
        strrchr(localPath, '/') ? strrchr(localPath, '/') + 1 : localPath;
    
    // 打开本地文件
    std::ifstream file(localPath, std::ios::binary);
    if (!file.is_open()) {
        printf("[-] Cannot open file: %s\n", localPath);
        return false;
    }
    
    // 获取文件大小
    file.seekg(0, std::ios::end);
    ULONGLONG fileSize = file.tellg();
    file.seekg(0, std::ios::beg);
    
    printf("[*] Uploading %s (%llu bytes)\n", fileName.c_str(), fileSize);
    
    // 发送上传请求
    UPLOAD_REQUEST req = {0};
    req.fileNameLen = (DWORD)fileName.length();
    req.fileSize = fileSize;
    req.chunkSize = DEFAULT_CHUNK_SIZE;
    
    DWORD totalSize = sizeof(req) + (DWORD)fileName.length();
    std::vector<BYTE> requestData(totalSize);
    
    memcpy(requestData.data(), &req, sizeof(req));
    memcpy(requestData.data() + sizeof(req), fileName.c_str(), fileName.length());
    
    MSG_HEADER header;
    header.magic = MAGIC_NUMBER;
    header.cmdType = CMD_UPLOAD_REQUEST;
    header.dataLen = totalSize;
    header.clientId = g_clientId;
    
    send(g_sock, (char*)&header, sizeof(header), 0);
    send(g_sock, (char*)requestData.data(), totalSize, 0);
    
    // 等待服务器响应
    MSG_HEADER response;
    if (recv(g_sock, (char*)&response, sizeof(response), 0) <= 0) {
        return false;
    }
    
    if (response.cmdType != CMD_UPLOAD_START || response.dataLen != sizeof(DWORD)) {
        printf("[-] Invalid server response\n");
        return false;
    }
    
    DWORD sessionId;
    if (recv(g_sock, (char*)&sessionId, sizeof(sessionId), 0) <= 0) {
        return false;
    }
    
    printf("[+] Upload session started: %lu\n", sessionId);
    
    // 分块上传
    DWORD chunkIndex = 0;
    ULONGLONG uploadedSize = 0;
    
    std::vector<BYTE> chunk(DEFAULT_CHUNK_SIZE);
    
    while (uploadedSize < fileSize) {
        // 读取数据块
        DWORD bytesToRead = (DWORD)min(DEFAULT_CHUNK_SIZE, fileSize - uploadedSize);
        file.read((char*)chunk.data(), bytesToRead);
        DWORD bytesRead = (DWORD)file.gcount();
        
        if (bytesRead == 0) break;
        
        // 计算校验和
        DWORD checksum = CalculateChecksum(chunk.data(), bytesRead);
        
        // 发送数据包
        UPLOAD_DATA_HEADER dataHeader;
        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_UPLOAD_DATA;
        dataMsg.dataLen = packetSize;
        dataMsg.clientId = g_clientId;
        
        send(g_sock, (char*)&dataMsg, sizeof(dataMsg), 0);
        send(g_sock, (char*)packet.data(), packetSize, 0);
        
        // 等待确认
        MSG_HEADER ack;
        if (recv(g_sock, (char*)&ack, sizeof(ack), 0) <= 0) {
            return false;
        }
        
        if (ack.cmdType == CMD_UPLOAD_ACK) {
            DWORD ackData[2];
            if (recv(g_sock, (char*)ackData, sizeof(ackData), 0) <= 0) {
                return false;
            }
            
            if (ackData[0] == sessionId && ackData[1] == chunkIndex) {
                uploadedSize += bytesRead;
                chunkIndex++;
                
                // 显示进度
                double progress = (double)uploadedSize / fileSize * 100;
                printf("\rProgress: %.1f%% (%llu/%llu)", progress, uploadedSize, fileSize);
                fflush(stdout);
            }
        } else if (ack.cmdType == CMD_UPLOAD_ERROR) {
            // 重传出错的块
            printf("\n[-] Checksum error, retransmitting chunk %lu\n", chunkIndex);
            continue;  // 重新发送当前块
        }
    }
    
    printf("\n[+] File upload completed\n");
    
    // 发送完成信号
    MSG_HEADER completeMsg;
    completeMsg.magic = MAGIC_NUMBER;
    completeMsg.cmdType = CMD_UPLOAD_COMPLETE;
    completeMsg.dataLen = sizeof(DWORD);
    completeMsg.clientId = g_clientId;
    
    send(g_sock, (char*)&completeMsg, sizeof(completeMsg), 0);
    send(g_sock, (char*)&sessionId, sizeof(sessionId), 0);
    
    // 等待服务器确认
    MSG_HEADER finalAck;
    if (recv(g_sock, (char*)&finalAck, sizeof(finalAck), 0) > 0) {
        if (finalAck.cmdType == CMD_UPLOAD_COMPLETE) {
            printf("[+] Server confirmed upload completion\n");
        }
    }
    
    file.close();
    return true;
}

// 上传命令处理
void HandleUploadCommand(const char* cmdLine) {
    if (strncmp(cmdLine, "upload ", 7) != 0) return;
    
    const char* params = cmdLine + 7;
    const char* spacePos = strchr(params, ' ');
    
    if (spacePos) {
        // upload local_path remote_name
        std::string localPath(params, spacePos - params);
        std::string remoteName(spacePos + 1);
        UploadFile(localPath.c_str(), remoteName.c_str());
    } else {
        // upload local_path
        UploadFile(params);
    }
}

// 测试上传功能
void TestUpload() {
    printf("=== File Upload Test ===\n");
    
    // 创建测试文件
    std::ofstream testFile("test_upload.txt");
    for (int i = 0; i < 1000; i++) {
        testFile << "This is line " << i << " of the test file.\n";
    }
    testFile.close();
    
    printf("[*] Created test file: test_upload.txt\n");
    
    // 上传测试
    UploadFile("test_upload.txt", "uploaded_test.txt");
    
    printf("[*] Upload test completed\n");
}

3. 完整示例

// upload_example.cpp
// 文件上传完整示例

#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>

// 模拟服务端处理
void SimulateServer() {
    printf("=== Simulated Server ===\n");
    printf("Listening for upload requests...\n");
    
    // 这里应该是实际的服务端代码
    // 等待客户端连接和上传请求
    
    std::this_thread::sleep_for(std::chrono::seconds(5));
    printf("[Server] Upload simulation completed\n");
}

// 模拟客户端上传
void SimulateClient() {
    printf("=== Simulated Client ===\n");
    
    // 创建大文件进行测试
    printf("[Client] Creating large test file...\n");
    
    std::ofstream largeFile("large_test.dat", std::ios::binary);
    if (largeFile.is_open()) {
        // 写入1MB数据
        std::vector<char> buffer(1024);
        for (int i = 0; i < 1024; i++) {
            for (int j = 0; j < 1024; j++) {
                buffer[j] = (char)(i * j);
            }
            largeFile.write(buffer.data(), buffer.size());
        }
        largeFile.close();
        
        printf("[Client] Created 1MB test file\n");
        
        // 模拟上传过程
        printf("[Client] Starting upload simulation...\n");
        
        ULONGLONG totalSize = 1024 * 1024;
        ULONGLONG uploaded = 0;
        DWORD chunkSize = 8192;
        
        while (uploaded < totalSize) {
            DWORD currentChunk = (DWORD)min(chunkSize, totalSize - uploaded);
            uploaded += currentChunk;
            
            double progress = (double)uploaded / totalSize * 100;
            printf("\r[Client] Upload progress: %.1f%%", progress);
            fflush(stdout);
            
            std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 模拟网络延迟
        }
        
        printf("\n[Client] Upload completed\n");
    }
}

int main() {
    printf("========================================\n");
    printf("     File Upload System Simulation       \n");
    printf("========================================\n\n");
    
    // 启动模拟服务端线程
    std::thread serverThread(SimulateServer);
    
    // 稍微延迟后启动客户端
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    SimulateClient();
    
    // 等待服务端完成
    serverThread.join();
    
    printf("\n[*] File upload system simulation completed\n");
    
    return 0;
}

4、课后作业

4.1、作业1:实现断点续传

添加上传中断后能够从中断点继续上传的功能。

4.2、作业2:添加文件压缩

在上传前对文件进行压缩以减少传输时间。

4.3、作业3:实现多文件批量上传

支持同时上传多个文件,并显示整体进度。