VC FTP服务器程序分析(三)
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服务器程序分析(三)的更多相关文章
- VC FTP服务器程序分析(一)
想在QT上移植一个FTP服务器程序,先学习windows下的FTP服务器例子,然后随便动手写点东西. 在pudn上搜索 "FTP服务器端和客户端实现 VC“这几个关键字,就可以搜到下面要分析 ...
- VC FTP服务器程序分析(二)
上面讲到了CClientThread类,打开这个类的实现,这个类实现了4个函数.依次分析: 1.InitInstance 其说明如下:InitInstance必须被重载以初始化每个用户界面线程的新 ...
- VC FTP服务器程序分析(四)
下面是数据传输的重点-CDataSocket类,函数不多,都比较重要. 1.OnAccept 数据tcp服务器被连接的虚函数,由框架调用. void CDataSocket::OnAccept(in ...
- 搭建ftp服务器实现文件共享
FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务. FTP(File Transfer Protocol ...
- 怎样创建FTP服务器
怎样创建FTP服务器 2008-05-06 08:42永远的探索|分类:操作系统/系统故障| 浏览6382次 我准备用局域网内的一台机器做FTP服务器,创建FTP服务器一定要用Windows serv ...
- linux上搭建ftp服务器
摘要 vsftpd 是"very secure FTP daemon"的缩写,安全性是它的一个最大的特点.vsftpd 是一个 UNIX 类操作系统上运行的服务器的名字,它可以运行 ...
- 转:【专题十二】实现一个简单的FTP服务器
引言: 休息一个国庆节后好久没有更新文章了,主要是刚开始休息完心态还没有调整过来的, 现在差不多进入状态了, 所以继续和大家分享下网络编程的知识,在本专题中将和大家分享如何自己实现一个简单的FTP服务 ...
- Linux中搭建FTP服务器
FTP工作原理 (1)FTP使用端口 [root@localhost ~]# cat /etc/services | grep ftp ftp-data 20/tcp #数据链路:端口20 ftp 2 ...
- 专题十二:实现一个简单的FTP服务器
引言: 在本专题中将和大家分享如何自己实现一个简单的FTP服务器.在我们平时的上网过程中,一般都是使用FTP的客户端来对商家提供的服务器进行访问(上传.下载文件),例如我们经常用到微软的SkyDriv ...
随机推荐
- kubernetes---CentOS7安装kubernetes1.11.2图文完整版
转载请注明出处:kubernetes-CentOS7安装kubernetes1.11.2图文完整版 架构规划 k8s至少需要一个master和一个node才能组成一个可用集群. 本章我们搭建一个mas ...
- ElasticSearch聚合入门(续)
主要理解聚合中的terms. 参考:http://www.cnblogs.com/xing901022/p/4947436.html Terms聚合 记录有多少F,多少M { "size&q ...
- 树状数组求第K大(From CLJ)
; <<log2[n];p;p>>=) if(a[ret+p]<=kth) kth-=a[ret+=p]; return ret;
- HDU4768:Flyer [ 二分的奇妙应用 好题 ]
传送门 Flyer Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- 王垠:完全用Linux工作 (2003)
完全用Linux工作,抛弃windows 我已经半年没有使用 Windows 的方式工作了.Linux 高效的完成了我所有的工作. GNU/Linux 不是每个人都想用的.如果你只需要处理一般的事务, ...
- 解决本地调用office组件成功,但是发布到IIS中出现的错误(检索COM类工厂中CLSID为{00024500-0000-0000-C000-000000000046}的组件时失败)
在C#操作word或者Excel,我们可能会用到微软内置的COM组件,会出现很多问题. 如:在本地调试导出Excel没有问题,发布到IIS就有问题了,检测到的异常: 我们会发现在iis上运行的程序,没 ...
- Unix操作系统LD_PRELOAD简介
http://blog.csdn.net/ieearth/article/details/49952047 Unix操作系统的动态链接库的知识中,这个功能主要就是用来有选择性的载入Unix操作系统不同 ...
- 批量修改WORD表格属性
有时候需要对word中很多表格的属性进行修改,而word无法批量修改属性,所有这里记录一个宏 Sub TableFormatter() Dim oTbl As Table, i As Integer ...
- Limitations of Forms Personalization (文档 ID 420518.1)
In this Document Purpose Scope Details Diagnostics & Utilities Community: References A ...
- BUPT复试专题—内存分配(2014-2)
题目描述 在操作系统中,内存分配是非常重要的工作.己知内存空间由N个内存块组成,这些内存块从1到N编号.进行内存分配时,操作系统将选择一块大小足够的内存全部分配给请求内存的进程.例如,当进程请求10M ...