Windows网络编程
8、实现FTP服务器
1、课程目标
- 理解FTP协议工作原理
- 实现FTP服务器核心功能
- 掌握主动和被动模式
- 实现文件上传下载
2、名词解释
| 术语 | 解释 |
|---|---|
| FTP | File Transfer Protocol文件传输协议 |
| 控制连接 | 端口21,传输命令 |
| 数据连接 | 端口20或随机,传输数据 |
| 主动模式 | PORT,服务器主动连接客户端 |
| 被动模式 | PASV,客户端连接服务器 |
3、代码实现
3.1、示例1:FTP服务器
// FtpServer.c - FTP服务器
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <direct.h>
#pragma comment(lib, "ws2_32.lib")
#define FTP_PORT 21
#define FTP_ROOT "C:\\FtpRoot"
typedef struct _FTP_SESSION {
SOCKET controlSocket;
SOCKET dataSocket;
SOCKET pasvSocket;
char currentDir[MAX_PATH];
char username[64];
BOOL authenticated;
BOOL pasvMode;
} FTP_SESSION;
void SendResponse(SOCKET sock, const char* response) {
send(sock, response, strlen(response), 0);
printf("[FTP] -> %s", response);
}
void HandleUser(FTP_SESSION* session, const char* username) {
strcpy_s(session->username, sizeof(session->username), username);
SendResponse(session->controlSocket, "331 Password required\r\n");
}
void HandlePass(FTP_SESSION* session, const char* password) {
// 简化认证
if (strcmp(session->username, "admin") == 0 && strcmp(password, "admin") == 0) {
session->authenticated = TRUE;
SendResponse(session->controlSocket, "230 User logged in\r\n");
} else {
SendResponse(session->controlSocket, "530 Login incorrect\r\n");
}
}
void HandlePwd(FTP_SESSION* session) {
char response[512];
sprintf_s(response, sizeof(response), "257 \"%s\"\r\n", session->currentDir);
SendResponse(session->controlSocket, response);
}
void HandlePasv(FTP_SESSION* session) {
struct sockaddr_in addr;
int addrLen = sizeof(addr);
// 创建被动模式监听套接字
session->pasvSocket = socket(AF_INET, SOCK_STREAM, 0);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0; // 随机端口
bind(session->pasvSocket, (struct sockaddr*)&addr, sizeof(addr));
listen(session->pasvSocket, 1);
getsockname(session->pasvSocket, (struct sockaddr*)&addr, &addrLen);
// 获取本机IP
char hostname[256];
gethostname(hostname, sizeof(hostname));
struct hostent* host = gethostbyname(hostname);
BYTE* ip = (BYTE*)host->h_addr;
WORD port = ntohs(addr.sin_port);
char response[128];
sprintf_s(response, sizeof(response),
"227 Entering Passive Mode (%d,%d,%d,%d,%d,%d)\r\n",
ip[0], ip[1], ip[2], ip[3], port / 256, port % 256);
SendResponse(session->controlSocket, response);
session->pasvMode = TRUE;
}
void HandleList(FTP_SESSION* session) {
if (session->pasvMode) {
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
session->dataSocket = accept(session->pasvSocket, (struct sockaddr*)&clientAddr, &addrLen);
}
if (session->dataSocket == INVALID_SOCKET) {
SendResponse(session->controlSocket, "425 Can't open data connection\r\n");
return;
}
SendResponse(session->controlSocket, "150 Opening data connection\r\n");
// 列出目录
char fullPath[MAX_PATH];
sprintf_s(fullPath, sizeof(fullPath), "%s%s\\*", FTP_ROOT, session->currentDir);
WIN32_FIND_DATAA findData;
HANDLE hFind = FindFirstFileA(fullPath, &findData);
if (hFind != INVALID_HANDLE_VALUE) {
char listLine[512];
do {
char type = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? 'd' : '-';
sprintf_s(listLine, sizeof(listLine),
"%crwxr-xr-x 1 owner group %10d Jan 01 00:00 %s\r\n",
type, findData.nFileSizeLow, findData.cFileName);
send(session->dataSocket, listLine, strlen(listLine), 0);
} while (FindNextFileA(hFind, &findData));
FindClose(hFind);
}
closesocket(session->dataSocket);
session->dataSocket = INVALID_SOCKET;
closesocket(session->pasvSocket);
session->pasvSocket = INVALID_SOCKET;
session->pasvMode = FALSE;
SendResponse(session->controlSocket, "226 Transfer complete\r\n");
}
void HandleRetr(FTP_SESSION* session, const char* filename) {
if (session->pasvMode) {
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
session->dataSocket = accept(session->pasvSocket, (struct sockaddr*)&clientAddr, &addrLen);
}
char fullPath[MAX_PATH];
sprintf_s(fullPath, sizeof(fullPath), "%s%s\\%s", FTP_ROOT, session->currentDir, filename);
HANDLE hFile = CreateFileA(fullPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
SendResponse(session->controlSocket, "550 File not found\r\n");
return;
}
SendResponse(session->controlSocket, "150 Opening data connection\r\n");
char buffer[8192];
DWORD bytesRead;
while (ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL) && bytesRead > 0) {
send(session->dataSocket, buffer, bytesRead, 0);
}
CloseHandle(hFile);
closesocket(session->dataSocket);
session->dataSocket = INVALID_SOCKET;
SendResponse(session->controlSocket, "226 Transfer complete\r\n");
}
void HandleStor(FTP_SESSION* session, const char* filename) {
if (session->pasvMode) {
struct sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
session->dataSocket = accept(session->pasvSocket, (struct sockaddr*)&clientAddr, &addrLen);
}
char fullPath[MAX_PATH];
sprintf_s(fullPath, sizeof(fullPath), "%s%s\\%s", FTP_ROOT, session->currentDir, filename);
HANDLE hFile = CreateFileA(fullPath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
SendResponse(session->controlSocket, "550 Cannot create file\r\n");
return;
}
SendResponse(session->controlSocket, "150 Opening data connection\r\n");
char buffer[8192];
int recvLen;
DWORD bytesWritten;
while ((recvLen = recv(session->dataSocket, buffer, sizeof(buffer), 0)) > 0) {
WriteFile(hFile, buffer, recvLen, &bytesWritten, NULL);
}
CloseHandle(hFile);
closesocket(session->dataSocket);
session->dataSocket = INVALID_SOCKET;
SendResponse(session->controlSocket, "226 Transfer complete\r\n");
}
void ProcessCommand(FTP_SESSION* session, const char* command) {
char cmd[16], arg[256];
arg[0] = '\0';
sscanf_s(command, "%s %[^\r\n]", cmd, 16, arg, 256);
printf("[FTP] <- %s %s\n", cmd, arg);
if (_stricmp(cmd, "USER") == 0) HandleUser(session, arg);
else if (_stricmp(cmd, "PASS") == 0) HandlePass(session, arg);
else if (_stricmp(cmd, "PWD") == 0) HandlePwd(session);
else if (_stricmp(cmd, "PASV") == 0) HandlePasv(session);
else if (_stricmp(cmd, "LIST") == 0) HandleList(session);
else if (_stricmp(cmd, "RETR") == 0) HandleRetr(session, arg);
else if (_stricmp(cmd, "STOR") == 0) HandleStor(session, arg);
else if (_stricmp(cmd, "TYPE") == 0) SendResponse(session->controlSocket, "200 Type set\r\n");
else if (_stricmp(cmd, "SYST") == 0) SendResponse(session->controlSocket, "215 Windows\r\n");
else if (_stricmp(cmd, "QUIT") == 0) SendResponse(session->controlSocket, "221 Goodbye\r\n");
else SendResponse(session->controlSocket, "502 Command not implemented\r\n");
}
DWORD WINAPI FtpClientThread(LPVOID lpParam) {
FTP_SESSION session = {0};
session.controlSocket = (SOCKET)lpParam;
strcpy_s(session.currentDir, sizeof(session.currentDir), "/");
SendResponse(session.controlSocket, "220 FTP Server Ready\r\n");
char buffer[1024];
int recvLen;
while ((recvLen = recv(session.controlSocket, buffer, sizeof(buffer) - 1, 0)) > 0) {
buffer[recvLen] = '\0';
ProcessCommand(&session, buffer);
}
closesocket(session.controlSocket);
return 0;
}
int main() {
WSADATA wsaData;
SOCKET listenSocket;
CreateDirectoryA(FTP_ROOT, NULL);
WSAStartup(MAKEWORD(2, 2), &wsaData);
listenSocket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(FTP_PORT);
bind(listenSocket, (struct sockaddr*)&addr, sizeof(addr));
listen(listenSocket, 5);
printf("FTP Server listening on port %d\n", FTP_PORT);
printf("Root directory: %s\n", FTP_ROOT);
while (1) {
SOCKET client = accept(listenSocket, NULL, NULL);
if (client != INVALID_SOCKET) {
CreateThread(NULL, 0, FtpClientThread, (LPVOID)client, 0, NULL);
}
}
return 0;
}
4、课后作业
- 添加CWD/CDUP命令
- 实现MKD/RMD命令
- 添加用户权限管理
- 实现断点续传
5、扩展阅读
- FTP协议RFC 959
- FTPS和SFTP
- 文件传输安全