// server.cpp - 文件中转服务器,支持上传时设置密码
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include <thread>
#include <mutex>
#include <sys/stat.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
const int BUFFER_SIZE = 4096;
const string STORAGE_DIR = "./storage/";
mutex fileMutex;
map<string, size_t> fileList; // 文件名 -> 大小
map<string, string> filePasswords; // 文件名 -> 密码(明文)
void initWinsock() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "初始化网络失败,请重试。" << endl;
exit(1);
}
}
void cleanupWinsock() {
WSACleanup();
}
bool fileExists(const string& filename) {
ifstream f(filename, ios::binary);
return f.good();
}
size_t getFileSize(const string& filename) {
struct stat buf;
if (stat(filename.c_str(), &buf) == 0) {
return static_cast<size_t>(buf.st_size);
}
return 0;
}
bool createStorageDir() {
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile(STORAGE_DIR.c_str(), &ffd);
if (hFind == INVALID_HANDLE_VALUE) {
if (!CreateDirectory(STORAGE_DIR.c_str(), NULL)) {
cout << "无法创建存储目录 " << STORAGE_DIR << endl;
return false;
}
} else {
FindClose(hFind);
}
return true;
}
vector<string> getStoredFiles() {
vector<string> files;
string searchPath = STORAGE_DIR + "*.*";
WIN32_FIND_DATA ffd;
HANDLE hFind = FindFirstFile(searchPath.c_str(), &ffd);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
files.push_back(ffd.cFileName);
}
} while (FindNextFile(hFind, &ffd));
FindClose(hFind);
}
lock_guard<mutex> lock(fileMutex);
fileList.clear();
for (const auto& f : files) {
ifstream fin(STORAGE_DIR + f, ios::binary | ios::ate);
if (fin.is_open()) {
fileList[f] = fin.tellg();
fin.close();
}
}
return files;
}
void sendFileList(SOCKET sock) {
auto files = getStoredFiles();
int count = files.size();
send(sock, (const char*)&count, sizeof(count), 0);
for (const auto& name : files) {
int len = name.length();
send(sock, (const char*)&len, sizeof(len), 0);
send(sock, name.c_str(), len, 0);
size_t size = fileList[name];
send(sock, (const char*)&size, sizeof(size), 0);
}
cout << "已发送文件列表给客户端(共 " << count << " 个文件)" << endl;
}
void saveReceivedFile(SOCKET sock) {
int nameLen;
recv(sock, (char*)&nameLen, sizeof(nameLen), 0);
string filename(nameLen, '\0');
recv(sock, &filename[0], nameLen, 0);
size_t fileSize;
recv(sock, (char*)&fileSize, sizeof(fileSize), 0);
bool requiresPassword;
recv(sock, (char*)&requiresPassword, sizeof(requiresPassword), 0);
string password = "";
if (requiresPassword) {
int pwdLen;
recv(sock, (char*)&pwdLen, sizeof(pwdLen), 0);
password.resize(pwdLen);
recv(sock, &password[0], pwdLen, 0);
}
string fullPath = STORAGE_DIR + filename;
int copyIndex = 1;
string baseName = filename.substr(0, filename.find_last_of('.'));
string ext = filename.substr(filename.find_last_of('.'));
string finalName = filename;
while (fileExists(fullPath)) {
finalName = baseName + "(" + to_string(copyIndex) + ")" + ext;
fullPath = STORAGE_DIR + finalName;
copyIndex++;
}
if (finalName != filename) {
int newLen = finalName.length();
send(sock, (const char*)&newLen, sizeof(newLen), 0);
send(sock, finalName.c_str(), newLen, 0);
cout << "文件重命名为 \"" << finalName << "\" 避免覆盖" << endl;
} else {
send(sock, (const char*)&nameLen, sizeof(nameLen), 0);
send(sock, filename.c_str(), nameLen, 0);
}
cout << "正在接收文件 \"" << filename << "\" -> \"" << finalName << "\" (" << fileSize << " 字节)" << endl;
ofstream outFile(fullPath, ios::binary);
if (!outFile.is_open()) {
cout << "无法创建文件进行写入: " << finalName << endl;
return;
}
char buffer[BUFFER_SIZE];
size_t totalReceived = 0;
int lastPercent = -1;
while (totalReceived < fileSize) {
int toRead = min(BUFFER_SIZE, static_cast<int>(fileSize - totalReceived));
int bytesRead = recv(sock, buffer, toRead, 0);
if (bytesRead <= 0) break;
outFile.write(buffer, bytesRead);
totalReceived += bytesRead;
int percent = (int)((double)totalReceived / fileSize * 100);
if (percent != lastPercent && percent % 5 == 0) {
cout << " 进度: " << percent << "% (" << totalReceived << "/" << fileSize << ")" << endl;
lastPercent = percent;
}
}
outFile.close();
if (totalReceived == fileSize) {
cout << "文件 \"" << finalName << "\" 接收完成!" << endl;
lock_guard<mutex> lock(fileMutex);
fileList[finalName] = fileSize;
if (requiresPassword && !password.empty()) {
filePasswords[finalName] = password;
cout << "已为文件 \"" << finalName << "\" 设置密码保护。" << endl;
} else {
filePasswords.erase(finalName);
}
} else {
cout << "文件接收中断,删除不完整文件。" << endl;
remove(fullPath.c_str());
}
}
void sendFileToClient(SOCKET sock) {
int nameLen;
int ret = recv(sock, (char*)&nameLen, sizeof(nameLen), 0);
if (ret <= 0) {
cout << "接收文件名长度失败,连接中断。" << endl;
return;
}
string filename(nameLen, '\0');
ret = recv(sock, &filename[0], nameLen, 0);
if (ret <= 0) {
cout << "接收文件名失败。" << endl;
return;
}
string fullPath = STORAGE_DIR + filename;
ifstream inFile(fullPath, ios::binary | ios::ate);
if (!inFile.is_open()) {
size_t notFound = (size_t)-1;
send(sock, (const char*)¬Found, sizeof(notFound), 0);
cout << "请求的文件 \"" << filename << "\" 不存在!" << endl;
return;
}
auto it = filePasswords.find(filename);
bool isProtected = (it != filePasswords.end());
send(sock, (const char*)&isProtected, sizeof(isProtected), 0);
bool accessGranted = true;
if (isProtected) {
char pwdBuffer[256] = {0};
// 必须完整接收 256 字节
int total = 0;
while (total < 256) {
ret = recv(sock, pwdBuffer + total, 256 - total, 0);
if (ret <= 0) {
cout << "接收密码时连接中断。" << endl;
closesocket(sock);
return;
}
total += ret;
}
string receivedPwd = pwdBuffer;
if (receivedPwd != it->second) {
accessGranted = false;
cout << "客户端尝试下载 \"" << filename << "\" 但密码错误!" << endl;
} else {
cout << "密码正确,允许下载 \"" << filename << "\"" << endl;
}
}
// 无论是否加密,都必须返回授权状态
send(sock, (const char*)&accessGranted, sizeof(accessGranted), 0);
if (!accessGranted) {
return; // 不继续发送文件
}
size_t fileSize = inFile.tellg();
inFile.seekg(0, ios::beg);
send(sock, (const char*)&fileSize, sizeof(fileSize), 0);
cout << "正在发送文件 \"" << filename << "\" (" << fileSize << " 字节) ..." << endl;
char buffer[BUFFER_SIZE];
size_t totalSent = 0;
int lastPercent = -1;
while (inFile.read(buffer, BUFFER_SIZE), inFile.gcount() > 0) {
streamsize chunkSize = inFile.gcount();
if (send(sock, buffer, chunkSize, 0) == SOCKET_ERROR) {
cout << "发送失败,连接中断。" << endl;
break;
}
totalSent += chunkSize;
int percent = (int)((double)totalSent / fileSize * 100);
if (percent != lastPercent && percent % 5 == 0) {
cout << " 进度: " << percent << "% (" << totalSent << "/" << fileSize << ")" << endl;
lastPercent = percent;
}
}
inFile.close();
if (totalSent == fileSize)
cout << "文件 \"" << filename << "\" 发送完成!" << endl;
}
void handleClient(SOCKET clientSocket) {
cout << "新客户端接入,开始处理请求..." << endl;
while (true) {
int cmd;
int ret = recv(clientSocket, (char*)&cmd, sizeof(cmd), 0);
if (ret <= 0) {
cout << "客户端断开连接。" << endl;
break;
}
switch (cmd) {
case 1:
saveReceivedFile(clientSocket);
break;
case 2:
sendFileToClient(clientSocket);
break;
case 3:
sendFileList(clientSocket);
break;
case 0:
cout << "客户端主动退出。" << endl;
closesocket(clientSocket);
return;
default:
cout << "未知指令: " << cmd << endl;
break;
}
}
closesocket(clientSocket);
}
int main() {
setlocale(LC_ALL, ".UTF8");
initWinsock();
if (!createStorageDir()) return 1;
int port;
cout << "========== 文件中转服务器 ==========" << endl;
cout << "请输入监听端口(例如 8888): ";
cin >> port;
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == INVALID_SOCKET) {
cout << "创建套接字失败。" << endl;
return 1;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = INADDR_ANY;
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
cout << "绑定端口失败,请检查是否被占用。" << endl;
closesocket(serverSocket);
return 1;
}
if (listen(serverSocket, 5) == SOCKET_ERROR) {
cout << "启动监听失败。" << endl;
closesocket(serverSocket);
return 1;
}
cout << "中转服务器已启动,正在监听端口 " << port << " ..." << endl;
cout << "所有文件将保存在 .\\storage\\ 目录下" << endl;
while (true) {
sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
SOCKET clientSock = accept(serverSocket, (struct sockaddr*)&clientAddr, &addrLen);
if (clientSock != INVALID_SOCKET) {
string ip = inet_ntoa(clientAddr.sin_addr);
cout << "\n来自 [" << ip << "] 的客户端已连接,开启新线程处理..." << endl;
thread t(handleClient, clientSock);
t.detach(); // 分离线程
}
}
closesocket(serverSocket);
cleanupWinsock();
return 0;
}
// client.cpp - 文件中转客户端
#include <winsock2.h>
#include <windows.h>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
const int BUFFER_SIZE = 4096;
void initWinsock() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "初始化网络失败,请重试。" << endl;
exit(1);
}
}
void cleanupWinsock() {
WSACleanup();
}
bool fileExists(const string& filename) {
ifstream f(filename, ios::binary);
return f.good();
}
size_t getFileSize(const string& filename) {
struct stat buf;
if (stat(filename.c_str(), &buf) == 0) {
return static_cast<size_t>(buf.st_size);
}
return 0;
}
struct FileInfo {
string name;
size_t size;
};
vector<FileInfo> requestFileList(SOCKET sock) {
int cmd = 3;
send(sock, (const char*)&cmd, sizeof(cmd), 0);
int count;
recv(sock, (char*)&count, sizeof(count), 0);
vector<FileInfo> files;
for (int i = 0; i < count; ++i) {
int len;
recv(sock, (char*)&len, sizeof(len), 0);
string name(len, '\0');
recv(sock, &name[0], len, 0);
size_t size;
recv(sock, (char*)&size, sizeof(size), 0);
files.push_back({name, size});
}
return files;
}
void displayFileList(const vector<FileInfo>& files) {
if (files.empty()) {
cout << "当前服务器上没有可下载的文件。\n" << endl;
return;
}
cout << "\n服务器文件列表:" << endl;
cout << "--------------------------------------------------" << endl;
for (size_t i = 0; i < files.size(); ++i) {
const auto& f = files[i];
double mb = f.size / (1024.0 * 1024.0);
cout << "[" << i+1 << "] " << f.name
<< " | " << f.size << " 字节"
<< " (" << (mb < 0.01 ? "<0.01" : to_string((int)(mb*100)/100.0)) << " MB)" << endl;
}
cout << "--------------------------------------------------" << endl;
}
void uploadFile(SOCKET sock) {
string filepath;
cout << "请输入要上传的文件路径: ";
cin >> filepath;
if (!fileExists(filepath)) {
cout << "找不到该文件,请确认路径正确。\n" << endl;
return;
}
size_t fileSize = getFileSize(filepath);
ifstream inFile(filepath, ios::binary);
if (!inFile.is_open()) {
cout << "无法打开文件读取。\n" << endl;
return;
}
size_t pos = filepath.find_last_of("\\/");
string filename = (pos == string::npos) ? filepath : filepath.substr(pos + 1);
int cmd = 1;
send(sock, (const char*)&cmd, sizeof(cmd), 0);
int nameLen = filename.length();
send(sock, (const char*)&nameLen, sizeof(nameLen), 0);
send(sock, filename.c_str(), nameLen, 0);
send(sock, (const char*)&fileSize, sizeof(fileSize), 0);
char protect;
string password = "";
cout << "是否为此文件设置下载密码?(y/n): ";
cin >> protect;
bool requiresPassword = (protect == 'y' || protect == 'Y');
send(sock, (const char*)&requiresPassword, sizeof(requiresPassword), 0);
if (requiresPassword) {
cout << "请输入下载口令: ";
cin >> password;
int pwdLen = password.length();
send(sock, (const char*)&pwdLen, sizeof(pwdLen), 0);
send(sock, password.c_str(), pwdLen, 0);
cout << "已设置密码保护,下载时需输入口令。\n" << endl;
} else {
cout << "该文件无需密码即可下载。\n" << endl;
}
cout << "正在上传 \"" << filename << "\" (" << fileSize << " 字节) ...\n" << endl;
char buffer[BUFFER_SIZE];
size_t totalSent = 0;
int lastPercent = -1;
while (inFile.read(buffer, BUFFER_SIZE), inFile.gcount() > 0) {
streamsize chunk = inFile.gcount();
if (send(sock, buffer, chunk, 0) == SOCKET_ERROR) {
cout << "上传失败,连接中断。\n" << endl;
inFile.close();
return;
}
totalSent += chunk;
int percent = (int)((double)totalSent / fileSize * 100);
if (percent != lastPercent && percent % 5 == 0) {
cout << " 进度: " << percent << "% (" << totalSent << "/" << fileSize << ")" << endl;
lastPercent = percent;
}
}
inFile.close();
recv(sock, (char*)&nameLen, sizeof(nameLen), 0);
string finalName(nameLen, '\0');
recv(sock, &finalName[0], nameLen, 0);
cout << "文件上传成功!服务器保存为 \"" << finalName << "\"\n" << endl;
}
void downloadFile(SOCKET sock) {
vector<FileInfo> files = requestFileList(sock);
displayFileList(files);
if (files.empty()) return;
int choice;
cout << "请输入要下载的文件编号 (1-" << files.size() << "),输入 0 取消: ";
cin >> choice;
if (choice < 1 || choice > (int)files.size()) {
cout << "操作取消。\n" << endl;
return;
}
const string& filename = files[choice - 1].name;
size_t fileSize = files[choice - 1].size;
int cmd = 2;
send(sock, (const char*)&cmd, sizeof(cmd), 0);
int nameLen = filename.length();
send(sock, (const char*)&nameLen, sizeof(nameLen), 0);
send(sock, filename.c_str(), nameLen, 0);
size_t actualSize;
recv(sock, (char*)&actualSize, sizeof(actualSize), 0);
if (actualSize == (size_t)-1) {
cout << "文件已被删除或无法访问!\n" << endl;
return;
}
bool isProtected;
recv(sock, (char*)&isProtected, sizeof(isProtected), 0);
if (isProtected) {
string password;
cout << "该文件受密码保护,请输入访问口令: ";
cin >> password;
char pwdBuffer[256] = {0};
strncpy(pwdBuffer, password.c_str(), 255);
send(sock, pwdBuffer, 256, 0); // 固定发 256 字节
bool accessGranted;
int ret = recv(sock, (char*)&accessGranted, sizeof(accessGranted), 0);
if (ret <= 0) {
cout << "与服务器断开连接!\n" << endl;
return;
}
if (!accessGranted) {
cout << "密码错误,禁止下载!\n" << endl;
return;
} else {
cout << "密码正确,开始下载...\n" << endl;
}
} else {
cout << "该文件无需密码,直接下载。\n" << endl;
}
ofstream outFile(filename, ios::binary);
if (!outFile.is_open()) {
cout << "无法创建本地文件 \"" << filename << "\" 进行写入。\n" << endl;
return;
}
cout << "正在下载 \"" << filename << "\" (" << actualSize << " 字节) ...\n" << endl;
char buffer[BUFFER_SIZE];
size_t totalReceived = 0;
int lastPercent = -1;
while (totalReceived < actualSize) {
int toRead = min(BUFFER_SIZE, static_cast<int>(actualSize - totalReceived));
int bytesRead = recv(sock, buffer, toRead, 0);
if (bytesRead <= 0) break;
outFile.write(buffer, bytesRead);
totalReceived += bytesRead;
int percent = (int)((double)totalReceived / actualSize * 100);
if (percent != lastPercent && percent % 5 == 0) {
cout << " 进度: " << percent << "% (" << totalReceived << "/" << actualSize << ")" << endl;
lastPercent = percent;
}
}
outFile.close();
if (totalReceived == actualSize) {
cout << "文件 \"" << filename << "\" 下载完成!\n" << endl;
} else {
cout << "下载中断,删除不完整文件。\n" << endl;
remove(filename.c_str());
}
}
int main() {
setlocale(LC_ALL, ".UTF8");
initWinsock();
string ip;
int port;
cout << "========== 文件中转客户端 ==========" << endl;
cout << "请输入服务器IP地址(例如 127.0.0.1): ";
cin >> ip;
cout << "请输入服务器端口号(例如 8888): ";
cin >> port;
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
cout << "创建套接字失败。\n" << endl;
return 1;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(port);
serverAddr.sin_addr.s_addr = inet_addr(ip.c_str());
cout << "正在连接服务器 " << ip << ":" << port << " ...\n" << endl;
if (connect(sock, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
cout << "连接服务器失败,请检查服务器是否运行。\n" << endl;
closesocket(sock);
return 1;
}
cout << "成功连接到中转服务器!\n" << endl;
while (true) {
cout << "\n主菜单:" << endl;
cout << "1. 上传文件到服务器" << endl;
cout << "2. 从服务器下载文件" << endl;
cout << "3. 刷新文件列表" << endl;
cout << "4. 退出程序" << endl;
cout << "请选择操作: ";
int choice;
cin >> choice;
switch (choice) {
case 1:
uploadFile(sock);
break;
case 2:
downloadFile(sock);
break;
case 3: {
auto files = requestFileList(sock);
displayFileList(files);
break;
}
case 4: {
int cmd = 0;
send(sock, (const char*)&cmd, sizeof(cmd), 0);
cout << "已退出客户端程序。\n" << endl;
closesocket(sock);
cleanupWinsock();
system("pause");
return 0;
}
default:
cout << "无效选择,请重新输入。\n" << endl;
break;
}
}
closesocket(sock);
cleanupWinsock();
system("pause");
return 0;
}
看看代码的Bug,并修改
最新发布