VC FTP服务器程序分析(三)

  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 }
View Code

  首先从命令行语句里拆分出命令关键字和参数。这里面的命令要是一个个列出来太长了。主要是要弄清楚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解析时被设置。

你可能感兴趣的:(VC FTP服务器程序分析(三))