FTP服务器与客户端设计与开发

andylance

贡献于2013-02-17

字数:25279 关键词: FTP服务器

 FTP服务器与客户端设计与开发 详细设计 程序包括5个主要功能: 1. 服务器的运行:启动和停止FTP服务 2. 用户管理:添加用户,删除用户和设置用户权限 3. 服务器配置:设置服务器开放端口,最大连接数等 4. 运行统计:统计当前服务器运行时期上传下载的流量等等 5. 安全设置:允许连接服务器的IP列表,以及禁止访问的IP 服务器的运行模块 功能:负责FTP服务器的运行。 使用类:CFTPServer类,CApplicationDlg类,CListenSocket类,CConnectThread类,CConnectSocket类 各种类的功能: 1. CFTPServer类:是CWnd的子类,作为程序的顶层类,负责实现或者调用各个成员函数 2. CApplicationDlg类:CDialog类的子类,实现程序主窗口。 3. CListenSocket类:负责监听FTP客户端连接,并实现有效连接 4. CConnectThread类:负责实现并保证多个连接的有效性。 5. CConnectSocket类:实现FTP命令的解析,数据的发送和接收 CFTPServer类 作为服务器的顶层类,实现服务器开始运行时的所有成员函数 申明如下: class CFTPServer : public CWnd { friend CConnectSocket;//CConnectSocket作为其友元类,可以访问内部私有数据成员 public: void SetGoodbyeMessage(LPCTSTR lpszText);//发送退出信息 void SetWelcomeMessage(LPCTSTR lpszText);//发送欢迎信息 void SetTimeout(int nValue);//设置暂停时间 void SetPort(int nValue);//设置端口 void SetMaxUsers(int nValue);//设置最大连接数 void SetStatisticsInterval(int nValue);//统计时间间隔 BOOL IsActive();//是否有效 void Stop(); BOOL Start(); CFTPServer(); virtual ~CFTPServer(); CUserManager m_UserManager;//用户管理对象 CSecurityManager m_SecurityManager;//安全策略 CFTPServer类 最主要的成员函数是start()和stop(),分别负责ftp服务器的开始运行和结束运行 函数声明如下: /********************************************************************/ /* */ /* Function name : Start */ /* Description : Start listining on port 21 and accept new */ /* connections. */ /* */ /********************************************************************/ BOOL CFTPServer::Start() { if (m_bRunning) return FALSE;//如果运行,返回错误标志 // create dummy window for message routing if (!CWnd::CreateEx(0, AfxRegisterWndClass(0), "FTP Server Notification Sink", WS_POPUP, 0,0,0,0, NULL, 0)) { AddTraceLine(0, "Failed to create notification window."); return FALSE; } // 开始创建socket if (m_ListenSocket.Create(m_nPort)) { // start listening if (m_ListenSocket.Listen()) { m_ListenSocket.m_pWndServer = this; m_bRunning = TRUE; SetTimer(1, m_nStatisticsInterval, NULL); AddTraceLine(0, "FTP Server started on port %d.", m_nPort); return TRUE; } } AddTraceLine(0, "FTP Server failed to listen on port %d.", m_nPort); // destroy notification window if (IsWindow(m_hWnd)) DestroyWindow(); m_hWnd = NULL; return FALSE; } /********************************************************************/ /* */ /* Function name : Stop */ /* Description : Stop FTP server. */ /* */ /********************************************************************/ void CFTPServer::Stop() { if (!m_bRunning) return; // stop statistics timer KillTimer(1); m_bRunning = FALSE; m_ListenSocket.Close(); CConnectThread* pThread = NULL; // close all running threads do { m_CriticalSection.Lock(); POSITION pos = m_ThreadList.GetHeadPosition(); if (pos != NULL) { pThread = (CConnectThread *)m_ThreadList.GetAt(pos); m_CriticalSection.Unlock(); // save thread members int nThreadID = pThread->m_nThreadID; HANDLE hThread = pThread->m_hThread; AddTraceLine(0, "[%d] Shutting down thread...", nThreadID); // tell thread to stop pThread->SetThreadPriority(THREAD_PRIORITY_HIGHEST); pThread->PostThreadMessage(WM_QUIT,0,0); // wait for thread to end, while keeping the messages pumping (max 5 seconds) if (WaitWithMessageLoop(hThread, 5000) == FALSE) { // thread doesn't want to stopped AddTraceLine(0, "[%d] Problem while killing thread.", nThreadID); // don't try again, so remove m_CriticalSection.Lock(); POSITION rmPos = m_ThreadList.Find(pThread); if (rmPos != NULL) m_ThreadList.RemoveAt(rmPos); m_CriticalSection.Unlock(); } else { AddTraceLine(0, "[%d] Thread successfully stopped.", nThreadID); } } else { m_CriticalSection.Unlock(); pThread = NULL; } } while (pThread != NULL); AddTraceLine(0, "FTP Server stopped."); if (IsWindow(m_hWnd)) DestroyWindow(); m_hWnd = NULL; } CListenSocket类 用于监听每个客户的连接,CListenSocket类是CAsyncSocket的子类,其成员函数listen监听来自客户端的连接,当监听到可以接收的socket的时候通过OnAccept函数准备创建有效连接的进程。 函数如下: void CListenSocket::OnAccept(int nErrorCode) { // New connection is being established CSocket sockit; // Accept the connection using a temp CSocket object. Accept(sockit); // Create a thread to handle the connection. The thread is created suspended so that we can // set variables in CConnectThread before it starts executing. CConnectThread* pThread = (CConnectThread*)AfxBeginThread(RUNTIME_CLASS(CConnectThread), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); if (!pThread) { sockit.Close(); TRACE("Could not create thread\n"); return; } CFTPServer *pWnd = (CFTPServer *)m_pWndServer; // since everything is successful, add the thread to our list pWnd->m_CriticalSection.Lock(); pWnd->m_ThreadList.AddTail(pThread); pWnd->m_CriticalSection.Unlock(); // save pointer pThread->m_pWndServer = m_pWndServer; // Pass the socket to the thread by passing the socket handle. You cannot pass // a CSocket object across threads. pThread->m_hSocket = sockit.Detach(); // Now start the thread. pThread->ResumeThread(); } CConnectThread类 CConnectThread类负责为每个有效进程创建一个线程,每个进程完成数据传输的所有任务,穿件县城后通过InitInstance完成线程的初始化 BOOL CConnectThread::InitInstance() { try { // Attach the socket handle to a CSocket object. // This makes sure that the socket notifications are sent to this thread. m_ConnectSocket.Attach(m_hSocket); m_ConnectSocket.m_pThread = this; CString strIPAddress; UINT nPort; m_ConnectSocket.GetPeerName(strIPAddress, nPort); // notify server that there's a new connection m_pWndServer->SendMessage(WM_THREADSTART, (WPARAM)this, 0); if (((CFTPServer *)m_pWndServer)->CheckMaxUsers()) { m_ConnectSocket.SendResponse("421 Too many users are connected, please try again later."); PostThreadMessage(WM_QUIT,0,0); } else if (!((CFTPServer *)m_pWndServer)->IsIPAddressAllowed(strIPAddress)) { m_ConnectSocket.SendResponse("421 Access denied, IP address was rejected by the server."); PostThreadMessage(WM_QUIT,0,0); } else { // send welcome message to client CString strText = ((CFTPServer *)m_pWndServer)->GetWelcomeMessage(); m_ConnectSocket.SendResponse("220 " + strText); m_nTimerID = ::SetTimer(NULL, 0, 1000, TimerProc); } } catch(CException *e) { e->Delete(); } return TRUE; } 线程结束以后,通过ExitInstance函数实现资源的释放代码如下: int CConnectThread::ExitInstance() { CFTPServer *pWnd = (CFTPServer *)m_pWndServer; try { pWnd->m_CriticalSection.Lock(); // delete this thread from the linked list POSITION pos = pWnd->m_ThreadList.Find(this); if(pos != NULL) { pWnd->m_ThreadList.RemoveAt(pos); } pWnd->m_CriticalSection.Unlock(); // notify service main loop pWnd->SendMessage(WM_THREADCLOSE, (WPARAM)this, 0); } catch(CException *e) { pWnd->m_CriticalSection.Unlock(); e->Delete(); } return CWinThread::ExitInstance(); } 为了了解传输过程中接收和发送的字节数,使用IncReceivedBytes和IncSentBytes来计算。这两个函数在CConnectSocket类中调用,代码如下: void CConnectThread::IncSentBytes(int nBytes) { m_LastDataTransferTime = CTime::GetCurrentTime(); m_nSentBytes += nBytes; // notify server class m_pWndServer->PostMessage(WM_THREADMSG, (WPARAM)0, (LPARAM)nBytes); } void CConnectThread::IncReceivedBytes(int nBytes) { m_LastDataTransferTime = CTime::GetCurrentTime(); m_nReceivedBytes += nBytes; // notify server class m_pWndServer->PostMessage(WM_THREADMSG, (WPARAM)1, (LPARAM)nBytes); } CConnectSocket类 每个线程都是通过一个CConnectSocket对象m_ConnectSocket来完成数据的接受和发送。当线程创建成功以后,m_ConnectSocket对象通过OnReceive函数获得数据,然后利用ParseCommand函数来解析其中FTP命令 void CConnectSocket::OnReceive(int nErrorCode) { TCHAR buff[BUFFERSIZE]; int nRead = Receive(buff, BUFFERSIZE); switch (nRead) { case 0: Close(); break; case SOCKET_ERROR: if (GetLastError() != WSAEWOULDBLOCK) { TCHAR szError[256]; wsprintf(szError, "OnReceive error: %d", GetLastError()); AfxMessageBox (szError); } break; default: if (nRead != SOCKET_ERROR && nRead != 0) { ((CConnectThread *)AfxGetThread())->IncReceivedBytes(nRead); // terminate the string buff[nRead] = 0; m_RxBuffer += CString(buff); GetRxLine(); } break; } CSocket::OnReceive(nErrorCode); } ParseCommand函数 是当前程序最重要的一个部分,它根据客户端提交的各种命令进行相应的操作代码如下 void CConnectSocket::ParseCommand() { static CFTPCommand commandList[] = { {TOK_USER, "USER", TRUE}, {TOK_PASS, "PASS", TRUE}, {TOK_CWD, "CWD", TRUE}, {TOK_PWD, "PWD", FALSE}, {TOK_PORT, "PORT", TRUE}, {TOK_PASV, "PASV", FALSE}, {TOK_TYPE, "TYPE", TRUE}, {TOK_LIST, "LIST", FALSE}, {TOK_REST, "REST", TRUE}, {TOK_CDUP, "CDUP", FALSE}, {TOK_RETR, "RETR", TRUE}, {TOK_STOR, "STOR", TRUE}, {TOK_SIZE, "SIZE", TRUE}, {TOK_DELE, "DELE", TRUE}, {TOK_RMD, "RMD", TRUE}, {TOK_MKD, "MKD", TRUE}, {TOK_RNFR, "RNFR", TRUE}, {TOK_RNTO, "RNTO", TRUE}, {TOK_ABOR, "ABOR", FALSE}, {TOK_SYST, "SYST", FALSE}, {TOK_NOOP, "NOOP", FALSE}, {TOK_BYE, "BYE", FALSE}, {TOK_QUIT, "QUIT", FALSE}, {TOK_ERROR, "", FALSE}, }; // parse command CString strCommand, strArguments; if (!GetRxCommand(strCommand, strArguments)) { return; } int nCommand; //查找命令 for (nCommand = TOK_USER; nCommand < TOK_ERROR; nCommand++) { // found command ? if (strCommand == commandList[nCommand].m_pszName) { // did we expect an argument ? if (commandList[nCommand].m_bHasArguments && (strArguments == "")) { SendResponse("501 Syntax error"); return; } break; } } if (nCommand == TOK_ERROR) { // command is not in our list SendResponse("500 Syntax error, command unrecognized."); return; } // no commands are excepted before successfull logged on if (nCommand > TOK_PASS && !m_bLoggedon) { SendResponse("530 Please log in with USER and PASS first."); return; } // proces command switch(nCommand) { // specify username case TOK_USER: { strArguments.MakeLower(); m_bLoggedon = FALSE; m_strUserName = strArguments; CString strPeerAddress; UINT nPeerPort; GetPeerName(strPeerAddress, nPeerPort); // tell FTP server a new user has connected CConnectThread *pThread = (CConnectThread *)m_pThread; ((CFTPServer *)pThread->m_pWndServer)->m_pEventSink->OnFTPUserConnected(m_pThread->m_nThreadID, m_strUserName, strPeerAddress); SendResponse("331 Password required for " + strArguments); } break; // specify password case TOK_PASS: { // already logged on ? if (m_bLoggedon) { SendResponse("503 Bad sequence of commands."); } else { // check user and password CUser user; if (theServer.m_UserManager.CheckUser(m_strUserName, strArguments, user)) { //设置用户主目录 m_strCurrentDir = "/"; // 成功登录提示 m_bLoggedon = TRUE; SendResponse("230 Logged on"); } else SendResponse("530 Login or password incorrect!"); } } break; // change current directory case TOK_CWD: { int nResult = theServer.m_UserManager.ChangeDirectory(m_strUserName, m_strCurrentDir, strArguments); CString str; switch(nResult) { case 0: str.Format("250 CWD successful. \"%s\" is current directory.", m_strCurrentDir); SendResponse(str); break; case 1: str.Format("550 CWD failed. \"%s\": Permission denied.", strArguments); SendResponse(str); break; default: str.Format("550 CWD failed. \"%s\": directory not found.", strArguments); SendResponse(str); break; } } break; // print current directory case TOK_PWD: { CString str; str.Format("257 \"%s\" is current directory.", m_strCurrentDir); SendResponse(str); } break; // specify IP and port (PORT a1,a2,a3,a4,p1,p2) -> IP address a1.a2.a3.a4, port p1*256+p2. case TOK_PORT: { CString strSub; int nCount=0; while (AfxExtractSubString(strSub, strArguments, nCount++, ',')) { switch(nCount) { case 1: // a1 m_TransferStatus.m_strRemoteHost = strSub; m_TransferStatus.m_strRemoteHost += "."; break; case 2: // a2 m_TransferStatus.m_strRemoteHost += strSub; m_TransferStatus.m_strRemoteHost += "."; break; case 3: // a3 m_TransferStatus.m_strRemoteHost += strSub; m_TransferStatus.m_strRemoteHost += "."; break; case 4: // a4 m_TransferStatus.m_strRemoteHost += strSub; break; case 5: // p1 m_TransferStatus.m_nRemotePort = 256*atoi(strSub); break; case 6: // p2 m_TransferStatus.m_nRemotePort += atoi(strSub); break; } } m_TransferStatus.m_bPassiveMode = FALSE; SendResponse("200 Port command successful"); break; } // switch to passive mode case TOK_PASV: { // delete existing datasocket DestroyDataSocket(); // create new data socket m_TransferStatus.m_pDataSocket = new CDataSocket(this, -1); if (!m_TransferStatus.m_pDataSocket->Create()) { DestroyDataSocket(); SendResponse("421 Can't create socket"); break; } // start listening m_TransferStatus.m_pDataSocket->Listen(); m_TransferStatus.m_pDataSocket->AsyncSelect(); CString strIP, strTmp; UINT nPort; // get our ip address GetSockName(strIP, nPort); // Now retrieve the port m_TransferStatus.m_pDataSocket->GetSockName(strTmp, nPort); // Reformat the ip strIP.Replace(".",","); // tell the client which address/port to connect to CString str; str.Format("227 Entering Passive Mode (%s,%d,%d)", strIP, nPort/256, nPort%256); SendResponse(str); m_TransferStatus.m_bPassiveMode = TRUE; break; } case TOK_TYPE: { SendResponse("200 Type set to " + strArguments); } break; // list current directory case TOK_LIST: { if(!m_TransferStatus.m_bPassiveMode && (m_TransferStatus.m_strRemoteHost == "" || m_TransferStatus.m_nRemotePort == -1)) { SendResponse("503 Bad sequence of commands."); } else { // if client did not specify a directory use current dir if (strArguments == "") { strArguments = m_strCurrentDir; } else { // check if argument is file or directory CString strResult; int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_LIST, strResult); if (nResult == 0) { strArguments = strResult; } } CString strListing; int nResult = theServer.m_UserManager.GetDirectoryList(m_strUserName, strArguments, strListing); switch(nResult) { case 1: SendResponse("550 Permission denied"); break; case 2: SendResponse("550 Directory not found"); break; default: if (!m_TransferStatus.m_bPassiveMode) { CDataSocket *pDataSocket = new CDataSocket(this, 0); pDataSocket->Create(); pDataSocket->SetData(strListing); pDataSocket->AsyncSelect(); m_TransferStatus.m_pDataSocket = pDataSocket; if (!pDataSocket->Connect(m_TransferStatus.m_strRemoteHost, m_TransferStatus.m_nRemotePort)) { if (GetLastError() != WSAEWOULDBLOCK) { SendResponse("425 Can't open data connection"); break; } } SendResponse("150 Opening data channel for directory list."); } else { m_TransferStatus.m_pDataSocket->SetData(strListing); m_TransferStatus.m_pDataSocket->SetTransferType(0); } break; } } break; } // change to parent directory case TOK_CDUP: { CString strDirectory = ".."; CString str; int nResult = theServer.m_UserManager.ChangeDirectory(m_strUserName, m_strCurrentDir, strDirectory); switch(nResult) { case 0: str.Format("250 CWD successful. \"%s\" is current directory.", m_strCurrentDir); SendResponse(str); break; case 1: str.Format("550 CWD failed. \"%s\": Permission denied.", strDirectory); SendResponse(str); break; case 2: str.Format("550 CWD failed. \"%s\": directory not found.", strDirectory); SendResponse(str); break; } } break; // retrieve file case TOK_RETR: { if(!m_TransferStatus.m_bPassiveMode && (m_TransferStatus.m_strRemoteHost == "" || m_TransferStatus.m_nRemotePort == -1)) { SendResponse("503 Bad sequence of commands."); break; } CString strResult; int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_DOWNLOAD, strResult); switch(nResult) { case 1: SendResponse("550 Permission denied"); break; case 2: SendResponse("550 File not found"); break; default: if (!m_TransferStatus.m_bPassiveMode) { CDataSocket *pDataSocket = new CDataSocket(this, 1); m_TransferStatus.m_pDataSocket = pDataSocket; pDataSocket->Create(); pDataSocket->AsyncSelect(); pDataSocket->SetData(strResult); if (pDataSocket->Connect(m_TransferStatus.m_strRemoteHost ,m_TransferStatus.m_nRemotePort) == 0) { if (GetLastError() != WSAEWOULDBLOCK) { SendResponse("425 Can't open data connection"); break; } } SendResponse("150 Opening data channel for file transfer."); } else { m_TransferStatus.m_pDataSocket->SetData(strResult); m_TransferStatus.m_pDataSocket->SetTransferType(1); } break; } break; } // client wants to upload file case TOK_STOR: { if(m_TransferStatus.m_bPassiveMode == -1) { SendResponse("503 Bad sequence of commands."); break; } if(!m_TransferStatus.m_bPassiveMode && (m_TransferStatus.m_strRemoteHost == "" || m_TransferStatus.m_nRemotePort == -1)) { SendResponse("503 Bad sequence of commands."); break; } CString strResult; int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_UPLOAD, strResult); switch(nResult) { case 1: SendResponse("550 Permission denied"); break; case 2: SendResponse("550 Filename invalid"); break; default: if (!m_TransferStatus.m_bPassiveMode) { CDataSocket *pDataSocket = new CDataSocket(this, 2); m_TransferStatus.m_pDataSocket = pDataSocket; pDataSocket->Create(); pDataSocket->AsyncSelect(); pDataSocket->SetData(strResult); if (pDataSocket->Connect(m_TransferStatus.m_strRemoteHost, m_TransferStatus.m_nRemotePort) == 0) { if (GetLastError() != WSAEWOULDBLOCK) { SendResponse("425 Can't open data connection"); break; } } SendResponse("150 Opening data channel for file transfer."); } else { m_TransferStatus.m_pDataSocket->SetData(strResult); m_TransferStatus.m_pDataSocket->SetTransferType(2); } break; } } break; // get file size case TOK_SIZE: { CString strResult; int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_DOWNLOAD, strResult); switch(nResult) { case 1: SendResponse("550 Permission denied"); break; case 2: SendResponse("550 File not found"); break; default: { CFileStatus status; CFile::GetStatus(strResult, status); CString strResponse; strResponse.Format("213 %d", status.m_size); SendResponse(strResponse); break; } } } break; // delete file case TOK_DELE: { CString strResult; int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_DELETE, strResult); switch(nResult) { case 1: SendResponse("550 Permission denied"); break; case 2: SendResponse("550 File not found"); break; default: try { CFile::Remove(strResult); } catch(CFileException *e) { e->Delete(); SendResponse("450 Internal error deleting the file."); break; } SendResponse("250 File deleted successfully"); break; } } break; // remove directory case TOK_RMD: { CString strResult; int nResult = theServer.m_UserManager.GetDirectory(m_strUserName, strArguments, m_strCurrentDir, FTP_DELETE, strResult); switch(nResult) { case 1: SendResponse("550 Permission denied"); break; case 2: SendResponse("550 Directory not found"); break; default: if (!RemoveDirectory(strResult)) { if (GetLastError() == ERROR_DIR_NOT_EMPTY) SendResponse("550 Directory not empty."); else SendResponse("450 Internal error deleting the directory."); } else { SendResponse("250 Directory deleted successfully"); } break; } } break; // create directory case TOK_MKD: { CString strResult; int nResult = theServer.m_UserManager.GetDirectory(m_strUserName, strArguments, m_strCurrentDir, FTP_CREATE_DIR, strResult); switch(nResult) { case 0: SendResponse("550 Directory already exists"); break; case 1: SendResponse("550 Can't create directory. Permission denied"); break; case 3: SendResponse("550 Directoryname not valid"); break; default: strResult += "\\"; CString strDir; BOOL bResult; // create directory structure one part at a time while (strResult != "") { strDir += strResult.Left(strResult.Find("\\")+1); strResult = strResult.Mid(strResult.Find("\\")+1); bResult = CreateDirectory(strDir, 0); } if (!bResult) { SendResponse("450 Internal error creating the directory."); } else SendResponse("250 Directory created successfully"); break; } } break; // rename file or directory (part 1) case TOK_RNFR: { CString strResult; int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_RENAME, strResult); if (nResult == 0) { m_strRenameFile = strResult; m_bRenameFile = TRUE; SendResponse("350 File exists, ready for destination name."); break; } else { // client wants to rename directory nResult = theServer.m_UserManager.GetDirectory(m_strUserName, strArguments, m_strCurrentDir, FTP_RENAME, strResult); switch(nResult) { case 0: m_strRenameFile = strResult; m_bRenameFile = FALSE; SendResponse("350 Directory exists, ready for destination name."); break; case 1: SendResponse("550 Permission denied"); break; default: SendResponse("550 file/directory not found"); break; } } } break; // rename file or directory (part 2) case TOK_RNTO: { if (m_strRenameFile.IsEmpty()) { SendResponse("503 Bad sequence of commands!"); break; } if (m_bRenameFile) { CString strResult; // check destination filename int nResult = theServer.m_UserManager.GetFileName(m_strUserName, strArguments, m_strCurrentDir, FTP_RENAME, strResult); switch(nResult) { case 0: SendResponse("550 file exists"); break; case 1: SendResponse("550 Permission denied"); break; default: if (!MoveFile(m_strRenameFile, strResult)) SendResponse("450 Internal error renaming the file"); else SendResponse("250 file renamed successfully"); break; } } else { CString strResult; // check destination directory name int nResult = theServer.m_UserManager.GetDirectory(m_strUserName, strArguments, m_strCurrentDir, FTP_RENAME, strResult); switch(nResult) { case 0: SendResponse("550 directory exists"); break; case 1: SendResponse("550 Permission denied"); break; case 3: SendResponse("550 Filename invalid"); break; default: if (!MoveFile(m_strRenameFile, strResult)) SendResponse("450 Internal error renaming the file"); else SendResponse("250 file renamed successfully"); break; } } } break; // abort transfer case TOK_ABOR: { if (m_TransferStatus.m_pDataSocket) { if (m_TransferStatus.m_pDataSocket->GetStatus() != XFERMODE_IDLE) SendResponse("426 Connection closed; transfer aborted."); // destroy data socket m_pThread->PostThreadMessage(WM_THREADMSG, 0, 0); // DestroyDataSocket(); } SendResponse("226 ABOR command successful"); break; } // get system info case TOK_SYST: SendResponse("215 UNIX emulated by Pablo's FTP Server"); break; // close connection case TOK_BYE: case TOK_QUIT: { // send goodbye message to client CConnectThread *pThread = (CConnectThread *)m_pThread; CString strText = ((CFTPServer *)pThread->m_pWndServer)->GetGoodbyeMessage(); SendResponse("220 " + strText); Close(); // tell our thread we have been closed // destroy connection m_pThread->PostThreadMessage(WM_THREADMSG, 1, 0); break; } // dummy instruction case TOK_NOOP: SendResponse("200 OK"); break; default: SendResponse("502 Command not implemented."); break; } } 用户管理模块 用户管理模块需要为指定的用户设置相应的权限,通过CUser类,CuserManager类和CDirectory类和CUserAccountsDlg完成 CUser类: 保存用户名称,密码,目录列表和用户是否被禁止 CuserManager类: 实现用户管理所有功能 CDirectory类: 保存目录是否允许下载,上传,重命名,删除创建目录及是否为主目录, CUserAccountsDlg类: 实现用户设置的对话框 下面介绍CuserManager类的常用的成员函数 class CUser : public CObject { DECLARE_SERIAL(CUser) CUser(); virtual ~CUser(); public: virtual void Serialize(CArchive&); CUser (const CUser &user); // copy-constructor CUser &operator=(const CUser &user); // =-operator CString m_strName; CString m_strPassword; CArray m_DirectoryArray; BOOL m_bAccountDisabled; }; 安全设置模块 负责设置需要屏蔽的,允许连接的ip列表。该模块通过CSecurityManager类实现安全设置功能,CSecurityPage类实现设置界面,CSecurityManager类的成员函数如下 class CSecurityManager { public: BOOL IsIPAddressBlocked(LPCTSTR lpszIPAddress); BOOL IsIPAddressNonBlocked(LPCTSTR lpszIPAddress); void UpdateBlockedList(CStringArray &strArray); void UpdateNonBlockedList(CStringArray &strArray); void GetNonBlockedList(CStringArray &strArray); void GetBlockedList(CStringArray &strArray); CSecurityManager(); virtual ~CSecurityManager(); BOOL Serialize(BOOL bStoring); protected: CStringArray m_NonBlockedList; CStringArray m_BlockedList; CString m_strFilename; CCriticalSection m_CriticalSection; 客户端与服务器连接 FTP是建立在TCP之上的连接,端口号使用21。若客户端与服务器之间成功连接,获得服务器根目录的所有文件并在列表框中显示。 //连接ftp服务器 void CMyFtpDlg::OnConnect() { UpdateData(TRUE); //新建对话 m_pInetSession=new CInternetSession(AfxGetAppName(),1,PRE_CONFIG_INTERNET_ACCESS); try { //新建连接对象 m_pFtpConnection=m_pInetSession->GetFtpConnection(m_strServer,m_strUserName, m_strPassword); } catch(CInternetException *pEx) { //获取错误 TCHAR szError[1024]; if(pEx->GetErrorMessage(szError,1024)) AfxMessageBox(szError); else AfxMessageBox("There was an exception"); pEx->Delete(); m_pFtpConnection=NULL; return; } m_pRemoteFinder = new CFtpFileFind(m_pFtpConnection); //获得服务器根目录的所有文件并在列表框中显示 BrowseDir("",&m_ctrlRemoteFiles,m_pRemoteFinder,&m_arrRemoteFiles); } 5.2 获取目录与显示 在建立的列表框中,显示出远程计算机目录和本地计算机目录。如目录下仍然有子文件,则在点击“下一层”按钮后,列表框显示该文件的子文件,如该文件已经是文件,那么则不变。 //初始化两个列表框控件 SetListCtrlStyle(&m_ctrlLocalFiles); SetListCtrlStyle(&m_ctrlRemoteFiles); AddHeaders(&m_ctrlLocalFiles); AddHeaders(&m_ctrlRemoteFiles); //本地文件目录以c盘为根目录 BrowseDir("c:",&m_ctrlLocalFiles,&m_LocalFinder,&m_arrLocalFiles); return TRUE; // return TRUE unless you set the focus to a control } void CMyFtpDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CMyFtpDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle 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; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CMyFtpDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } //获得指定目录下的所有文件,并在列表框中显示 void CMyFtpDlg::BrowseDir(CString strDir,CListCtrl* pLstCtrl,CFileFind* pFinder,CArray* pFilesArr) { //清空列表框 pLstCtrl->DeleteAllItems(); pFilesArr->RemoveAll(); int nIndex = 0; CString szDir = strDir; if(szDir.Right(1) != "\\") szDir += "\\"; szDir += "*.*"; //查找目录下的文件 BOOL res = pFinder->FindFile(szDir); while(res) { res = pFinder->FindNextFile(); //是目录 if(pFinder->IsDirectory() && !pFinder->IsDots()) { FILEITEM fileitem; fileitem.nItem = nIndex; fileitem.bDir = TRUE; fileitem.strFileName = pFinder->GetFileName(); pFilesArr->Add(fileitem); AddItem(pLstCtrl,nIndex,TRUE,pFinder->GetFileName()); } //是文件 else if(!pFinder->IsDirectory() && !pFinder->IsDots()) { FILEITEM fileitem; fileitem.nItem = nIndex; fileitem.bDir = FALSE; fileitem.strFileName = pFinder->GetFileName(); pFilesArr->Add(fileitem); AddItem(pLstCtrl,nIndex,FALSE,pFinder->GetFileName()); } nIndex++; } } 5.3 文件的上传与下载 通过按钮控制文件的上传和下载,只能是文件,如果是文件夹,则提示不能下载。 //响应“download”按钮 void CMyFtpDlg::OnDownload() { //如果没有建立连接,退出 if(m_pFtpConnection==NULL) return; //下载所有选中文件 POSITION pos = m_ctrlRemoteFiles.GetFirstSelectedItemPosition(); if (pos == NULL) AfxMessageBox("请选择要下载的文件"); else { while (pos) { int nItem = m_ctrlRemoteFiles.GetNextSelectedItem(pos); DownFile(m_arrRemoteFiles.GetAt(nItem)); } } } //响应“Upload”按钮 void CMyFtpDlg::OnUpload() { //如果没有建立连接,退出 if(m_pFtpConnection==NULL) return; //上传所有选中文件 POSITION pos = m_ctrlLocalFiles.GetFirstSelectedItemPosition(); if (pos == NULL) AfxMessageBox("请选择要上传的文件"); else { while (pos) { int nItem = m_ctrlLocalFiles.GetNextSelectedItem(pos); UpFile(m_arrLocalFiles.GetAt(nItem)); } } } //下载单个文件 void CMyFtpDlg::DownFile(FILEITEM fileItem) { if(fileItem.bDir == TRUE) { AfxMessageBox("本程序暂时不支持下载整个文件夹,请选择文件下载"); } else { //格式化文件名 CString strLocalFile,strRemoteFile; strRemoteFile.Format("%s\\%s",m_pRemoteFinder->GetRoot(),fileItem.strFileName); strLocalFile.Format("%s\\%s",m_LocalFinder.GetRoot(),fileItem.strFileName); //下载 if(m_pFtpConnection->GetFile(strLocalFile,strLocalFile)) { CString strMsg; strMsg.Format("下载文件%s成功!",fileItem.strFileName); AfxMessageBox(strMsg); } } } //上传单个文件 void CMyFtpDlg::UpFile(FILEITEM fileItem) { if(fileItem.bDir == TRUE) { AfxMessageBox("本程序暂时不支持上载整个文件夹,请选择文件上载"); } else { //格式化文件名 CString strLocalFile,strRemoteFile; strRemoteFile.Format("%s\\%s",m_pRemoteFinder->GetRoot(),fileItem.strFileName); strLocalFile.Format("%s\\%s",m_LocalFinder.GetRoot(),fileItem.strFileName); //上传 if(m_pFtpConnection->PutFile(strLocalFile,strLocalFile)) { CString strMsg; strMsg.Format("上载文件%s成功!",fileItem.strFileName); AfxMessageBox(strMsg); } } }

下载文档,方便阅读与编辑

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享文档获得金币 ]
1 人已下载

下载文档

相关文档