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 ...
随机推荐
- BZOJ 1007 [HNOI2008]水平可见直线 ——计算几何
用了trinkle的方法,半平面交转凸包. 写了一发,既没有精度误差,也很好写. #include <map> #include <ctime> #include <cm ...
- ecplise 使用快捷键
1. [ALT+/] 此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不全方法和属性名称犯愁,当记不全类.方法和属性的名字时,多体验一下[ALT+/]快捷键带来的好处吧. 2. ...
- docker 给容器配置ip(和主机一个网段)
docker 给容器配置ip(和主机一个网段).详情参考:http://www.xiaomastack.com/2015/02/06/docker-static-ip/ #/bin/bash ] || ...
- 降雨量 BZOJ 1067
降雨量 [问题描述] 我们常常会说这样的话:“X年是自Y年以来降雨量最多的”.它的含义是X年的降雨量不超过Y年,且对于任意Y<Z<X,Z年的降雨量严格小于X年.例如2002,2003,20 ...
- MongoDB_起步
MongoDB基本概念 <1> mogoDB是一个文档存储类型的nosql数据库,文档存储一般用类似json的格式存储,存储的内容是文档型的. 这样也就有机会对某些字段建立索引, < ...
- 42.QT-QSqlQuery类操作SQLite数据库(创建、查询、删除、修改)详解
Qt 提供了 QtSql 模块来提供平台独立的基于 SQL 的数据库操作.这里我们所说的“平台 独立”,既包括操作系统平台,也包括各个数据库平台,Qt支持以下几种数据库: QT自带SQLITE数据库, ...
- oracle-统计员工x
1. SELECTe.depid,avg(s.bonussalary+s.basesalary) AS avgsal from employ e,salary s where e.employId=s ...
- 【Linux学习笔记】栈与函数调用惯例
栈与函数调用惯例(又称调用约定)— 基础篇 记得一年半前参加百度的校招面试时,被问到函数调用惯例的问题.当时只是懂个大概,比如常见函数调用约定类型及对应的参数入栈顺序等.最近看书过程中,重新回顾了这些 ...
- BUPT复试专题—图像压缩存储(2015)
题目描述 以二维数组表示图像,其值只有0.1两种,寻找两幅图像中最大的相同方阵 输入 第一行输入一个n,接下来的2n行输入两个n*n数组,寻找一个最大的m*m子区域,使得两个数组在该子区域完全相同 ...
- js 验证 输入值 全是数字
1.使用isNaN()函数 isNaN()的缺点就在于 null.空格以及空串会被按照0来处理 NaN: Not a Number /** *判断是否是数字 **/ function isRealNu ...