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 ...
随机推荐
- 洛谷P1435 回文字串
题目背景 IOI2000第一题 题目描述 回文词是一种对称的字符串.任意给定一个字符串,通过插入若干字符,都可以变成回文词.此题的任务是,求出将给定字符串变成回文词所需要插入的最少字符数. 比如 “A ...
- 【bzoj1710】[Usaco2007 Open]Cheappal 廉价回文
[bzoj1710][Usaco2007 Open]Cheappal 廉价回文 Description 为了跟踪所有的牛,农夫JOHN在农场上装了一套自动系统. 他给了每一个头牛一个电子牌号 当牛走过 ...
- cf287E Main Sequence
As you know, Vova has recently become a new shaman in the city of Ultima Thule. So, he has received ...
- 在 Linux 实例上自动安装并运行 VNC Server
原文网址:https://help.aliyun.com/knowledge_detail/41181.html?spm=5176.8208715.110.11.4c184ae8mlC7Yy 您可以使 ...
- COdevs 天梯 水题系列
1203 判断浮点数是否相等 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 青铜 Bronze 题目描述 Description 给出两个浮点数,请你判断这两个浮点数是否相等 输入 ...
- Codeforces Round #268 (Div. 2) D. Two Sets [stl - set + 暴力]
8161957 2014-10-10 06:12:37 njczy2010 D - Two Sets GNU C++ A ...
- WEB学习-HTML的基本语法特性
HTML对换行不敏感,对tab不敏感 HTML只在乎标签的嵌套结构,嵌套的关系.谁嵌套了谁,谁被谁嵌套了,和换行.tab无关. 换不换行.tab不tab,都不影响页面的结构. 所以: • <di ...
- GoldenDict词典下载安装
Debian/Ubuntu下载: sudo apt-get install goldendict 添加中文维基百科/维基词典: 选择[词典]->[词典来源]->[维基百科]->[添加 ...
- Unable to locate Attribute with the the given name [] on this ManagedType
最近在写Springboot+hibernate的项目,遇到这个问题 好坑,查了半天没发现我哪里配置错了,后来发现实体类声明字段大小写敏感,把声明字段改成小写就行了
- 前端和后端采用接口访问时的调用验证机制(基于JWT的前后端验证)(思路探讨)
说明:基于前后端,尤其是使用Ajax请求的接口,现在市面上网页上调用的Ajax基本都是没有验证的,如果单独提取之后可以无线的刷数据. 继上一篇http://www.cnblogs.com/EasonJ ...