CControlSocket类的分析,CControlSocket类的内容比较多,为什么呢。因为通信控制命令的传输全部在这里,通信协议的多样也导致了协议解析的多样。

  1、OnReceive  其大致说明:本函数由框架调用,通知套接字缓冲中有数据,可以调用Receive函数取出。

 void CControlSocket::OnReceive(int nErrorCode)
{
try
{
TCHAR buff[BUFFER_SIZE+]; int nRead = Receive(buff, BUFFER_SIZE);
switch (nRead)
{
case :
Close();
break; case SOCKET_ERROR:
if (GetLastError() != WSAEWOULDBLOCK)
{
TCHAR szError[];
wsprintf(szError, "OnReceive error: %d", GetLastError());
AfxMessageBox (szError);
}
break; default:
if ((m_RxBuffer.GetLength() + nRead) > BUFFER_OVERFLOW)
{
((CClientThread *)m_pThread)->PostStatusMessage("Buffer overflow: DOS attack?"); // buffer overflow (DOS attack ?)
AfxGetThread()->PostThreadMessage(WM_QUIT,,);
}
else
if (nRead != SOCKET_ERROR && nRead != )
{
// terminate the string
buff[nRead] = ;
m_RxBuffer += CString(buff); GetCommandLine();
}
break;
}
}
catch(...)
{
// something bad happened... (DOS attack?)
((CClientThread *)m_pThread)->PostStatusMessage("Exception occurred in CSocket::OnReceive()!");
// close the connection.
AfxGetThread()->PostThreadMessage(WM_QUIT,,);
}
CSocket::OnReceive(nErrorCode);
}

  这个函数就是读控制命令套接字及相应的错误处理。接收到的数据,保存在了m_RxBuffer中。接下来GetCommandLine函数解析接收到的数据。

  2、GetCommandLine函数 解析命令数据

 void CControlSocket::GetCommandLine()
{
CString strTemp;
int nIndex; while(!m_RxBuffer.IsEmpty()) //有接收到的数据待处理
{
nIndex = m_RxBuffer.Find("\r\n"); //找一条完整的命令的结束符
if (nIndex != -)
{
strTemp = m_RxBuffer.Left(nIndex); //将这条命令提取出来
m_RxBuffer = m_RxBuffer.Mid(nIndex + ); //更新m_RxBuffer 去掉已经提取出来的命令
if (!strTemp.IsEmpty())
{
m_strCommands.AddTail(strTemp); //可能while循环中提取出多条命令,这里增加一个队列
// parse and execute command
ProcessCommand(); //去处理这些命令,如果直接处理命令的话,就没有上面m_strCommandsz这个队列缓冲了
}
}
else
break;
}
}

  该有的解释已经在上面了。CString::Mid的函数与我记忆中的可能有些差别,特意查询下。CString CString ::Mid(int nFirst) const

  返回值:返回一个包含指定范围字符的拷贝的CString对象。nFirst 此CString对象中的要被提取的子串的第一个字符的从零开始的索引。

  4、ProcessCmd 解释这些命令,这个函数比较长,值得学习的也有很多。

 void CControlSocket::ProcessCommand()
{
CString strCommand, strArgs; // get command
CString strBuff = m_strCommands.RemoveHead(); //得到第一条待解析的命令
int nIndex = strBuff.Find(" "); //查找空格
if (nIndex == -)
{
strCommand = strBuff;
}
else
{
strCommand = strBuff.Left(nIndex); //这几行代码就是去掉命令语句里开始的空格(如果有)
strArgs= strBuff.Mid(nIndex+); //并将命令关键字和参数分离开来
}
strCommand.MakeUpper(); //命令关键字全部转大写 // log command
((CClientThread *)m_pThread)->PostStatusMessage(strCommand + " " + strArgs); //在界面上打印收到的命令及参数 if ((m_nStatus == STATUS_LOGIN) && (strCommand != "USER" && strCommand != "PASS")) //这句话后面应该有错
{ //应该是strArgs != "PASS"
SendResponse("530 Please login with USER and PASS.");
return;
} // specify username
if (strCommand == "USER") //是USER命令
{
// only accept anonymous account
if (strArgs.CompareNoCase(AfxGetApp()->GetProfileString("Settings", "userName", "sunrise")) == )
{
SendResponse("331 User name ok, need password.");
m_strUserName = strArgs; //保存登陆上来的用户名
}
else
SendResponse("530 Not logged in. No such account.");
}
else
// specify password
if (strCommand == "PASS") //是PASS命令
{
if (m_strUserName.IsEmpty()) //要先USER命令
{
SendResponse("503 Login with USER first.");
return;
} //密码是meng
if (strArgs.CompareNoCase(AfxGetApp()->GetProfileString("Settings", "password", "meng")) != )
{
SendResponse("503 password is wrong.");
return;
}
// login client
m_strHomeDir = ((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_strHomeDirectory; //设置主目录
m_strCurrentDir = m_strHomeDir; //设置当前目录 SendResponse("230 User logged in."); //回复登陆成功
m_nStatus = STATUS_IDLE; //修改状态
}
else
// close connection
if (strCommand == "QUIT") //QUIT命令
{
// send goodbye message to client
SendResponse("220 Goodbye."); Close(); // tell our thread we have been closed
AfxGetThread()->PostThreadMessage(WM_QUIT,,);
}
else
// change transfer type
if (strCommand == "TYPE")
{
SendResponse("200 Type set to %s.", strArgs);
}
else
// print current directory
if ((strCommand == "PWD") || (strCommand == "XPWD"))
{
CString strRelativePath;
GetRelativePath(m_strCurrentDir, strRelativePath); //获取当前目录
SendResponse("257 \"%s\" is current directory.", strRelativePath);
}
else
// change working directory
if (strCommand == "CWD")
{
DoChangeDirectory(strArgs); //更改路径
}
else
// change to parent directory
if (strCommand == "CDUP")
{
DoChangeDirectory("..");
}
else
// specify IP and port (PORT a1,a2,a3,a4,p1,p2) -> IP address a1.a2.a3.a4, port p1*256+p2.
if (strCommand == "PORT") //在这里指定端口
{
CString strSub;
int nCount=; while (AfxExtractSubString(strSub, strArgs, nCount++, ',')) //这里又提取了参数,这个命令是在干嘛
{
switch(nCount)
{
case : // a1
m_strRemoteHost = strSub;
m_strRemoteHost += ".";
break;
case : // a2
m_strRemoteHost += strSub;
m_strRemoteHost += ".";
break;
case : // a3
m_strRemoteHost += strSub;
m_strRemoteHost += ".";
break;
case : // a4
m_strRemoteHost += strSub;
break;
case : // p1
m_nRemotePort = *atoi(strSub);
break;
case : // p2
m_nRemotePort += atoi(strSub);
break;
}
}
SendResponse("200 Port command successful.");
}
else
// list current directory (or a specified file/directory)
if ((strCommand == "LIST") ||
(strCommand == "NLST"))
{
StripParameters(strArgs); CString strResult;
if (!GetDirectoryList(strArgs, strResult))
{
return;
} SendResponse("150 Opening ASCII mode data connection for directory list."); // create data connection with client
if (CreateDataConnection()) //在这里就创建了数据套接字连接了
{
if (strResult.IsEmpty())
{
// close data connection with client
DestroyDataConnection(); SendResponse("226 Transfer complete.");
m_nStatus = STATUS_IDLE;
return;
}
}
else
{
// close data connection with client
DestroyDataConnection(); //不成功则销毁
return;
} m_nStatus = STATUS_LIST; // m_pDataSocket->AsyncSelect(); // send the listing
m_pDataSocket->SendData(strResult);
}
else
// retrieve file
if (strCommand == "RETR")
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowDownload)
{
DoRetrieveFile(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// client wants to upload file
if (strCommand == "STOR")
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowUpload)
{
DoStoreFile(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// delete file
if (strCommand == "DELE")
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowDelete)
{
DoDeleteFile(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// remove directory
if ((strCommand == "RMD") || (strCommand == "XRMD"))
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowDelete)
{
DoDeleteDirectory(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// create directory
if ((strCommand == "MKD") || (strCommand == "XMKD"))
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowCreateDirectory)
{
DoCreateDirectory(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// rename file or directory (part 1)
if (strCommand == "RNFR")
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowRename)
{
DoRenameFrom(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// rename file or directory (part 2)
if (strCommand == "RNTO")
{
if (((CServerDlg *)AfxGetApp()->m_pMainWnd)->m_bAllowRename)
{
DoRenameTo(strArgs);
}
else
{
SendResponse("550 Permission denied.");
}
}
else
// restart transfer from 'x'
if (strCommand == "REST")
{
m_dwRestartOffset = atol(strArgs);
SendResponse("350 Restarting at %d.", m_dwRestartOffset);
}
else
// get file size
if (strCommand == "SIZE")
{
CString strLocalPath;
GetLocalPath(strArgs, strLocalPath); // check if directory exists
if (!FileExists(strLocalPath, FALSE))
{
SendResponse("550 File not found.");
return;
}
CFileStatus status;
CFile::GetStatus(strLocalPath, status);
SendResponse("213 %d", status.m_size);
}
else
// switch to passive mode
if (strCommand == "PASV")
{
// delete existing datasocket
DestroyDataConnection(); // create new data socket
m_pDataSocket = new CDataSocket(this); if (!m_pDataSocket->Create(gFixedDataPort))
{
DestroyDataConnection();
SendResponse("421 Failed to create socket.");
return;
}
// start listening
m_pDataSocket->Listen();
m_pDataSocket->AsyncSelect(); CString strIP, strTmp;
UINT nPort; // get our ip address
GetSockName(strIP, nPort);
// retrieve port
m_pDataSocket->GetSockName(strTmp, nPort);
// replace dots
strIP.Replace(".", ",");
// tell the client which address/port to connect to
SendResponse("227 Entering Passive Mode (%s,%d,%d).", strIP, nPort/, nPort%);
m_bPassiveMode = TRUE;
}
else
// abort transfer
if ((strCommand == "ABOR") || (strCommand.Right() == "ABOR"))
{
if (m_pDataSocket)
{
if (m_nStatus != STATUS_IDLE)
{
SendResponse("426 Data connection closed.");
}
// destroy data connection
m_pThread->PostThreadMessage(WM_DESTROYDATACONNECTION, ,);
}
SendResponse("226 ABOR command successful.");
}
else
// get system info
if (strCommand == "SYST")
{
SendResponse("215 UNIX emulated by Baby FTP Server.");
}
else
// dummy instruction
if (strCommand == "NOOP")
{
SendResponse("200 NOOP command successful.");
}
else
{
SendResponse("502 Command not implemented.");
}
}

  首先从命令行语句里拆分出命令关键字和参数。这里面的命令要是一个个列出来太长了。主要是要弄清楚FTP通信的流程,也就是这些命令的内在逻辑,等到具体开发某个功能的时候,再一一弄清楚就可以了。

   5、m_pDataSocket是CControlSocket中的成员变量,指向的是数据传输socket。在什么地方创建了CDataSocket呢,在LIST/NLIST命令中调用了 CreateDataConnection函数,这里创建了CDataSocket。然后在PASV命令里也创建了CDataSocket。这其实就是文件数据传输时的两种模式,前一种作为tcp客户端,后一种作为tcp服务器。下面是CreateDataConnect函数:

 BOOL CControlSocket::CreateDataConnection()
{
if (!m_bPassiveMode)
{
m_pDataSocket = new CDataSocket(this);
if (m_pDataSocket->Create())
{
m_pDataSocket->AsyncSelect(FD_CONNECT | FD_CLOSE | FD_ACCEPT);
// connect to remote site
if (m_pDataSocket->Connect(m_strRemoteHost, m_nRemotePort) == )
{
if (GetLastError() != WSAEWOULDBLOCK)
{
SendResponse("425 Can't open data connection.");
return FALSE;
}
}
}
else
{
SendResponse("421 Failed to create data connection socket.");
return FALSE;
}
} // wait until we're connected
DWORD dwTickCount = GetTickCount() + ;
while (!m_pDataSocket->m_bConnected)
{
DoEvents();
if (dwTickCount < GetTickCount())
{
SendResponse("421 Failed to create data connection socket.");
return FALSE;
}
}
return TRUE;
}

  数据传输socket已tcp客户端的形式连接服务器,注意连接的端口和服务器ip。这两个参数都在参数PORT解析时被设置。

VC FTP服务器程序分析(三)的更多相关文章

  1. VC FTP服务器程序分析(一)

    想在QT上移植一个FTP服务器程序,先学习windows下的FTP服务器例子,然后随便动手写点东西. 在pudn上搜索 "FTP服务器端和客户端实现 VC“这几个关键字,就可以搜到下面要分析 ...

  2. VC FTP服务器程序分析(二)

    上面讲到了CClientThread类,打开这个类的实现,这个类实现了4个函数.依次分析: 1.InitInstance   其说明如下:InitInstance必须被重载以初始化每个用户界面线程的新 ...

  3. VC FTP服务器程序分析(四)

    下面是数据传输的重点-CDataSocket类,函数不多,都比较重要. 1.OnAccept  数据tcp服务器被连接的虚函数,由框架调用. void CDataSocket::OnAccept(in ...

  4. 搭建ftp服务器实现文件共享

    FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务. FTP(File Transfer Protocol ...

  5. 怎样创建FTP服务器

    怎样创建FTP服务器 2008-05-06 08:42永远的探索|分类:操作系统/系统故障| 浏览6382次 我准备用局域网内的一台机器做FTP服务器,创建FTP服务器一定要用Windows serv ...

  6. linux上搭建ftp服务器

    摘要 vsftpd 是"very secure FTP daemon"的缩写,安全性是它的一个最大的特点.vsftpd 是一个 UNIX 类操作系统上运行的服务器的名字,它可以运行 ...

  7. 转:【专题十二】实现一个简单的FTP服务器

    引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...

  8. Linux中搭建FTP服务器

    FTP工作原理 (1)FTP使用端口 [root@localhost ~]# cat /etc/services | grep ftp ftp-data 20/tcp #数据链路:端口20 ftp 2 ...

  9. 专题十二:实现一个简单的FTP服务器

    引言: 在本专题中将和大家分享如何自己实现一个简单的FTP服务器.在我们平时的上网过程中,一般都是使用FTP的客户端来对商家提供的服务器进行访问(上传.下载文件),例如我们经常用到微软的SkyDriv ...

随机推荐

  1. BZOJ3532 [Sdoi2014]Lis 【网络流退流】

    题目 给定序列A,序列中的每一项Ai有删除代价Bi和附加属性Ci.请删除若 干项,使得4的最长上升子序列长度减少至少1,且付出的代价之和最小,并输出方案. 如果有多种方案,请输出将删去项的附加属性排序 ...

  2. 常州模拟赛d2t2 小X的密室

    题目描述 密室中有 N 个房间,初始时,小 X 在 1 号房间,而出口在 N 号房间. 密室的每一个房间中可能有着一些钥匙和一些传送门,一个传送门会 单向地 创造一条从房间 X 到房间 Y 的通道.另 ...

  3. 【线段树查询区间最值】poj 3264 Balanced Lineup

    #include<cstdio> #include<algorithm> using namespace std; ; struct Seg { int l,r,mi,ma; ...

  4. uva 12304点与直线与圆之间的关系

    Problem E 2D Geometry 110 in 1! This is a collection of 110 (in binary) 2D geometry problems. Circum ...

  5. 看 nova

    本节重点介绍 nova-scheduler 的调度机制和实现方法:即解决如何选择在哪个计算节点上启动 instance 的问题. 创建 Instance 时,用户会提出资源需求,例如 CPU.内存.磁 ...

  6. 标准C程序设计七---07

    Linux应用             编程深入            语言编程 标准C程序设计七---经典C11程序设计    以下内容为阅读:    <标准C程序设计>(第7版) 作者 ...

  7. Day 7 Linux之系统监控、硬盘分区等

    Linux之系统监控.硬盘分区等 系统监控 系统监视和进程控制工具—top和free 1) 掌握top命令的功能:top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况, ...

  8. CentOS 7.3 源码安装 OpenVAS 9

    https://my.oschina.net/u/2613235/blog/1583198

  9. Educational Codeforces Round 51 (Rated for Div. 2) The Shortest Statement

    题目链接:The Shortest Statement 今天又在群里看到一个同学问$n$个$n$条边,怎么查询两点直接最短路.看来这种题还挺常见的. 为什么最终答案要从42个点的最短路(到$x,y$) ...

  10. windows安装SUSE Linux Enterprise Server 12

    一:打开“开发人员模式” 点击开始菜单按钮,选择“设置” 在设置中选择“更新和安全” 在菜单中选择“针对开发人员”,在三个选项中,选中“开发人员模式” 在弹出的警告框中点击“是” 这样开发人员模式就打 ...