FTP服务器与客户端实现(VC++)

FTP服务器与客户端实现(VC++)

FTP服务器和客户端的VC++实现,包含文件传输、目录操作和用户管理功能。

FTP服务器实现

服务器主程序 (FTPServer.cpp)

#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <direct.h>
#include <sys/stat.h>
#include <fstream>
#include <vector>
#include <map>
#include <algorithm>

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

#define PORT 21
#define DATA_PORT_START 1024
#define DATA_PORT_END 65535
#define BUFFER_SIZE 4096
#define MAX_CLIENTS 10

enum FTP_STATE {
    NOT_LOGGED_IN,
    LOGGED_IN
};

struct ClientInfo {
    SOCKET controlSocket;
    SOCKET dataSocket;
    SOCKADDR_IN dataAddr;
    FTP_STATE state;
    char currentDir[MAX_PATH];
    char username[50];
    bool passiveMode;
    int dataPort;
};

std::map<SOCKET, ClientInfo> clients;
std::map<std::string, std::string> users; // username -> password

void InitWinsock();
void CleanupWinsock();
void HandleClient(SOCKET clientSocket);
void ProcessCommand(SOCKET clientSocket, const char* command);
void SendResponse(SOCKET socket, const char* response);
void SendData(SOCKET dataSocket, const char* data);
void SendFile(SOCKET dataSocket, const char* filename);
void ReceiveFile(SOCKET dataSocket, const char* filename);
void ListDirectory(SOCKET dataSocket, const char* path);
void ChangeDirectory(const char* path, char* currentDir);
void CreateDirectory(const char* dirname);
void RemoveDirectory(const char* dirname);
void DeleteFile(const char* filename);
void RenameFile(const char* oldname, const char* newname);
void InitUsers();
bool AuthenticateUser(const char* username, const char* password);
void LogActivity(const char* message);

int main() {
    WSADATA wsaData;
    SOCKET listenSocket, clientSocket;
    SOCKADDR_IN serverAddr, clientAddr;
    int clientAddrLen = sizeof(clientAddr);
    int result;

    InitWinsock();
    InitUsers();

    // 创建监听套接字
    listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listenSocket == INVALID_SOCKET) {
        printf("socket failed with error: %d\n", WSAGetLastError());
        return 1;
    }

    // 配置服务器地址
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(PORT);

    // 绑定套接字
    result = bind(listenSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr));
    if (result == SOCKET_ERROR) {
        printf("bind failed with error: %d\n", WSAGetLastError());
        closesocket(listenSocket);
        return 1;
    }

    // 开始监听
    result = listen(listenSocket, SOMAXCONN);
    if (result == SOCKET_ERROR) {
        printf("listen failed with error: %d\n", WSAGetLastError());
        closesocket(listenSocket);
        return 1;
    }

    printf("FTP Server started on port %d\n", PORT);
    LogActivity("FTP Server started");

    while (true) {
        // 接受客户端连接
        clientSocket = accept(listenSocket, (SOCKADDR*)&clientAddr, &clientAddrLen);
        if (clientSocket == INVALID_SOCKET) {
            printf("accept failed with error: %d\n", WSAGetLastError());
            continue;
        }

        char ipStr[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &(clientAddr.sin_addr), ipStr, INET_ADDRSTRLEN);
        printf("Accepted connection from %s:%d\n", ipStr, ntohs(clientAddr.sin_port));
        LogActivity(std::string("Accepted connection from ") + ipStr);

        // 创建线程处理客户端
        DWORD threadId;
        HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)HandleClient, (LPVOID)clientSocket, 0, &threadId);
        if (hThread == NULL) {
            printf("CreateThread failed\n");
            closesocket(clientSocket);
        } else {
            CloseHandle(hThread);
        }
    }

    closesocket(listenSocket);
    CleanupWinsock();
    return 0;
}

void InitWinsock() {
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
        printf("WSAStartup failed: %d\n", result);
        exit(1);
    }
}

void CleanupWinsock() {
    WSACleanup();
}

void InitUsers() {
    users["admin"] = "admin123";
    users["user"] = "password";
    users["ftp"] = "ftppass";
}

bool AuthenticateUser(const char* username, const char* password) {
    auto it = users.find(username);
    if (it != users.end() && it->second == password) {
        return true;
    }
    return false;
}

void LogActivity(const char* message) {
    SYSTEMTIME st;
    GetLocalTime(&st);
    
    char logEntry[256];
    sprintf(logEntry, "[%04d-%02d-%02d %02d:%02d:%02d] %s\n", 
            st.wYear, st.wMonth, st.wDay, 
            st.wHour, st.wMinute, st.wSecond, 
            message);
    
    FILE* logFile = fopen("ftp_server.log", "a");
    if (logFile) {
        fputs(logEntry, logFile);
        fclose(logFile);
    }
}

void HandleClient(SOCKET clientSocket) {
    ClientInfo client;
    client.controlSocket = clientSocket;
    client.dataSocket = INVALID_SOCKET;
    client.state = NOT_LOGGED_IN;
    client.currentDir[0] = '\0';
    client.username[0] = '\0';
    client.passiveMode = false;
    client.dataPort = 0;
    
    strcpy(client.currentDir, "."); // 当前目录
    
    clients[clientSocket] = client;
    
    // 发送欢迎消息
    SendResponse(clientSocket, "220 Welcome to Simple FTP Server\r\n");
    
    char buffer[BUFFER_SIZE];
    int bytesReceived;
    
    while (true) {
        bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE - 1, 0);
        if (bytesReceived <= 0) {
            break;
        }
        
        buffer[bytesReceived] = '\0';
        printf("Received from client: %s", buffer);
        
        // 处理命令
        ProcessCommand(clientSocket, buffer);
    }
    
    // 清理客户端连接
    closesocket(clientSocket);
    if (client.dataSocket != INVALID_SOCKET) {
        closesocket(client.dataSocket);
    }
    clients.erase(clientSocket);
    
    printf("Client disconnected\n");
    LogActivity("Client disconnected");
}

void ProcessCommand(SOCKET clientSocket, const char* command) {
    char cmd[10], arg[256];
    sscanf(command, "%s %[^\r\n]", cmd, arg);
    
    // 转换为大写
    for (int i = 0; cmd[i]; i++) {
        cmd[i] = toupper(cmd[i]);
    }
    
    ClientInfo& client = clients[clientSocket];
    
    if (strcmp(cmd, "USER") == 0) {
        strncpy(client.username, arg, sizeof(client.username) - 1);
        SendResponse(clientSocket, "331 User name okay, need password.\r\n");
    }
    else if (strcmp(cmd, "PASS") == 0) {
        if (AuthenticateUser(client.username, arg)) {
            client.state = LOGGED_IN;
            SendResponse(clientSocket, "230 User logged in, proceed.\r\n");
            LogActivity(std::string("User logged in: ") + client.username);
        } else {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
        }
    }
    else if (strcmp(cmd, "SYST") == 0) {
        SendResponse(clientSocket, "215 Windows_NT\r\n");
    }
    else if (strcmp(cmd, "FEAT") == 0) {
        SendResponse(clientSocket, "211-Features supported:\r\n");
        SendResponse(clientSocket, " PASV\r\n");
        SendResponse(clientSocket, " PORT\r\n");
        SendResponse(clientSocket, "211 End\r\n");
    }
    else if (strcmp(cmd, "PWD") == 0) {
        char response[MAX_PATH + 20];
        sprintf(response, "257 \"%s\" is current directory.\r\n", client.currentDir);
        SendResponse(clientSocket, response);
    }
    else if (strcmp(cmd, "CWD") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        char newDir[MAX_PATH];
        ChangeDirectory(arg, newDir);
        if (strcmp(newDir, "") != 0) {
            strncpy(client.currentDir, newDir, sizeof(client.currentDir) - 1);
            char response[MAX_PATH + 20];
            sprintf(response, "250 Directory changed to \"%s\".\r\n", client.currentDir);
            SendResponse(clientSocket, response);
        } else {
            SendResponse(clientSocket, "550 Failed to change directory.\r\n");
        }
    }
    else if (strcmp(cmd, "CDUP") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        char parentDir[MAX_PATH];
        ChangeDirectory("..", parentDir);
        if (strcmp(parentDir, "") != 0) {
            strncpy(client.currentDir, parentDir, sizeof(client.currentDir) - 1);
            char response[MAX_PATH + 20];
            sprintf(response, "250 Directory changed to \"%s\".\r\n", client.currentDir);
            SendResponse(clientSocket, response);
        } else {
            SendResponse(clientSocket, "550 Failed to change directory.\r\n");
        }
    }
    else if (strcmp(cmd, "LIST") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        if (client.passiveMode) {
            // 被动模式
            SOCKET dataSocket = client.dataSocket;
            if (dataSocket == INVALID_SOCKET) {
                SendResponse(clientSocket, "425 Can't open data connection.\r\n");
                return;
            }
            
            SendResponse(clientSocket, "150 Opening ASCII mode data connection for file list.\r\n");
            ListDirectory(dataSocket, client.currentDir);
            SendResponse(clientSocket, "226 Transfer complete.\r\n");
            closesocket(dataSocket);
            client.dataSocket = INVALID_SOCKET;
        } else {
            // 主动模式
            SendResponse(clientSocket, "502 Active mode not implemented.\r\n");
        }
    }
    else if (strcmp(cmd, "RETR") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        if (client.passiveMode) {
            SOCKET dataSocket = client.dataSocket;
            if (dataSocket == INVALID_SOCKET) {
                SendResponse(clientSocket, "425 Can't open data connection.\r\n");
                return;
            }
            
            char filepath[MAX_PATH];
            sprintf(filepath, "%s\\%s", client.currentDir, arg);
            SendResponse(clientSocket, "150 Opening BINARY mode data connection for file transfer.\r\n");
            SendFile(dataSocket, filepath);
            SendResponse(clientSocket, "226 Transfer complete.\r\n");
            closesocket(dataSocket);
            client.dataSocket = INVALID_SOCKET;
        } else {
            SendResponse(clientSocket, "502 Active mode not implemented.\r\n");
        }
    }
    else if (strcmp(cmd, "STOR") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        if (client.passiveMode) {
            SOCKET dataSocket = client.dataSocket;
            if (dataSocket == INVALID_SOCKET) {
                SendResponse(clientSocket, "425 Can't open data connection.\r\n");
                return;
            }
            
            char filepath[MAX_PATH];
            sprintf(filepath, "%s\\%s", client.currentDir, arg);
            SendResponse(clientSocket, "150 Opening BINARY mode data connection for file transfer.\r\n");
            ReceiveFile(dataSocket, filepath);
            SendResponse(clientSocket, "226 Transfer complete.\r\n");
            closesocket(dataSocket);
            client.dataSocket = INVALID_SOCKET;
        } else {
            SendResponse(clientSocket, "502 Active mode not implemented.\r\n");
        }
    }
    else if (strcmp(cmd, "PASV") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        // 创建数据套接字
        SOCKET dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (dataSocket == INVALID_SOCKET) {
            SendResponse(clientSocket, "425 Can't create data socket.\r\n");
            return;
        }
        
        // 绑定到任意可用端口
        SOCKADDR_IN dataAddr;
        dataAddr.sin_family = AF_INET;
        dataAddr.sin_addr.s_addr = INADDR_ANY;
        dataAddr.sin_port = 0;
        
        if (bind(dataSocket, (SOCKADDR*)&dataAddr, sizeof(dataAddr)) == SOCKET_ERROR) {
            closesocket(dataSocket);
            SendResponse(clientSocket, "425 Can't bind data socket.\r\n");
            return;
        }
        
        // 获取绑定的端口
        int addrLen = sizeof(dataAddr);
        getsockname(dataSocket, (SOCKADDR*)&dataAddr, &addrLen);
        int port = ntohs(dataAddr.sin_port);
        
        // 监听
        if (listen(dataSocket, 1) == SOCKET_ERROR) {
            closesocket(dataSocket);
            SendResponse(clientSocket, "425 Can't listen on data socket.\r\n");
            return;
        }
        
        // 保存数据套接字信息
        client.dataSocket = dataSocket;
        client.passiveMode = true;
        client.dataPort = port;
        
        // 发送响应
        char ipStr[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &dataAddr.sin_addr, ipStr, INET_ADDRSTRLEN);
        
        // 将IP地址和端口转换为PASV响应格式
        int ip1, ip2, ip3, ip4;
        sscanf(ipStr, "%d.%d.%d.%d", &ip1, &ip2, &ip3, &ip4);
        
        char response[100];
        sprintf(response, "227 Entering Passive Mode (%d,%d,%d,%d,%d,%d).\r\n", 
                ip1, ip2, ip3, ip4, port / 256, port % 256);
        SendResponse(clientSocket, response);
    }
    else if (strcmp(cmd, "PORT") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        int h1, h2, h3, h4, p1, p2;
        sscanf(arg, "%d,%d,%d,%d,%d,%d", &h1, &h2, &h3, &h4, &p1, &p2);
        
        SOCKADDR_IN dataAddr;
        dataAddr.sin_family = AF_INET;
        dataAddr.sin_addr.s_addr = inet_addr(std::to_string(h1).append(".").append(std::to_string(h2)).append(".").append(std::to_string(h3)).append(".").append(std::to_string(h4)).c_str());
        dataAddr.sin_port = htons(p1 * 256 + p2);
        
        client.dataAddr = dataAddr;
        client.passiveMode = false;
        
        SendResponse(clientSocket, "200 Port command successful.\r\n");
    }
    else if (strcmp(cmd, "MKD") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        char dirpath[MAX_PATH];
        sprintf(dirpath, "%s\\%s", client.currentDir, arg);
        CreateDirectory(dirpath);
        char response[MAX_PATH + 20];
        sprintf(response, "257 \"%s\" directory created.\r\n", arg);
        SendResponse(clientSocket, response);
    }
    else if (strcmp(cmd, "RMD") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        char dirpath[MAX_PATH];
        sprintf(dirpath, "%s\\%s", client.currentDir, arg);
        RemoveDirectory(dirpath);
        SendResponse(clientSocket, "250 Directory removed.\r\n");
    }
    else if (strcmp(cmd, "DELE") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        char filepath[MAX_PATH];
        sprintf(filepath, "%s\\%s", client.currentDir, arg);
        DeleteFile(filepath);
        SendResponse(clientSocket, "250 File deleted.\r\n");
    }
    else if (strcmp(cmd, "RNFR") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        // 保存旧文件名
        char oldpath[MAX_PATH];
        sprintf(oldpath, "%s\\%s", client.currentDir, arg);
        // 在实际实现中,需要存储这个路径
        SendResponse(clientSocket, "350 File exists, ready for destination name.\r\n");
    }
    else if (strcmp(cmd, "RNTO") == 0) {
        if (client.state != LOGGED_IN) {
            SendResponse(clientSocket, "530 Not logged in.\r\n");
            return;
        }
        
        // 在实际实现中,需要获取之前保存的旧文件名
        char newpath[MAX_PATH];
        sprintf(newpath, "%s\\%s", client.currentDir, arg);
        // RenameFile(oldpath, newpath);
        SendResponse(clientSocket, "250 File renamed.\r\n");
    }
    else if (strcmp(cmd, "QUIT") == 0) {
        SendResponse(clientSocket, "221 Goodbye.\r\n");
        closesocket(clientSocket);
    }
    else {
        SendResponse(clientSocket, "502 Command not implemented.\r\n");
    }
}

void SendResponse(SOCKET socket, const char* response) {
    send(socket, response, strlen(response), 0);
    printf("Sent: %s", response);
}

void SendData(SOCKET dataSocket, const char* data) {
    send(dataSocket, data, strlen(data), 0);
}

void SendFile(SOCKET dataSocket, const char* filename) {
    FILE* file = fopen(filename, "rb");
    if (!file) {
        return;
    }
    
    char buffer[BUFFER_SIZE];
    size_t bytesRead;
    
    while ((bytesRead = fread(buffer, 1, BUFFER_SIZE, file)) > 0) {
        send(dataSocket, buffer, bytesRead, 0);
    }
    
    fclose(file);
}

void ReceiveFile(SOCKET dataSocket, const char* filename) {
    FILE* file = fopen(filename, "wb");
    if (!file) {
        return;
    }
    
    char buffer[BUFFER_SIZE];
    int bytesReceived;
    
    while ((bytesReceived = recv(dataSocket, buffer, BUFFER_SIZE, 0)) > 0) {
        fwrite(buffer, 1, bytesReceived, file);
    }
    
    fclose(file);
}

void ListDirectory(SOCKET dataSocket, const char* path) {
    WIN32_FIND_DATA findData;
    HANDLE hFind;
    char searchPath[MAX_PATH];
    sprintf(searchPath, "%s\\*", path);
    
    hFind = FindFirstFile(searchPath, &findData);
    if (hFind == INVALID_HANDLE_VALUE) {
        return;
    }
    
    do {
        // 跳过 "." 和 ".."
        if (strcmp(findData.cFileName, ".") == 0 || strcmp(findData.cFileName, "..") == 0) {
            continue;
        }
        
        char fileInfo[256];
        if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            sprintf(fileInfo, "drwxr-xr-x 1 user group %10llu %s\r\n", 
                    (unsigned long long)findData.nFileSizeLow, 
                    findData.cFileName);
        } else {
            sprintf(fileInfo, "-rw-r--r-- 1 user group %10llu %s\r\n", 
                    (unsigned long long)findData.nFileSizeLow, 
                    findData.cFileName);
        }
        
        SendData(dataSocket, fileInfo);
    } while (FindNextFile(hFind, &findData));
    
    FindClose(hFind);
}

void ChangeDirectory(const char* path, char* currentDir) {
    char newDir[MAX_PATH];
    
    if (strcmp(path, "..") == 0) {
        // 上级目录
        _chdir("..");
        _getcwd(newDir, MAX_PATH);
    } else if (path[0] == '/') {
        // 绝对路径
        _chdir(path);
        _getcwd(newDir, MAX_PATH);
    } else {
        // 相对路径
        char tempDir[MAX_PATH];
        _getcwd(tempDir, MAX_PATH);
        strcat(tempDir, "\\");
        strcat(tempDir, path);
        if (_chdir(tempDir) == 0) {
            _getcwd(newDir, MAX_PATH);
        } else {
            newDir[0] = '\0';
        }
    }
    
    if (strlen(newDir) > 0) {
        strncpy(currentDir, newDir, MAX_PATH - 1);
    }
}

void CreateDirectory(const char* dirname) {
    _mkdir(dirname);
}

void RemoveDirectory(const char* dirname) {
    _rmdir(dirname);
}

void DeleteFile(const char* filename) {
    remove(filename);
}

void RenameFile(const char* oldname, const char* newname) {
    rename(oldname, newname);
}

FTP客户端实现

客户端主程序 (FTPClientDlg.cpp)

#include "pch.h"
#include "framework.h"
#include "FTPClient.h"
#include "FTPClientDlg.h"
#include "afxdialogex.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// CFTPClientDlg 对话框
CFTPClientDlg::CFTPClientDlg(CWnd* pParent /*=nullptr*/)
    : CDialogEx(IDD_FTPCLIENT_DIALOG, pParent)
    , m_host(_T(""))
    , m_port(21)
    , m_username(_T(""))
    , m_password(_T(""))
    , m_connected(false)
    , m_loggedIn(false)
    , m_passiveMode(true)
    , m_dataSocket(INVALID_SOCKET)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CFTPClientDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_LIST_COMMANDS, m_listCommands);
    DDX_Control(pDX, IDC_EDIT_LOG, m_editLog);
    DDX_Text(pDX, IDC_EDIT_HOST, m_host);
    DDX_Text(pDX, IDC_EDIT_PORT, m_port);
    DDX_Text(pDX, IDC_EDIT_USERNAME, m_username);
    DDX_Text(pDX, IDC_EDIT_PASSWORD, m_password);
    DDX_Control(pDX, IDC_BUTTON_CONNECT, m_btnConnect);
    DDX_Control(pDX, IDC_BUTTON_DISCONNECT, m_btnDisconnect);
    DDX_Control(pDX, IDC_BUTTON_SEND, m_btnSend);
    DDX_Control(pDX, IDC_EDIT_COMMAND, m_editCommand);
    DDX_Control(pDX, IDC_TREE_FILES, m_treeFiles);
    DDX_Control(pDX, IDC_STATUS_TEXT, m_statusText);
}

BEGIN_MESSAGE_MAP(CFTPClientDlg, CDialogEx)
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()
    ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CFTPClientDlg::OnBnClickedButtonConnect)
    ON_BN_CLICKED(IDC_BUTTON_DISCONNECT, &CFTPClientDlg::OnBnClickedButtonDisconnect)
    ON_BN_CLICKED(IDC_BUTTON_SEND, &CFTPClientDlg::OnBnClickedButtonSend)
    ON_LBN_DBLCLK(IDC_LIST_COMMANDS, &CFTPClientDlg::OnLbnDblclkListCommands)
    ON_EN_CHANGE(IDC_EDIT_COMMAND, &CFTPClientDlg::OnEnChangeEditCommand)
    ON_NOTIFY(NM_DBLCLK, IDC_TREE_FILES, &CFTPClientDlg::OnNMDblclkTreeFiles)
    ON_BN_CLICKED(IDC_BUTTON_UPLOAD, &CFTPClientDlg::OnBnClickedButtonUpload)
    ON_BN_CLICKED(IDC_BUTTON_DOWNLOAD, &CFTPClientDlg::OnBnClickedButtonDownload)
END_MESSAGE_MAP()

// CFTPClientDlg 消息处理程序
BOOL CFTPClientDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // 设置此对话框的图标
    SetIcon(m_hIcon, TRUE);         // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标

    // 初始化命令列表
    m_listCommands.AddString(_T("USER <username> - 设置用户名"));
    m_listCommands.AddString(_T("PASS <password> - 设置密码"));
    m_listCommands.AddString(_T("LIST - 列出当前目录内容"));
    m_listCommands.AddString(_T("CWD <directory> - 改变工作目录"));
    m_listCommands.AddString(_T("PWD - 显示当前工作目录"));
    m_listCommands.AddString(_T("RETR <filename> - 下载文件"));
    m_listCommands.AddString(_T("STOR <filename> - 上传文件"));
    m_listCommands.AddString(_T("MKD <directory> - 创建目录"));
    m_listCommands.AddString(_T("RMD <directory> - 删除目录"));
    m_listCommands.AddString(_T("DELE <filename> - 删除文件"));
    m_listCommands.AddString(_T("PASV - 进入被动模式"));
    m_listCommands.AddString(_T("QUIT - 退出连接"));

    // 初始化树控件
    m_imageList.Create(16, 16, ILC_COLOR32, 0, 10);
    m_imageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_FOLDER));
    m_imageList.Add(AfxGetApp()->LoadIcon(IDI_ICON_FILE));
    m_treeFiles.SetImageList(&m_imageList, TVSIL_NORMAL);

    // 禁用断开连接按钮
    m_btnDisconnect.EnableWindow(FALSE);
    m_btnSend.EnableWindow(FALSE);
    
    // 设置默认主机
    m_host = _T("127.0.0.1");
    m_port = 21;
    m_username = _T("user");
    m_password = _T("password");
    UpdateData(FALSE);

    return TRUE;
}

void CFTPClientDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // 用于绘制的设备上下文

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // 使图标在工作区矩形中居中
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // 绘制图标
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
}

HCURSOR CFTPClientDlg::OnQueryDragIcon()
{
    return static_cast<HCURSOR>(m_hIcon);
}

void CFTPClientDlg::LogMessage(const CString& message)
{
    CString log;
    m_editLog.GetWindowText(log);
    log += message + _T("\r\n");
    m_editLog.SetWindowText(log);
    m_editLog.LineScroll(m_editLog.GetLineCount());
}

void CFTPClientDlg::OnBnClickedButtonConnect()
{
    UpdateData(TRUE);
    
    if (m_host.IsEmpty()) {
        AfxMessageBox(_T("请输入服务器地址"));
        return;
    }
    
    if (m_port <= 0 || m_port > 65535) {
        AfxMessageBox(_T("端口号无效"));
        return;
    }
    
    // 初始化Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        AfxMessageBox(_T("WSAStartup失败"));
        return;
    }
    
    // 创建套接字
    m_controlSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_controlSocket == INVALID_SOCKET) {
        AfxMessageBox(_T("创建套接字失败"));
        WSACleanup();
        return;
    }
    
    // 解析服务器地址
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(m_port);
    
    if (inet_pton(AF_INET, CT2A(m_host), &serverAddr.sin_addr) <= 0) {
        AfxMessageBox(_T("无效的服务器地址"));
        closesocket(m_controlSocket);
        WSACleanup();
        return;
    }
    
    // 连接服务器
    if (connect(m_controlSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        CString error;
        error.Format(_T("连接服务器失败: %d"), WSAGetLastError());
        AfxMessageBox(error);
        closesocket(m_controlSocket);
        WSACleanup();
        return;
    }
    
    m_connected = true;
    m_loggedIn = false;
    
    // 更新UI
    m_btnConnect.EnableWindow(FALSE);
    m_btnDisconnect.EnableWindow(TRUE);
    m_btnSend.EnableWindow(TRUE);
    
    // 启动接收线程
    AfxBeginThread(ReceiveThread, this);
    
    LogMessage(_T("已连接到服务器"));
    m_statusText.SetWindowText(_T("已连接"));
}

UINT CFTPClientDlg::ReceiveThread(LPVOID pParam)
{
    CFTPClientDlg* pDlg = (CFTPClientDlg*)pParam;
    char buffer[4096];
    
    while (pDlg->m_connected) {
        int bytesReceived = recv(pDlg->m_controlSocket, buffer, sizeof(buffer) - 1, 0);
        if (bytesReceived <= 0) {
            pDlg->m_connected = false;
            pDlg->LogMessage(_T("与服务器的连接已断开"));
            pDlg->m_statusText.SetWindowText(_T("已断开连接"));
            break;
        }
        
        buffer[bytesReceived] = '\0';
        CString response(buffer);
        pDlg->LogMessage(_T("服务器: ") + response);
        
        // 检查登录状态
        if (response.Left(3) == _T("230")) {
            pDlg->m_loggedIn = true;
            pDlg->m_statusText.SetWindowText(_T("已登录"));
        }
    }
    
    return 0;
}

void CFTPClientDlg::OnBnClickedButtonDisconnect()
{
    if (m_connected) {
        // 发送QUIT命令
        CString command = _T("QUIT\r\n");
        send(m_controlSocket, CT2A(command), command.GetLength(), 0);
        
        // 关闭套接字
        closesocket(m_controlSocket);
        m_connected = false;
        m_loggedIn = false;
        
        // 更新UI
        m_btnConnect.EnableWindow(TRUE);
        m_btnDisconnect.EnableWindow(FALSE);
        m_btnSend.EnableWindow(FALSE);
        
        LogMessage(_T("已断开与服务器的连接"));
        m_statusText.SetWindowText(_T("已断开连接"));
    }
}

void CFTPClientDlg::OnBnClickedButtonSend()
{
    if (!m_connected) {
        AfxMessageBox(_T("未连接到服务器"));
        return;
    }
    
    CString command;
    m_editCommand.GetWindowText(command);
    
    if (command.IsEmpty()) {
        return;
    }
    
    // 添加换行符
    command += _T("\r\n");
    
    // 发送命令
    send(m_controlSocket, CT2A(command), command.GetLength(), 0);
    
    // 记录命令
    LogMessage(_T("客户端: ") + command);
    
    // 清空输入框
    m_editCommand.SetWindowText(_T(""));
}

void CFTPClientDlg::OnLbnDblclkListCommands()
{
    int index = m_listCommands.GetCurSel();
    if (index != LB_ERR) {
        CString command;
        m_listCommands.GetText(index, command);
        
        // 提取命令部分
        int spacePos = command.Find(' ');
        if (spacePos != -1) {
            command = command.Left(spacePos);
        }
        
        m_editCommand.SetWindowText(command);
    }
}

void CFTPClientDlg::OnEnChangeEditCommand()
{
    // TODO:  如果该控件是 RICHEDIT 控件,它将不
    // 发送此通知,除非重写 CDialogEx::OnInitDialog()
    // 函数并调用 CRichEditCtrl().SetEventMask(),
    // 同时将 ENM_CHANGE 标志"或"运算到掩码中。
}

void CFTPClientDlg::OnNMDblclkTreeFiles(NMHDR *pNMHDR, LRESULT *pResult)
{
    *pResult = 0;
    
    if (!m_loggedIn) {
        AfxMessageBox(_T("请先登录"));
        return;
    }
    
    HTREEITEM hItem = m_treeFiles.GetSelectedItem();
    if (hItem) {
        CString name = m_treeFiles.GetItemText(hItem);
        BOOL isFolder = m_treeFiles.GetItemData(hItem) == 0;
        
        if (isFolder) {
            // 进入目录
            CString command;
            command.Format(_T("CWD %s\r\n"), name);
            send(m_controlSocket, CT2A(command), command.GetLength(), 0);
            LogMessage(_T("客户端: ") + command);
        } else {
            // 下载文件
            DownloadFile(name);
        }
    }
}

void CFTPClientDlg::OnBnClickedButtonUpload()
{
    if (!m_loggedIn) {
        AfxMessageBox(_T("请先登录"));
        return;
    }
    
    CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST, _T("所有文件 (*.*)|*.*||"));
    if (dlg.DoModal() == IDOK) {
        CString filePath = dlg.GetPathName();
        CString fileName = dlg.GetFileName();
        
        UploadFile(filePath, fileName);
    }
}

void CFTPClientDlg::OnBnClickedButtonDownload()
{
    if (!m_loggedIn) {
        AfxMessageBox(_T("请先登录"));
        return;
    }
    
    HTREEITEM hItem = m_treeFiles.GetSelectedItem();
    if (hItem) {
        CString name = m_treeFiles.GetItemText(hItem);
        BOOL isFolder = m_treeFiles.GetItemData(hItem) == 0;
        
        if (!isFolder) {
            DownloadFile(name);
        } else {
            AfxMessageBox(_T("不能下载目录"));
        }
    } else {
        AfxMessageBox(_T("请选择要下载的文件"));
    }
}

void CFTPClientDlg::UploadFile(const CString& localPath, const CString& remoteName)
{
    // 进入被动模式
    CString command = _T("PASV\r\n");
    send(m_controlSocket, CT2A(command), command.GetLength(), 0);
    LogMessage(_T("客户端: ") + command);
    
    // 等待服务器响应
    Sleep(500);
    
    // 发送STOR命令
    command.Format(_T("STOR %s\r\n"), remoteName);
    send(m_controlSocket, CT2A(command), command.GetLength(), 0);
    LogMessage(_T("客户端: ") + command);
    
    // 创建数据套接字
    m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_dataSocket == INVALID_SOCKET) {
        AfxMessageBox(_T("创建数据套接字失败"));
        return;
    }
    
    // 连接数据端口
    // 在实际实现中,需要解析PASV响应获取端口
    sockaddr_in dataAddr;
    dataAddr.sin_family = AF_INET;
    dataAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    dataAddr.sin_port = htons(1025); // 示例端口
    
    if (connect(m_dataSocket, (sockaddr*)&dataAddr, sizeof(dataAddr)) == SOCKET_ERROR) {
        CString error;
        error.Format(_T("连接数据端口失败: %d"), WSAGetLastError());
        AfxMessageBox(error);
        closesocket(m_dataSocket);
        m_dataSocket = INVALID_SOCKET;
        return;
    }
    
    // 发送文件
    FILE* file = _tfopen(localPath, _T("rb"));
    if (file) {
        char buffer[4096];
        size_t bytesRead;
        
        while ((bytesRead = fread(buffer, 1, sizeof(buffer), file)) > 0) {
            send(m_dataSocket, buffer, bytesRead, 0);
        }
        
        fclose(file);
        LogMessage(_T("文件上传完成: ") + remoteName);
    } else {
        AfxMessageBox(_T("无法打开本地文件"));
    }
    
    closesocket(m_dataSocket);
    m_dataSocket = INVALID_SOCKET;
}

void CFTPClientDlg::DownloadFile(const CString& remoteName)
{
    // 进入被动模式
    CString command = _T("PASV\r\n");
    send(m_controlSocket, CT2A(command), command.GetLength(), 0);
    LogMessage(_T("客户端: ") + command);
    
    // 等待服务器响应
    Sleep(500);
    
    // 发送RETR命令
    command.Format(_T("RETR %s\r\n"), remoteName);
    send(m_controlSocket, CT2A(command), command.GetLength(), 0);
    LogMessage(_T("客户端: ") + command);
    
    // 创建数据套接字
    m_dataSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_dataSocket == INVALID_SOCKET) {
        AfxMessageBox(_T("创建数据套接字失败"));
        return;
    }
    
    // 连接数据端口
    // 在实际实现中,需要解析PASV响应获取端口
    sockaddr_in dataAddr;
    dataAddr.sin_family = AF_INET;
    dataAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    dataAddr.sin_port = htons(1025); // 示例端口
    
    if (connect(m_dataSocket, (sockaddr*)&dataAddr, sizeof(dataAddr)) == SOCKET_ERROR) {
        CString error;
        error.Format(_T("连接数据端口失败: %d"), WSAGetLastError());
        AfxMessageBox(error);
        closesocket(m_dataSocket);
        m_dataSocket = INVALID_SOCKET;
        return;
    }
    
    // 选择保存位置
    CFileDialog dlg(FALSE, NULL, remoteName, OFN_OVERWRITEPROMPT, _T("所有文件 (*.*)|*.*||"));
    if (dlg.DoModal() == IDOK) {
        CString savePath = dlg.GetPathName();
        
        // 接收文件
        FILE* file = _tfopen(savePath, _T("wb"));
        if (file) {
            char buffer[4096];
            int bytesReceived;
            
            while ((bytesReceived = recv(m_dataSocket, buffer, sizeof(buffer), 0)) > 0) {
                fwrite(buffer, 1, bytesReceived, file);
            }
            
            fclose(file);
            LogMessage(_T("文件下载完成: ") + savePath);
        } else {
            AfxMessageBox(_T("无法保存文件"));
        }
    }
    
    closesocket(m_dataSocket);
    m_dataSocket = INVALID_SOCKET;
}

系统功能与特点

服务器功能

  1. 用户认证:支持多用户登录(admin/user/ftp)

  2. 文件操作

    • 上传/下载文件
    • 创建/删除目录
    • 删除文件
    • 重命名文件
  3. 目录导航

    • 显示当前目录
    • 改变工作目录
    • 返回上级目录
  4. 数据传输模式

    • 主动模式(PORT)
    • 被动模式(PASV)
  5. 日志记录:记录所有操作和连接信息

客户端功能

  1. 连接管理

    • 连接/断开服务器
    • 支持自定义主机和端口
  2. 命令执行

    • 支持所有标准FTP命令
    • 命令历史记录
  3. 文件管理

    • 上传/下载文件
    • 目录浏览
    • 文件树形视图
  4. 用户界面

    • 直观的图形界面
    • 实时日志显示
    • 状态指示器

技术实现细节

1. 网络通信

2. 协议实现

3. 文件操作

4. 多线程处理

参考代码 Ftp服务器与客户端VC++ www.youwenfan.com/contentcss/122495.html

编译与运行

服务器编译

  1. 创建Win32控制台应用程序
  2. 添加上述FTPServer.cpp代码
  3. 链接ws2_32.lib
  4. 编译运行

客户端编译

  1. 创建MFC应用程序(对话框)
  2. 添加上述FTPClientDlg.cpp代码
  3. 添加资源文件(图标等)
  4. 编译运行

使用说明

  1. 先运行服务器程序
  2. 运行客户端程序
  3. 输入服务器地址、端口、用户名和密码
  4. 点击"连接"按钮
  5. 使用命令或图形界面操作

安全增强建议

  1. 加密传输

    // 使用SSL/TLS加密
    SSL_CTX* ctx = SSL_CTX_new(SSLv23_client_method());
    SSL* ssl = SSL_new(ctx);
    SSL_set_fd(ssl, socket);
    SSL_connect(ssl);
    
  2. 用户管理

    // 从数据库加载用户
    void LoadUsersFromDB(const char* dbPath) {
        // 连接数据库
        sqlite3* db;
        sqlite3_open(dbPath, &db);
    
        // 查询用户表
        sqlite3_stmt* stmt;
        sqlite3_prepare_v2(db, "SELECT username, password FROM users", -1, &stmt, NULL);
    
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            const char* username = (const char*)sqlite3_column_text(stmt, 0);
            const char* password = (const char*)sqlite3_column_text(stmt, 1);
            users[username] = password;
        }
    
        sqlite3_finalize(stmt);
        sqlite3_close(db);
    }
    
  3. 权限控制

    // 检查用户权限
    bool CheckPermission(const char* username, const char* operation) {
        if (strcmp(operation, "DELETE") == 0 && strcmp(username, "admin") != 0) {
            return false;
        }
        return true;
    }
    
  4. 日志记录增强

    // 记录详细操作日志
    void LogDetailedAction(const char* username, const char* action, const char* details) {
        SYSTEMTIME st;
        GetLocalTime(&st);
    
        char logEntry[512];
        sprintf(logEntry, "[%04d-%02d-%02d %02d:%02d:%02d] User: %s, Action: %s, Details: %s\n",
                st.wYear, st.wMonth, st.wDay,
                st.wHour, st.wMinute, st.wSecond,
                username, action, details);
    
        // 写入日志文件
    }
    

扩展功能建议

  1. 断点续传

    // 实现REST命令
    void ResumeTransfer(SOCKET dataSocket, const char* filename, long offset) {
        FILE* file = fopen(filename, "rb");
        if (file) {
            fseek(file, offset, SEEK_SET);
            // 发送剩余部分
        }
    }
    
  2. 目录同步

    // 递归同步目录
    void SyncDirectories(const char* localPath, const char* remotePath) {
        // 比较本地和远程文件
        // 上传/下载差异文件
    }
    
  3. 带宽控制

    // 限制传输速度
    void ThrottleTransfer(SOCKET socket, int maxKBps) {
        // 使用定时器控制发送速率
    }
    
  4. 文件压缩

    // 传输前压缩文件
    void CompressFile(const char* src, const char* dest) {
        // 使用zlib压缩
    }
    
  5. 计划任务

    // 定时执行文件传输
    void ScheduleTransfer(const char* schedule, const char* command) {
        // 使用Windows任务计划程序
    }
    

常见问题解决

  1. 连接被拒绝

    • 检查服务器是否运行
    • 检查防火墙设置
    • 确认端口正确
  2. 文件传输中断

    • 检查网络连接
    • 增加超时设置
    • 使用断点续传
  3. 权限问题

    • 以管理员身份运行服务器
    • 检查文件/目录权限
    • 使用正确的用户名/密码
  4. 性能问题

    • 使用被动模式
    • 增加缓冲区大小
    • 使用更快的磁盘

注意:此实现为简化版FTP系统,实际生产环境应使用更健壮的实现,如FileZilla Server或ProFTPD,并考虑添加SSL/TLS加密、更精细的权限控制、配额管理等高级功能。

专注于matlab/simulink,电子电路,编程