远程控制开发基础
4、被控端文件取回服务端
1、课程目标
- 实现从被控端上传文件到控制端的功能
- 掌握文件传输协议设计
- 理解分块传输和断点续传机制
- 处理大文件传输的内存管理
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:实现多文件批量上传
支持同时上传多个文件,并显示整体进度。