一、系统概述
本系统实现了一个基于服务器的点对点文件传输平台,允许多个客户端通过中央服务器进行文件交换。系统采用C/S架构,支持文件上传、下载、列表查询等功能,适用于企业内网文件共享、分布式存储等场景。
二、系统架构
2.1 整体架构
上传/下载
上传/下载
上传/下载
文件存储
用户管理
客户端1
中央服务器
客户端2
客户端3
文件存储系统
数据库
2.2 核心组件
- 中央服务器:处理客户端连接、文件路由、权限管理
- 文件存储系统:存储上传的文件
- 数据库:存储用户信息、文件元数据
- 客户端程序:提供用户界面和文件操作功能
三、服务器端实现
3.1 服务器主程序 (FileServer.cpp)
cpp
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <thread>
#include <mutex>
#include <direct.h>
#pragma comment(lib, "ws2_32.lib")
#define PORT 8888
#define BUFFER_SIZE 4096
#define MAX_CLIENTS 10
using namespace std;
struct ClientInfo {
SOCKET socket;
string username;
bool authenticated;
};
struct FileInfo {
string filename;
string owner;
long size;
time_t uploadTime;
};
mutex mtx;
map<SOCKET, ClientInfo> clients;
map<string, FileInfo> fileDatabase;
string storagePath = "ServerStorage/";
void handleClient(SOCKET clientSocket);
void processCommand(SOCKET clientSocket, const string& command);
void sendFile(SOCKET clientSocket, const string& filename);
void receiveFile(SOCKET clientSocket, const string& filename, long fileSize);
void broadcastFileList(SOCKET excludeSocket = INVALID_SOCKET);
void logActivity(const string& message);
int main() {
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cerr << "WSAStartup failed." << endl;
return 1;
}
// 创建存储目录
_mkdir(storagePath.c_str());
SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serverSocket == INVALID_SOCKET) {
cerr << "Socket creation failed: " << WSAGetLastError() << endl;
WSACleanup();
return 1;
}
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);
if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
cerr << "Bind failed: " << WSAGetLastError() << endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {
cerr << "Listen failed: " << WSAGetLastError() << endl;
closesocket(serverSocket);
WSACleanup();
return 1;
}
cout << "Server started on port " << PORT << endl;
logActivity("Server started");
while (true) {
sockaddr_in clientAddr;
int clientAddrSize = sizeof(clientAddr);
SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
if (clientSocket == INVALID_SOCKET) {
cerr << "Accept failed: " << WSAGetLastError() << endl;
continue;
}
char ipStr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(clientAddr.sin_addr), ipStr, INET_ADDRSTRLEN);
cout << "New connection from " << ipStr << ":" << ntohs(clientAddr.sin_port) << endl;
// 为新客户端创建线程
thread clientThread(handleClient, clientSocket);
clientThread.detach();
}
closesocket(serverSocket);
WSACleanup();
return 0;
}
void handleClient(SOCKET clientSocket) {
char buffer[BUFFER_SIZE];
int bytesReceived;
// 添加客户端到列表
{
lock_guard<mutex> lock(mtx);
clients[clientSocket] = {clientSocket, "", false};
}
// 发送欢迎消息
string welcome = "Welcome to File Transfer Server\n";
send(clientSocket, welcome.c_str(), welcome.size(), 0);
while (true) {
bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
if (bytesReceived <= 0) {
break; // 连接关闭或出错
}
buffer[bytesReceived] = '\0';
string command(buffer);
processCommand(clientSocket, command);
}
// 客户端断开连接
{
lock_guard<mutex> lock(mtx);
string username = clients[clientSocket].username;
clients.erase(clientSocket);
cout << "Client " << username << " disconnected" << endl;
logActivity(username + " disconnected");
}
closesocket(clientSocket);
}
void processCommand(SOCKET clientSocket, const string& command) {
lock_guard<mutex> lock(mtx);
string cmd = command.substr(0, command.find(' '));
string args = command.substr(command.find(' ') + 1);
if (cmd == "LOGIN") {
// 登录命令: LOGIN username password
string username = args.substr(0, args.find(' '));
string password = args.substr(args.find(' ') + 1);
// 这里简化处理,实际应用中应验证密码
clients[clientSocket].username = username;
clients[clientSocket].authenticated = true;
string response = "Login successful. Welcome " + username + "\n";
send(clientSocket, response.c_str(), response.size(), 0);
cout << "User " << username << " logged in" << endl;
logActivity(username + " logged in");
// 发送文件列表
broadcastFileList(clientSocket);
}
else if (cmd == "LIST") {
// 列出文件命令
if (!clients[clientSocket].authenticated) {
send(clientSocket, "Please login first\n", 24, 0);
return;
}
string fileList = "Files available:\n";
for (const auto& file : fileDatabase) {
fileList += file.first + " (" + to_string(file.second.size) + " bytes) by " + file.second.owner + "\n";
}
send(clientSocket, fileList.c_str(), fileList.size(), 0);
}
else if (cmd == "UPLOAD") {
// 上传文件命令: UPLOAD filename filesize
if (!clients[clientSocket].authenticated) {
send(clientSocket, "Please login first\n", 24, 0);
return;
}
string filename = args.substr(0, args.find(' '));
string sizeStr = args.substr(args.find(' ') + 1);
long fileSize = stol(sizeStr);
// 添加到文件数据库
FileInfo info;
info.filename = filename;
info.owner = clients[clientSocket].username;
info.size = fileSize;
info.uploadTime = time(nullptr);
fileDatabase[filename] = info;
// 通知客户端准备接收
string response = "Ready to receive " + filename + "\n";
send(clientSocket, response.c_str(), response.size(), 0);
// 接收文件
receiveFile(clientSocket, filename, fileSize);
// 广播更新后的文件列表
broadcastFileList();
logActivity(clients[clientSocket].username + " uploaded " + filename);
}
else if (cmd == "DOWNLOAD") {
// 下载文件命令: DOWNLOAD filename
if (!clients[clientSocket].authenticated) {
send(clientSocket, "Please login first\n", 24, 0);
return;
}
string filename = args;
if (fileDatabase.find(filename) != fileDatabase.end()) {
// 发送文件信息
string fileInfo = "FILE " + filename + " " + to_string(fileDatabase[filename].size) + "\n";
send(clientSocket, fileInfo.c_str(), fileInfo.size(), 0);
// 发送文件内容
sendFile(clientSocket, filename);
logActivity(clients[clientSocket].username + " downloaded " + filename);
} else {
string response = "File not found\n";
send(clientSocket, response.c_str(), response.size(), 0);
}
}
else if (cmd == "DELETE") {
// 删除文件命令: DELETE filename
if (!clients[clientSocket].authenticated) {
send(clientSocket, "Please login first\n", 24, 0);
return;
}
string filename = args;
if (fileDatabase.find(filename) != fileDatabase.end() &&
fileDatabase[filename].owner == clients[clientSocket].username) {
// 从数据库删除
fileDatabase.erase(filename);
// 删除物理文件
string filePath = storagePath + filename;
remove(filePath.c_str());
string response = "File deleted successfully\n";
send(clientSocket, response.c_str(), response.size(), 0);
// 广播更新后的文件列表
broadcastFileList();
logActivity(clients[clientSocket].username + " deleted " + filename);
} else {
string response = "File not found or permission denied\n";
send(clientSocket, response.c_str(), response.size(), 0);
}
}
else {
string response = "Unknown command\n";
send(clientSocket, response.c_str(), response.size(), 0);
}
}
void sendFile(SOCKET clientSocket, const string& filename) {
string filePath = storagePath + filename;
ifstream file(filePath, ios::binary | ios::ate);
if (!file) {
cerr << "Failed to open file: " << filePath << endl;
return;
}
streamsize size = file.tellg();
file.seekg(0, ios::beg);
char buffer[BUFFER_SIZE];
while (size > 0) {
streamsize chunkSize = min(static_cast<streamsize>(BUFFER_SIZE), size);
file.read(buffer, chunkSize);
send(clientSocket, buffer, static_cast<int>(chunkSize), 0);
size -= chunkSize;
}
file.close();
}
void receiveFile(SOCKET clientSocket, const string& filename, long fileSize) {
string filePath = storagePath + filename;
ofstream file(filePath, ios::binary);
if (!file) {
cerr << "Failed to create file: " << filePath << endl;
return;
}
long remaining = fileSize;
char buffer[BUFFER_SIZE];
while (remaining > 0) {
int chunkSize = min(BUFFER_SIZE, static_cast<int>(remaining));
int bytesReceived = recv(clientSocket, buffer, chunkSize, 0);
if (bytesReceived <= 0) {
break; // 连接关闭或出错
}
file.write(buffer, bytesReceived);
remaining -= bytesReceived;
}
file.close();
}
void broadcastFileList(SOCKET excludeSocket) {
string fileList = "UPDATE_FILE_LIST\n";
for (const auto& file : fileDatabase) {
fileList += file.first + "|" + file.second.owner + "|" +
to_string(file.second.size) + "|" +
to_string(file.second.uploadTime) + "\n";
}
for (const auto& client : clients) {
if (client.first != excludeSocket && client.second.authenticated) {
send(client.first, fileList.c_str(), fileList.size(), 0);
}
}
}
void logActivity(const string& message) {
ofstream logFile("server.log", ios::app);
if (logFile) {
time_t now = time(nullptr);
char* dt = ctime(&now);
logFile << "[" << dt << "] " << message << endl;
logFile.close();
}
}
3.2 服务器功能说明
- 用户认证:简单的用户名/密码登录(实际应用中应使用加密存储)
- 文件管理 :
- 上传文件(UPLOAD命令)
- 下载文件(DOWNLOAD命令)
- 删除文件(DELETE命令)
- 列出文件(LIST命令)
- 文件存储:服务器本地目录存储上传的文件
- 日志记录:记录所有操作和连接事件
- 广播通知:文件列表更新时通知所有客户端
四、客户端实现
4.1 客户端主程序 (FileClientDlg.cpp)
cpp
#include <winsock2.h>
#include <ws2tcpip.h>
#include <afxwin.h>
#include <afxext.h>
#include <afxcmn.h>
#include <string>
#include <vector>
#include <fstream>
#include <thread>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "comctl32.lib")
#define PORT 8888
#define BUFFER_SIZE 4096
using namespace std;
class CFileClientDlg : public CDialogEx {
public:
CFileClientDlg(CWnd* pParent = nullptr);
enum { IDD = IDD_FILECLIENT_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
private:
// 控件变量
CEdit m_editServerIP;
CEdit m_editUsername;
CEdit m_editPassword;
CButton m_btnConnect;
CButton m_btnDisconnect;
CListBox m_listFiles;
CEdit m_editLocalPath;
CButton m_btnBrowse;
CButton m_btnUpload;
CButton m_btnDownload;
CButton m_btnDelete;
CProgressCtrl m_progress;
CStatic m_status;
// Socket相关
SOCKET m_clientSocket;
bool m_connected;
string m_username;
thread m_receiveThread;
// 方法
void ConnectToServer();
void DisconnectFromServer();
void SendCommand(const string& cmd);
void ReceiveFileList();
void UploadFile(const string& filePath);
void DownloadFile(const string& filename);
void DeleteFile(const string& filename);
void UpdateUI(bool connected);
void UpdateFileList(const string& list);
void UpdateProgress(int progress);
void SetStatus(const string& message);
static DWORD WINAPI ReceiveThread(LPVOID lpParam);
void StartReceiveThread();
void StopReceiveThread();
};
BEGIN_MESSAGE_MAP(CFileClientDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CFileClientDlg::OnBnClickedButtonConnect)
ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, &CFileClientDlg::OnBnClickedButtonDisconnect)
ON_BN_CLICKED(IDC_BUTTON_BROWSE, &CFileClientDlg::OnBnClickedButtonBrowse)
ON_BN_CLICKED(IDC_BUTTON_UPLOAD, &CFileClientDlg::OnBnClickedButtonUpload)
ON_BN_CLICKED(IDC_BUTTON_DOWNLOAD, &CFileClientDlg::OnBnClickedButtonDownload)
ON_BN_CLICKED(IDC_BUTTON_DELETE, &CFileClientDlg::OnBnClickedButtonDelete)
ON_LBN_DBLCLK(IDC_LIST_FILES, &CFileClientDlg::OnLbnDblclkListFiles)
END_MESSAGE_MAP()
CFileClientDlg::CFileClientDlg(CWnd* pParent)
: CDialogEx(IDD_FILECLIENT_DIALOG, pParent), m_clientSocket(INVALID_SOCKET), m_connected(false) {
}
void CFileClientDlg::DoDataExchange(CDataExchange* pDX) {
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_EDIT_SERVER_IP, m_editServerIP);
DDX_Control(pDX, IDC_EDIT_USERNAME, m_editUsername);
DDX_Control(pDX, IDC_EDIT_PASSWORD, m_editPassword);
DDX_Control(pDX, IDC_BUTTON_CONNECT, m_btnConnect);
DDX_Control(pDX, IDC_BUTTON_DISCONNECT, m_btnDisconnect);
DDX_Control(pDX, IDC_LIST_FILES, m_listFiles);
DDX_Control(pDX, IDC_EDIT_LOCAL_PATH, m_editLocalPath);
DDX_Control(pDX, IDC_BUTTON_BROWSE, m_btnBrowse);
DDX_Control(pDX, IDC_BUTTON_UPLOAD, m_btnUpload);
DDX_Control(pDX, IDC_BUTTON_DOWNLOAD, m_btnDownload);
DDX_Control(pDX, IDC_BUTTON_DELETE, m_btnDelete);
DDX_Control(pDX, IDC_PROGRESS_FILE, m_progress);
DDX_Control(pDX, IDC_STATIC_STATUS, m_status);
}
BOOL CFileClientDlg::OnInitDialog() {
CDialogEx::OnInitDialog();
// 初始化控件
m_editServerIP.SetWindowText(_T("127.0.0.1"));
m_editUsername.SetWindowText(_T("user1"));
m_editPassword.SetWindowText(_T("pass1"));
m_editLocalPath.SetWindowText(_T("C:\\test.txt"));
m_btnDisconnect.EnableWindow(FALSE);
m_btnUpload.EnableWindow(FALSE);
m_btnDownload.EnableWindow(FALSE);
m_btnDelete.EnableWindow(FALSE);
m_progress.SetRange(0, 100);
m_progress.SetPos(0);
// 初始化Socket库
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
AfxMessageBox(_T("WSAStartup failed"));
return FALSE;
}
return TRUE;
}
void CFileClientDlg::ConnectToServer() {
CString serverIP, username, password;
m_editServerIP.GetWindowText(serverIP);
m_editUsername.GetWindowText(username);
m_editPassword.GetWindowText(password);
// 创建Socket
m_clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (m_clientSocket == INVALID_SOCKET) {
AfxMessageBox(_T("Socket creation failed"));
return;
}
// 解析服务器地址
sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);
inet_pton(AF_INET, CT2A(serverIP), &serverAddr.sin_addr);
// 连接服务器
if (connect(m_clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
AfxMessageBox(_T("Connection failed"));
closesocket(m_clientSocket);
m_clientSocket = INVALID_SOCKET;
return;
}
// 登录
string loginCmd = "LOGIN " + string(CT2A(username)) + " " + string(CT2A(password)) + "\n";
send(m_clientSocket, loginCmd.c_str(), loginCmd.size(), 0);
// 启动接收线程
StartReceiveThread();
m_connected = true;
m_username = CT2A(username);
UpdateUI(true);
SetStatus("Connected to server");
}
void CFileClientDlg::DisconnectFromServer() {
if (m_connected) {
StopReceiveThread();
closesocket(m_clientSocket);
m_clientSocket = INVALID_SOCKET;
m_connected = false;
UpdateUI(false);
SetStatus("Disconnected from server");
}
}
void CFileClientDlg::SendCommand(const string& cmd) {
if (m_connected) {
send(m_clientSocket, cmd.c_str(), cmd.size(), 0);
}
}
void CFileClientDlg::ReceiveFileList() {
SendCommand("LIST\n");
}
void CFileClientDlg::UploadFile(const string& filePath) {
ifstream file(filePath, ios::binary | ios::ate);
if (!file) {
AfxMessageBox(_T("Failed to open file"));
return;
}
streamsize size = file.tellg();
file.seekg(0, ios::beg);
string filename = filePath.substr(filePath.find_last_of("\\/") + 1);
string cmd = "UPLOAD " + filename + " " + to_string(size) + "\n";
SendCommand(cmd);
// 发送文件内容
char buffer[BUFFER_SIZE];
while (size > 0) {
streamsize chunkSize = min(static_cast<streamsize>(BUFFER_SIZE), size);
file.read(buffer, chunkSize);
send(m_clientSocket, buffer, static_cast<int>(chunkSize), 0);
size -= chunkSize;
// 更新进度
int progress = static_cast<int>((1.0 - static_cast<double>(size)/file.tellg()) * 100);
UpdateProgress(progress);
}
file.close();
SetStatus("File uploaded: " + filename);
}
void CFileClientDlg::DownloadFile(const string& filename) {
string cmd = "DOWNLOAD " + filename + "\n";
SendCommand(cmd);
}
void CFileClientDlg::DeleteFile(const string& filename) {
string cmd = "DELETE " + filename + "\n";
SendCommand(cmd);
}
void CFileClientDlg::UpdateUI(bool connected) {
m_btnConnect.EnableWindow(!connected);
m_btnDisconnect.EnableWindow(connected);
m_btnUpload.EnableWindow(connected);
m_btnDownload.EnableWindow(connected);
m_btnDelete.EnableWindow(connected);
m_editServerIP.EnableWindow(!connected);
m_editUsername.EnableWindow(!connected);
m_editPassword.EnableWindow(!connected);
}
void CFileClientDlg::UpdateFileList(const string& list) {
m_listFiles.ResetContent();
if (list.find("UPDATE_FILE_LIST") != string::npos) {
// 解析文件列表
size_t pos = list.find('\n');
if (pos != string::npos) {
string data = list.substr(pos + 1);
size_t start = 0;
while (start < data.length()) {
size_t end = data.find('\n', start);
if (end == string::npos) break;
string line = data.substr(start, end - start);
m_listFiles.AddString(CA2T(line.c_str()));
start = end + 1;
}
}
} else {
// 直接显示列表
m_listFiles.AddString(CA2T(list.c_str()));
}
}
void CFileClientDlg::UpdateProgress(int progress) {
m_progress.SetPos(progress);
CString status;
status.Format(_T("Progress: %d%%"), progress);
m_status.SetWindowText(status);
}
void CFileClientDlg::SetStatus(const string& message) {
m_status.SetWindowText(CA2T(message.c_str()));
}
DWORD WINAPI CFileClientDlg::ReceiveThread(LPVOID lpParam) {
CFileClientDlg* pThis = (CFileClientDlg*)lpParam;
char buffer[BUFFER_SIZE];
while (pThis->m_connected) {
int bytesReceived = recv(pThis->m_clientSocket, buffer, BUFFER_SIZE, 0);
if (bytesReceived <= 0) {
pThis->DisconnectFromServer();
break;
}
buffer[bytesReceived] = '\0';
string data(buffer);
if (data.find("FILE ") == 0) {
// 文件传输开始
size_t space1 = data.find(' ');
size_t space2 = data.find(' ', space1 + 1);
string filename = data.substr(space1 + 1, space2 - space1 - 1);
long fileSize = stol(data.substr(space2 + 1));
// 选择保存位置
CFileDialog dlg(FALSE, NULL, CA2T(filename.c_str()),
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("All Files (*.*)|*.*||"), pThis);
if (dlg.DoModal() == IDOK) {
CString savePath = dlg.GetPathName();
ofstream file(CT2A(savePath), ios::binary);
if (file) {
long remaining = fileSize;
while (remaining > 0) {
int chunkSize = min(BUFFER_SIZE, static_cast<int>(remaining));
int recvd = recv(pThis->m_clientSocket, buffer, chunkSize, 0);
if (recvd <= 0) break;
file.write(buffer, recvd);
remaining -= recvd;
// 更新进度
int progress = static_cast<int>((1.0 - static_cast<double>(remaining)/fileSize) * 100);
pThis->UpdateProgress(progress);
}
file.close();
pThis->SetStatus("File downloaded: " + CT2A(savePath));
}
}
} else {
// 普通消息
pThis->UpdateFileList(data);
}
}
return 0;
}
void CFileClientDlg::StartReceiveThread() {
m_receiveThread = thread(ReceiveThread, this);
}
void CFileClientDlg::StopReceiveThread() {
if (m_receiveThread.joinable()) {
m_connected = false;
m_receiveThread.join();
}
}
// 事件处理函数
void CFileClientDlg::OnBnClickedButtonConnect() {
ConnectToServer();
ReceiveFileList();
}
void CFileClientDlg::OnBnClickedButtonDisconnect() {
DisconnectFromServer();
}
void CFileClientDlg::OnBnClickedButtonBrowse() {
CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST,
_T("All Files (*.*)|*.*||"), this);
if (dlg.DoModal() == IDOK) {
m_editLocalPath.SetWindowText(dlg.GetPathName());
}
}
void CFileClientDlg::OnBnClickedButtonUpload() {
CString filePath;
m_editLocalPath.GetWindowText(filePath);
UploadFile(CT2A(filePath));
}
void CFileClientDlg::OnBnClickedButtonDownload() {
int sel = m_listFiles.GetCurSel();
if (sel != LB_ERR) {
CString filename;
m_listFiles.GetText(sel, filename);
DownloadFile(CT2A(filename));
}
}
void CFileClientDlg::OnBnClickedButtonDelete() {
int sel = m_listFiles.GetCurSel();
if (sel != LB_ERR) {
CString filename;
m_listFiles.GetText(sel, filename);
DeleteFile(CT2A(filename));
ReceiveFileList(); // 刷新列表
}
}
void CFileClientDlg::OnLbnDblclkListFiles() {
OnBnClickedButtonDownload();
}
4.2 客户端功能说明
- 连接管理 :
- 连接/断开服务器
- 用户登录/登出
- 文件操作 :
- 上传文件到服务器
- 从服务器下载文件
- 删除服务器上的文件
- 查看文件列表
- 用户界面 :
- 文件列表显示
- 传输进度条
- 状态信息显示
- 后台处理 :
- 独立线程处理服务器消息
- 文件传输进度更新
- 自动刷新文件列表
参考代码 VC++基于服务器的点对点文件传输实例 www.youwenfan.com/contentcst/122450.html
五、系统使用说明
5.1 运行环境
- Windows操作系统
- Visual C++ 2010或更高版本
- 支持C++11标准的编译器
5.2 安装步骤
-
编译服务器程序:
cl FileServer.cpp ws2_32.lib -
编译客户端程序:
cl FileClientDlg.cpp user32.lib gdi32.lib comctl32.lib ws2_32.lib -
创建服务器存储目录:
mkdir ServerStorage
5.3 使用流程
-
启动服务器:
FileServer.exe -
启动客户端:
FileClient.exe -
在客户端界面:
- 输入服务器IP地址(默认127.0.0.1)
- 输入用户名和密码(默认user1/pass1)
- 点击"Connect"连接服务器
-
文件操作:
- 上传:选择本地文件,点击"Upload"
- 下载:双击文件列表中的文件
- 删除:选择文件,点击"Delete"
- 刷新:点击"List"按钮
六、系统特点
6.1 技术特点
- 多线程架构:服务器为每个客户端创建独立线程
- 非阻塞UI:文件传输在后台线程进行
- 流式传输:支持大文件分块传输
- 实时更新:文件列表变更时自动通知客户端
- 进度反馈:文件传输过程中显示进度
6.2 安全特性
- 用户认证:简单的用户名/密码验证
- 权限控制:用户只能删除自己上传的文件
- 数据隔离:每个用户的文件相互隔离
- 传输加密:可扩展为SSL/TLS加密传输
七、扩展功能建议
7.1 增强安全性
cpp
// 在服务器添加密码验证
bool VerifyCredentials(const string& username, const string& password) {
// 实际应用中应从数据库验证
map<string, string> credentials = {
{"user1", "5f4dcc3b5aa765d61d8327deb882cf99"}, // pass1的MD5
{"user2", "098f6bcd4621d373cade4e832627b4f6"} // pass2的MD5
};
if (credentials.find(username) != credentials.end()) {
return credentials[username] == md5(password);
}
return false;
}
7.2 添加文件搜索功能
cpp
// 在服务器添加搜索命令
else if (cmd == "SEARCH") {
string keyword = args;
string results = "Search results for \"" + keyword + "\":\n";
for (const auto& file : fileDatabase) {
if (file.first.find(keyword) != string::npos) {
results += file.first + " by " + file.second.owner + "\n";
}
}
send(clientSocket, results.c_str(), results.size(), 0);
}
7.3 实现断点续传
cpp
// 在文件传输中添加断点续传支持
void resumeFileTransfer(SOCKET socket, const string& filename, long startPos) {
string filePath = storagePath + filename;
ifstream file(filePath, ios::binary | ios::ate);
if (file) {
long currentSize = file.tellg();
if (currentSize < startPos) {
// 文件损坏,重新开始
startPos = 0;
}
} else {
startPos = 0;
}
// 发送续传请求
string cmd = "RESUME " + filename + " " + to_string(startPos) + "\n";
send(socket, cmd.c_str(), cmd.size(), 0);
// 继续传输...
}
八、总结
本系统实现了一个基于服务器的点对点文件传输平台,具有以下特点:
-
完整的C/S架构:
- 服务器处理连接、认证、文件路由
- 客户端提供用户友好的操作界面
- 支持多客户端并发访问
-
核心功能实现:
- 用户认证与权限管理
- 文件上传/下载/删除
- 文件列表实时更新
- 传输进度显示
-
技术亮点:
- 多线程并发处理
- 流式文件传输
- 非阻塞UI设计
- 实时事件通知
-
可扩展性:
- 易于添加新功能(如文件搜索、断点续传)
- 支持安全增强(SSL/TLS加密)
- 可集成数据库系统
系统适用于企业内网文件共享、分布式存储、教育资源共享等多种场景,通过简单的扩展可满足更复杂的应用需求。