CControlSocket类的分析,CControlSocket类的内容比较多,为什么呢。因为通信控制命令的传输全部在这里,通信协议的多样也导致了协议解析的多样。
1、OnReceive 其大致说明:本函数由框架调用,通知套接字缓冲中有数据,可以调用Receive函数取出。
1 void CControlSocket::OnReceive(int nErrorCode) 2 { 3 try 4 { 5 TCHAR buff[BUFFER_SIZE+1]; 6 7 int nRead = Receive(buff, BUFFER_SIZE); 8 switch (nRead) 9 { 10 case 0: 11 Close(); 12 break; 13 14 case SOCKET_ERROR: 15 if (GetLastError() != WSAEWOULDBLOCK) 16 { 17 TCHAR szError[256]; 18 wsprintf(szError, "OnReceive error: %d", GetLastError()); 19 AfxMessageBox (szError); 20 } 21 break; 22 23 default: 24 if ((m_RxBuffer.GetLength() + nRead) > BUFFER_OVERFLOW) 25 { 26 ((CClientThread *)m_pThread)->PostStatusMessage("Buffer overflow: DOS attack?"); 27 28 // buffer overflow (DOS attack ?) 29 AfxGetThread()->PostThreadMessage(WM_QUIT,0,0); 30 } 31 else 32 if (nRead != SOCKET_ERROR && nRead != 0) 33 { 34 // terminate the string 35 buff[nRead] = 0; 36 m_RxBuffer += CString(buff); 37 38 GetCommandLine(); 39 } 40 break; 41 } 42 } 43 catch(...) 44 { 45 // something bad happened... (DOS attack?) 46 ((CClientThread *)m_pThread)->PostStatusMessage("Exception occurred in CSocket::OnReceive()!"); 47 // close the connection. 48 AfxGetThread()->PostThreadMessage(WM_QUIT,0,0); 49 } 50 CSocket::OnReceive(nErrorCode); 51 }
这个函数就是读控制命令套接字及相应的错误处理。接收到的数据,保存在了m_RxBuffer中。接下来GetCommandLine函数解析接收到的数据。
2、GetCommandLine函数 解析命令数据
1 void CControlSocket::GetCommandLine() 2 { 3 CString strTemp; 4 int nIndex; 5 6 while(!m_RxBuffer.IsEmpty()) //有接收到的数据待处理 7 { 8 nIndex = m_RxBuffer.Find("\r\n"); //找一条完整的命令的结束符 9 if (nIndex != -1) 10 { 11 strTemp = m_RxBuffer.Left(nIndex); //将这条命令提取出来 12 m_RxBuffer = m_RxBuffer.Mid(nIndex + 2); //更新m_RxBuffer 去掉已经提取出来的命令 13 if (!strTemp.IsEmpty()) 14 { 15 m_strCommands.AddTail(strTemp); //可能while循环中提取出多条命令,这里增加一个队列 16 // parse and execute command 17 ProcessCommand(); //去处理这些命令,如果直接处理命令的话,就没有上面m_strCommandsz这个队列缓冲了 18 } 19 } 20 else 21 break; 22 } 23 }
该有的解释已经在上面了。CString::Mid的函数与我记忆中的可能有些差别,特意查询下。CString CString ::Mid(int nFirst) const
返回值:返回一个包含指定范围字符的拷贝的CString对象。nFirst 此CString对象中的要被提取的子串的第一个字符的从零开始的索引。
4、ProcessCmd 解释这些命令,这个函数比较长,值得学习的也有很多。
1 void CControlSocket::ProcessCommand() 2 { 3 CString strCommand, strArgs; 4 5 // get command 6 CString strBuff = m_strCommands.RemoveHead(); //得到第一条待解析的命令 7 int nIndex = strBuff.Find(" "); //查找空格 8 if (nIndex == -1) 9 { 10 strCommand = strBuff; 11 } 12 else 13 { 14 strCommand = strBuff.Left(nIndex); //这几行代码就是去掉命令语句里开始的空格(如果有) 15 strArgs= strBuff.Mid(nIndex+1); //并将命令关键字和参数分离开来 16 } 17 strCommand.MakeUpper(); //命令关键字全部转大写 18 19 // log command 20 ((CClientThread *)m_pThread)->PostStatusMessage(strCommand + " " + strArgs); //在界面上打印收到的命令及参数 21 22 if ((m_nStatus == STATUS_LOGIN) && (strCommand != "USER" && strCommand != "PASS")) //这句话后面应该有错 23 { //应该是strArgs != "PASS" 24 SendResponse("530 Please login with USER and PASS."); 25 return; 26 } 27 28 // specify username 29 if (strCommand == "USER") //是USER命令 30 { 31 // only accept anonymous account 32 if (strArgs.CompareNoCase(AfxGetApp()->GetProfileString("Settings", "userName", "sunrise")) == 0) 33 { 34 SendResponse("331 User name ok, need password."); 35 m_strUserName = strArgs; //保存登陆上来的用户名 36 } 37 else 38 SendResponse("530 Not logged in. No such account."); 39 } 40 else 41 // specify password 42 if (strCommand == "PASS") //是PASS命令 43 { 44 if (m_strUserName.IsEmpty()) //要先USER命令 45 { 46 SendResponse("503 Login with USER first."); 47 return; 48 } 49 50 //密码是meng 51 if (strArgs.CompareNoCase(AfxGetApp()->GetProfileString("Settings", "password", "meng")) != 0) 52 { 53 SendResponse("503 password is wrong."); 54 return; 55 } 56 // login client 57 m_strHomeDir = ((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_strHomeDirectory; //设置主目录 58 m_strCurrentDir = m_strHomeDir; //设置当前目录 59 60 SendResponse("230 User logged in."); //回复登陆成功 61 m_nStatus = STATUS_IDLE; //修改状态 62 } 63 else 64 // close connection 65 if (strCommand == "QUIT") //QUIT命令 66 { 67 // send goodbye message to client 68 SendResponse("220 Goodbye."); 69 70 Close(); 71 72 // tell our thread we have been closed 73 AfxGetThread()->PostThreadMessage(WM_QUIT,0,0); 74 } 75 else 76 // change transfer type 77 if (strCommand == "TYPE") 78 { 79 SendResponse("200 Type set to %s.", strArgs); 80 } 81 else 82 // print current directory 83 if ((strCommand == "PWD") || (strCommand == "XPWD")) 84 { 85 CString strRelativePath; 86 GetRelativePath(m_strCurrentDir, strRelativePath); //获取当前目录 87 SendResponse("257 \"%s\" is current directory.", strRelativePath); 88 } 89 else 90 // change working directory 91 if (strCommand == "CWD") 92 { 93 DoChangeDirectory(strArgs); //更改路径 94 } 95 else 96 // change to parent directory 97 if (strCommand == "CDUP") 98 { 99 DoChangeDirectory(".."); 100 } 101 else 102 // specify IP and port (PORT a1,a2,a3,a4,p1,p2) -> IP address a1.a2.a3.a4, port p1*256+p2. 103 if (strCommand == "PORT") //在这里指定端口 104 { 105 CString strSub; 106 int nCount=0; 107 108 while (AfxExtractSubString(strSub, strArgs, nCount++, ',')) //这里又提取了参数,这个命令是在干嘛 109 { 110 switch(nCount) 111 { 112 case 1: // a1 113 m_strRemoteHost = strSub; 114 m_strRemoteHost += "."; 115 break; 116 case 2: // a2 117 m_strRemoteHost += strSub; 118 m_strRemoteHost += "."; 119 break; 120 case 3: // a3 121 m_strRemoteHost += strSub; 122 m_strRemoteHost += "."; 123 break; 124 case 4: // a4 125 m_strRemoteHost += strSub; 126 break; 127 case 5: // p1 128 m_nRemotePort = 256*atoi(strSub); 129 break; 130 case 6: // p2 131 m_nRemotePort += atoi(strSub); 132 break; 133 } 134 } 135 SendResponse("200 Port command successful."); 136 } 137 else 138 // list current directory (or a specified file/directory) 139 if ((strCommand == "LIST") || 140 (strCommand == "NLST")) 141 { 142 StripParameters(strArgs); 143 144 CString strResult; 145 if (!GetDirectoryList(strArgs, strResult)) 146 { 147 return; 148 } 149 150 SendResponse("150 Opening ASCII mode data connection for directory list."); 151 152 // create data connection with client 153 if (CreateDataConnection()) //在这里就创建了数据套接字连接了 154 { 155 if (strResult.IsEmpty()) 156 { 157 // close data connection with client 158 DestroyDataConnection(); 159 160 SendResponse("226 Transfer complete."); 161 m_nStatus = STATUS_IDLE; 162 return; 163 } 164 } 165 else 166 { 167 // close data connection with client 168 DestroyDataConnection(); //不成功则销毁 169 return; 170 } 171 172 m_nStatus = STATUS_LIST; // 173 174 m_pDataSocket->AsyncSelect(); 175 176 // send the listing 177 m_pDataSocket->SendData(strResult); 178 } 179 else 180 // retrieve file 181 if (strCommand == "RETR") 182 { 183 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowDownload) 184 { 185 DoRetrieveFile(strArgs); 186 } 187 else 188 { 189 SendResponse("550 Permission denied."); 190 } 191 } 192 else 193 // client wants to upload file 194 if (strCommand == "STOR") 195 { 196 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowUpload) 197 { 198 DoStoreFile(strArgs); 199 } 200 else 201 { 202 SendResponse("550 Permission denied."); 203 } 204 } 205 else 206 // delete file 207 if (strCommand == "DELE") 208 { 209 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowDelete) 210 { 211 DoDeleteFile(strArgs); 212 } 213 else 214 { 215 SendResponse("550 Permission denied."); 216 } 217 } 218 else 219 // remove directory 220 if ((strCommand == "RMD") || (strCommand == "XRMD")) 221 { 222 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowDelete) 223 { 224 DoDeleteDirectory(strArgs); 225 } 226 else 227 { 228 SendResponse("550 Permission denied."); 229 } 230 } 231 else 232 // create directory 233 if ((strCommand == "MKD") || (strCommand == "XMKD")) 234 { 235 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowCreateDirectory) 236 { 237 DoCreateDirectory(strArgs); 238 } 239 else 240 { 241 SendResponse("550 Permission denied."); 242 } 243 } 244 else 245 // rename file or directory (part 1) 246 if (strCommand == "RNFR") 247 { 248 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowRename) 249 { 250 DoRenameFrom(strArgs); 251 } 252 else 253 { 254 SendResponse("550 Permission denied."); 255 } 256 } 257 else 258 // rename file or directory (part 2) 259 if (strCommand == "RNTO") 260 { 261 if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowRename) 262 { 263 DoRenameTo(strArgs); 264 } 265 else 266 { 267 SendResponse("550 Permission denied."); 268 } 269 } 270 else 271 // restart transfer from 'x' 272 if (strCommand == "REST") 273 { 274 m_dwRestartOffset = atol(strArgs); 275 SendResponse("350 Restarting at %d.", m_dwRestartOffset); 276 } 277 else 278 // get file size 279 if (strCommand == "SIZE") 280 { 281 CString strLocalPath; 282 GetLocalPath(strArgs, strLocalPath); 283 284 // check if directory exists 285 if (!FileExists(strLocalPath, FALSE)) 286 { 287 SendResponse("550 File not found."); 288 return; 289 } 290 CFileStatus status; 291 CFile::GetStatus(strLocalPath, status); 292 SendResponse("213 %d", status.m_size); 293 } 294 else 295 // switch to passive mode 296 if (strCommand == "PASV") 297 { 298 // delete existing datasocket 299 DestroyDataConnection(); 300 301 // create new data socket 302 m_pDataSocket = new CDataSocket(this); 303 304 if (!m_pDataSocket->Create(gFixedDataPort)) 305 { 306 DestroyDataConnection(); 307 SendResponse("421 Failed to create socket."); 308 return; 309 } 310 // start listening 311 m_pDataSocket->Listen(); 312 m_pDataSocket->AsyncSelect(); 313 314 CString strIP, strTmp; 315 UINT nPort; 316 317 // get our ip address 318 GetSockName(strIP, nPort); 319 // retrieve port 320 m_pDataSocket->GetSockName(strTmp, nPort); 321 // replace dots 322 strIP.Replace(".", ","); 323 // tell the client which address/port to connect to 324 SendResponse("227 Entering Passive Mode (%s,%d,%d).", strIP, nPort/256, nPort%256); 325 m_bPassiveMode = TRUE; 326 } 327 else 328 // abort transfer 329 if ((strCommand == "ABOR") || (strCommand.Right(4) == "ABOR")) 330 { 331 if (m_pDataSocket) 332 { 333 if (m_nStatus != STATUS_IDLE) 334 { 335 SendResponse("426 Data connection closed."); 336 } 337 // destroy data connection 338 m_pThread->PostThreadMessage(WM_DESTROYDATACONNECTION, 0 ,0); 339 } 340 SendResponse("226 ABOR command successful."); 341 } 342 else 343 // get system info 344 if (strCommand == "SYST") 345 { 346 SendResponse("215 UNIX emulated by Baby FTP Server."); 347 } 348 else 349 // dummy instruction 350 if (strCommand == "NOOP") 351 { 352 SendResponse("200 NOOP command successful."); 353 } 354 else 355 { 356 SendResponse("502 Command not implemented."); 357 } 358 }
首先从命令行语句里拆分出命令关键字和参数。这里面的命令要是一个个列出来太长了。主要是要弄清楚FTP通信的流程,也就是这些命令的内在逻辑,等到具体开发某个功能的时候,再一一弄清楚就可以了。
5、m_pDataSocket是CControlSocket中的成员变量,指向的是数据传输socket。在什么地方创建了CDataSocket呢,在LIST/NLIST命令中调用了 CreateDataConnection函数,这里创建了CDataSocket。然后在PASV命令里也创建了CDataSocket。这其实就是文件数据传输时的两种模式,前一种作为tcp客户端,后一种作为tcp服务器。下面是CreateDataConnect函数:
1 BOOL CControlSocket::CreateDataConnection() 2 { 3 if (!m_bPassiveMode) 4 { 5 m_pDataSocket = new CDataSocket(this); 6 if (m_pDataSocket->Create()) 7 { 8 m_pDataSocket->AsyncSelect(FD_CONNECT | FD_CLOSE | FD_ACCEPT); 9 // connect to remote site 10 if (m_pDataSocket->Connect(m_strRemoteHost, m_nRemotePort) == 0) 11 { 12 if (GetLastError() != WSAEWOULDBLOCK) 13 { 14 SendResponse("425 Can't open data connection."); 15 return FALSE; 16 } 17 } 18 } 19 else 20 { 21 SendResponse("421 Failed to create data connection socket."); 22 return FALSE; 23 } 24 } 25 26 // wait until we're connected 27 DWORD dwTickCount = GetTickCount() + 5000; 28 while (!m_pDataSocket->m_bConnected) 29 { 30 DoEvents(); 31 if (dwTickCount < GetTickCount()) 32 { 33 SendResponse("421 Failed to create data connection socket."); 34 return FALSE; 35 } 36 } 37 return TRUE; 38 }
数据传输socket已tcp客户端的形式连接服务器,注意连接的端口和服务器ip。这两个参数都在参数PORT解析时被设置。