通过socket实现FTP客户端(MFC版)
使用socket我们可以自己编写一个FTP的客户端。开始之前先了解FTP的两种工作模式大致的过程。
被动模式的流程
被动模式下,FTP客户端使用一个非知名的随机端口N(>1024)去连接FTP服务器的21端口建立控制连接,然后通过控制通道发送PASV命令到服务器,服务器收到后会开启一个随机端口P(>1024)并开始监听,同时告诉客户端连接我的端口P,客户端随后使用N+1端口连接服务器的端口P,建立数据连接。
主动模式的流程
主动模式下,FTP客户端使用一个非知名的随机端口N(>1024)去连接FTP服务器的21端口建立控制连接,然后通过控制通道发送PORT N+1命令到服务器,同时开始监听N+1端口,服务器收到后主动使用20端口与客户端的N+1端口连接,建立数据连接。
客户端与服务器交互的常用命令
这里的命令是指底层数据包封装的命令,并不是指用户在控制台使用的与服务器交互的命令,比如dir、get、put等等。
-
客户端使用USER、PASS命令登录 FTP 服务器。
-
客户端使用PASV命令告知服务器使用被动模式,获取服务器数据连接的端口号,与服务器建立数据连接。
-
客户端使用PORT命令告知服务器使用主动模式,附带自己监听的数据端口,让服务器主动与其建立数据连接。
-
客户端使用SIZE命令获取文件的大小。
-
客户端使用RETR、STOR命令下载、上传文件。
-
客户端使用QUIT命令退出连接。
使用socket实现FTP客户端
创建socket
WSADATA WSAData; if (WSAStartup(MAKEWORD(2,2),&WSAData)!=0)//建立套接字绑定 { MessageBox("初始化Winsock失败!"); return; } SockCtrl=socket(AF_INET,SOCK_STREAM,0);//创建连接套接字 if (SockCtrl==INVALID_SOCKET) { MessageBox("创建控制Socket失败!"); WSACleanup(); return; }
建立控制连接
nConnect=connect(SockCtrl,(sockaddr*)&serveraddr,sizeof(serveraddr));//建立FTP控制连接 if (nConnect==SOCKET_ERROR) { MessageBox("控制连接建立失败!"); closesocket(SockCtrl); WSACleanup(); return; } if (!RecvRespond()) return; else { //判断连接应答码 if (RespondCode==220) { m_tips+="Server: "; m_tips+=Respond; } else { MessageBox("控制连接响应错误!"); closesocket(SockCtrl); WSACleanup(); return; } }
定义SendCommand函数,用于客户端发送命令
//发送命令 bool CFtpClientDlg::SendCommand(void) { int nSend; nSend=send(SockCtrl,Command,strlen(Command),0); if (nSend==SOCKET_ERROR) { MessageBox("Socket发送失败!"); closesocket(SockCtrl); WSACleanup(); return false; } return true; }
定义RecvRespond函数,用于客户端接收响应码
//接收响应码 bool CFtpClientDlg::RecvRespond(void) { int nRecv; memset(Respond,0,MAX_SIZE); nRecv=recv(SockCtrl,Respond,MAX_SIZE,0);//通过连接接收响应 if (nRecv==SOCKET_ERROR) { MessageBox("Socket接收失败!"); closesocket(SockCtrl); WSACleanup(); return false; } //从响应中解析响应码 char* ReplyCodes=new char[3]; memset(ReplyCodes,0,3); memcpy(ReplyCodes,Respond,3); RespondCode=atoi(ReplyCodes); return true; }
验证用户名、口令
sprintf(Command,"USER %s\r\n",m_user); //Command为事先定义的缓冲区 if (!SendCommand()) return; m_tips+="Client: ";//m_tips为编辑框控件变量,用于显示交互过程 m_tips+=Command; if (!RecvRespond()) return; else { //判断USER应答码 if (RespondCode==220 || RespondCode==331) { m_tips+="Server: "; m_tips+=Respond; } else { MessageBox("USER响应错误!"); closesocket(SockCtrl); WSACleanup(); return; } } if (RespondCode==331)//User name okay,need password { //合成PASS命令 memset(Command,0,MAX_SIZE); sprintf(Command,"PASS %s\r\n",m_pwd); if (!SendCommand()) return; m_tips+="Client: PASS ******\r\n"; if (!RecvRespond()) return; else { //判断PASS响应码 if (RespondCode==230) { m_tips+="Server: "; m_tips+=Respond; m_CONNECT.EnableWindow(FALSE); m_QUIT.EnableWindow