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. (2015大作业)茹何优雅的手写正则表达式引擎(regular expression engine

    貌似刚开学的时候装了个逼,和老师立了个flag说我要写个正则表达式引擎,然后学期末估计老师早就忘了这茬了,在历时3个月的懒癌发作下,终于在这学期末deadline的时候花了一个下午加晚上在没有网的房间 ...

  2. 常州模拟赛d2t1 小X的质数

    题目背景 小 X 是一位热爱数学的男孩子,在茫茫的数字中,他对质数更有一种独特的 情感.小 X 认为,质数是一切自然数起源的地方. 题目描述 在小 X 的认知里,质数是除了本身和 1 以外,没有其他因 ...

  3. hdu 1104 数论+bfs

    Remainder Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total ...

  4. 封装的一个Worker方法,利用线程处理运行时间较长的js代码

    function webWorker(targetFile,content){ var promise=new Promise(function(resolve,reject){ var web=ne ...

  5. Arduino学习笔记1---开发环境搭建

    主要内容:(一). Arduino IDE的下载及安装 (二). Arduino IDE的应用 (三). Arduino的程序结构 (四). Arduino程序的编译及下载 (一). Arduino ...

  6. css3 nth-child 与 nth-of-type 的区别

    by zhangxinxu from http://www.zhangxinxu.com本文地址:http://www.zhangxinxu.com/wordpress/?p=1709 一.深呼吸,直 ...

  7. RabbitMQ 简介以及使用场景

    目录 一. RabbitMQ 简介 二. RabbitMQ 使用场景 1. 解耦(为面向服务的架构(SOA)提供基本的最终一致性实现) 2. 异步提升效率 3. 流量削峰 三. 引入消息队列的优缺点 ...

  8. 【Java TCP/IP Socket】TCP Socket(含代码)

    TCP的Java支持 协议相当于相互通信的程序间达成的一种约定,它规定了分组报文的结构.交换方式.包含的意义以及怎样对报文所包含的信息进行解析,TCP/IP协议族有IP协议.TCP协议和UDP协议.现 ...

  9. SQL Server 存储字符数较大字段的问题

    SQL Server 2000专门提供了处理text,ntext,image字段的函数,他们是: TEXTPTR TEXTVALID READTEXT UPDATETEXT WRITETEXT 一般作 ...

  10. Info.plist 的字段解释

    bundle字段 这些字段名都是XML中的名称,在xcode的属性编辑器中,名字并不相同 bundle目录中的属性列表详细描述了有关该bundle的信息.Finder和一些系统API在一些情况下会使用 ...