• 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("
    ");       //找一条完整的命令的结束符
     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解析时被设置。

  • 相关阅读:
    JAVA安装
    capture格式布局
    CSS样式表
    进制的转换
    CentOs7设置主机名称,以及主机名称和ip的对应关系
    CentOS7中NAT网卡设置静态IP
    CentOs7安装配置JDK
    基于Go语言构建区块链:part5
    基于Go语言构建区块链:part4
    BoltDB使用笔记
  • 原文地址:https://www.cnblogs.com/kanite/p/5153637.html
Copyright © 2020-2023  润新知