FTP基础知识_FTP服务端应用开发

网络编程_进阶_FTP服务器开发

文件传输是日常生活,工作中需求量较高的一项功能,FTP可以使用户在两个已联网的计算机之间传输文件,它是互联网环境下传递文件最主要的方法。
要了解FTP的传输过程,学习主动模式被动模式的原理以及了解FTP相关的命令和应答信息自然是避免不了的.
下面对FTP进行简单的介绍.


FTP简介

FTP( File Transfer Protocol )文件传输协议是基于TCP协议的.
基于UDP协议的是TFTP( Trivial File Transfer Protocol )简单文件传送协议,这里直介绍TCP.
TCP是互联网上使用得最广泛得文件传输协议.
正因为基于TCP协议,因此它是面向连接的,相对于无连接协议更安全可靠.
这类协议的特点是在存取一个文件之前必须先获取到该文件的本地副本.
修改文件的时候是对这个副本做修改,然后再将修改后的文件副本传回源节点.
FTP协议使用两个端口来操作数据的收发,一个数据端口和一个控制命令端口( 也叫做端口 ).
通常控制命令端口是21,数据端口是20,如下图所示 :
在这里插入图片描述

控制命令端口主要使用在FTP控制部分,登录用户,密码验证,
发送文件的名称以及发送方式的设置等操作都使用这个端口连接,
每次操作都会建立一个用于数据传输的TCP连接;

数据端口通常使用20端口,但出于安全的考虑在数据传输用的端口号中使用随机数来进行分配.

  • 主动模式
    无论是主动模式还是被动模式,都需要建立两个TCP连接.下面是主动模式的连接过程.
  1. 客户端通过一个随机端口n连接到服务端的命令端口21;
  2. 接着客户端开始监听端口n+1,并向服务端发送命令"PORT n+1";
  3. 再由服务端自己的数据端口20连接到客户端指定的数据端口n+1.
  • 被动模式
    命令连接和数据连接都有客户端发起.先由客户端打开两个随机本地端口nn+1(端口通常是大于1024的高位端口),
  1. 客户端的第一个端口n连接服务端的21端口,提交"PASV"命令;
  2. 之后服务端开启一个随机端口向客户端发送"PORT"命令;
  3. 客户端使用第二个端口n+1连接服务端的随机端口,来进行数据传输.
  • FTP命令和应答信息
访问控制命令
USER 用户名 输入用户名
PASS 密码 输入密码
CWD 目录名 修改工作目录
QUIT 退出 正常结束
设置传输参数命令
PORT 指定数据传输时使用的IP地址和端口
PASV 不是从服务器端向客户端建立链接, 而是由客户端开始向服务器端建立数据传输用的链接
TYPE 设置发送和接收的数据类型
STRU 指定文件结构
FTP服务命令
RETR 文件名 从FTP服务器下载文件(RETRIEVE)
STOR 文件名 向服务器上传文件(STORE)
STOU 文件名 向服务器发送文件. 当存在同名文件时,为了避免冲突,适当地修改当前文件名后上传(STORE UNIQUE)
APPE 文件名 向服务器发送文件. 当存在同名文件时,将当前文件内容追加到已有文件(APPEND)
RNFR 文件名 指定RNTO之前要修改名称文件(RENAME FROM)
RNTO 文件名 修改由RNFR指定文件的文件名(RENAME TO)
ABOR 处理中断,异常退出
DELE 文件名 从服务器上删除指定文件
RMD 目录名 删除目录
MKD 目录名 创建目录
PWD 列出当前目录位置
LIST 文件列表的请求
NLST 文件名一览表请求(NAME LIST)
SITE 字符串 执行服务器提供的特殊命令
SYST 获取服务器操作系统的信息(SYSTEM)
STAT 显示服务器FTP的状态
HELP 命令帮助
NOOP 无操作(NO OPERATION)
提供信息
120 服务器准备就绪的时间
( Service ready in nnn minutes )
125 打开数据连接,开始传输
( Data connection already open;transfer starting )
150 打开连接
( File status okay;about open data connection )
连接管理相关应答
200 成功
( Command okay )
202 命令没有执行
( Command not implemented, superfluous at this site )
211 系统状态回复
( System status,or system help reply )
212 目录状态回复
( Directory status )
213 文件状态回复
( File status )
214 帮助信息回复
( Help message )
215 系统类型回复
( Name system type.Where Name is an official system name from the list in the Assigned Numbers document )
220 服务就绪
( Service ready for new user )
221 退出网络
( Service closing control connection. Logged out if appropriate )
225 打开数据连接
( Data connection open; no transfer in progress )
226 结束数据连接
( Closing data connection. Requested file action successful )
227 进入被动模式(IP地址,256*端口1,+端口2)
( Entering Passive Mode(h1,h2,h3,h4,p1,p2) )
230 用户已登录
( User logged in, proceed )
250 文件行为完成
( Requested file action okay,completed )
257 路径名建立
( "PATHNAME" created )
验证与用户相关应答
331 要求密码
( User name okay,need password )
332 要求账户
( Need account for login )
350 文件行为暂停
( Requested file action pending further information )
不固定的错误
421 服务关闭
( Service not available,closing control connection. This may be a reply to any command if the service knows it shut down )
425 无法打开数据连接
( Can't open data connection )
426 结束连接
( Connection closed; transfer aborted )
450 文件不可用
( Requested file action not taken. File unavailable )
451 遇到本地错误
( Requested action aborted; local error in processing )
452 系统磁盘空间不足
( Requested action not taken. Insufficient storage space in system )
文件系统相关应答
500 无效命令
( Syntax error, command unrecognized )
501 错误参数
( Syntax error in parameters or arguments )
502 命令没有执行
( Command not implemented )
503 错误指令序列
( Bad sequence of commands )
504 无效的命令参数
( Command not implemented for that parameter )
530 未登录网络
( Not logged in )
532 存储文件需要账号
( Need account for storing files )
550 文件不可用
( Requested action not taken. File unavailable )
551 不知道的页类型
( Requested action aborted: page type unknown )
552 超过存储分配
( Requested file action aborted. Exceeded storage allocation )
553 文件名不允许
( Requested action not taken. File name not allowed )

FTP数据包分析

操作win10系统的command窗口连接ubuntu搭建的vsftp服务端,
再使用Wireshark工具捕获FTP数据包.
通过command窗口使用命令行的方式可以更加直观的看到FTP服务端与客户端之间的交互行为.

  1. 在建立FTP连接之前双方主机会通过ARP包确认身份,我这里使用的是Vmware虚拟机,使用nat连接模式.\
  • 服务端 ip192.168.159.88
  • 客户端 ip192.168.159.1
    在这里插入图片描述

数据包内容 :
在这里插入图片描述

客户端对服务端发起的建立第一条TCP连接,客户端的随机端口连接服务端的控制命令端口21,这里也是经典的TCP三次握手.

在这里插入图片描述

  1. 输入用户名和密码,客户端接收到响应信息,可对照上一部分的表理解.
    在这里插入图片描述

相关的数据包(输入用户名和密码) :
在这里插入图片描述

  1. 使用命令行"quote PASV"可以切换成PASV被动模式,服务队给出的随机端口是256*36+236 = 9452
    在这里插入图片描述

被动模式数据包 :
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IeRtIoai-1653187944232)(passive_data_packet.png)]

数据包内容 :
在这里插入图片描述

使用被动模式下载文件:
在这里插入图片描述

主动模式下的数据包:
在这里插入图片描述

从服务端下载文件的时候建立第二条TCP连接 :
在这里插入图片描述

下载文件时的数据包:
在这里插入图片描述

  1. 命令行输入"quit"断开连接的时候,是经典的TCP四次挥手 :
    在这里插入图片描述在这里插入图片描述

FTP项目代码

  • CSock_kun类来封装WindowsSock API
#pragma once
#include <WinSock2.h>

typedef int  socklen_t;
typedef  void THREAD_RET;

class CSock_kun
{
public:
	CSock_kun();
	~CSock_kun();

	operator SOCKET() {
		return m_hSocket;
	}
	
	/* *
	 * Function: 封装WSA的socket和bind函数,达到更便捷的调用方式
	 * parm1: UINT 是unsigned int类型在windows的宏定义.IP的端口,传入到sockaddr_in结构体
	 * parm2: Socket类型,socket函数需要的参数,SOCK_STREAM(tcp)或者SOCK_DGRAM(udp)
	 * parm3: LPCSTR 是一种字符串类型,L表示long,P表示指针,C的意思是常量,STR就是字符串.
	 *        IP,传入到sockaddr_in的结构体
	 * */
	BOOL Create(UINT nPort = 0,int nSockType = SOCK_STREAM,LPCSTR sIP = NULL);
	
	/* *
	 * Function: 封装WSA的sendto函数,功能是发送未建立连接的udp数据包,一般在服务端使用
	 * parm1: 待发送的缓冲区数据的地址,注意这里是一个指针类型
	 * parm2: 参数1数据的字节数
	 * parm3: 类型参考Create成员函数,目标IP,是传入到sockaddr_in结构体
	 * parm4: 类型参考Create成员函数,目标IP的端口,同样也是传入到sockaddr_in的结构体
	 * */
	int SendTo(void *dataBuf,int nLen,LPCSTR sIP,UINT nPort);

	/* *
	 * Function: 接收数据的函数,tcp和udp都使用此函数,一般在客户端使用
	 * parm1: 接收数据的缓冲区地址
	 * parm2: 接收数据的字节数
	 * parm3: flags通常将值设为0,进阶使用可参考:
	 *	  [linux socket中 send recv函数的 flags参数](https://blog.csdn.net/whatday/article/details/89964168)
	 * */
	int Recveive(const void *dataBuf,int nLen,int nFlags = 0);

	/* *
	 * Function: 一般用于tcp协议的连接,功能是套接字处于连接状态时使用,客户端使用本函数
	 *        来发送数据给服务端,服务端通过本函数向客户端发送应答.
	 * parm1: 发送数据的缓冲区地址
	 * parm2: 发送数据的字节数
	 * parm3: 同Recveive函数的Flags参数一样
	 * */
	int Send(const void* dataBuf,int nLen,int nFlags = 0);

	/* *
	 * Function: 服务端在bind绑定套接字以后,还需要listen来让套接字处于被动监听的状态
	 * parm1: 请求队列的最大长度,当请求队列满时就不再接收新的请求.
	 * */
	BOOL Listen(int nConnectionBacklog = 5);

	/* *
	 * Function: 封装WSA的accept函数.套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求
	 * parm1: socka 为服务端的套接字
	 * parm2: sIP,传入到 sockaddr_in 结构体变量,类型是LPSTR,不是const类型
	 * parm3: pPort,也是传入到sockaddr_in 结构体中
	 * */
	BOOL Accept(CSock_kun&socka,LPSTR sIP=NULL,UINT *nPort=NULL);
	
	/* *
	 * Funciton: 用来建立连接,一般是客户端使用
	 * parm1: sIP,传入到 sockaddr_in 结构体变量,类型同Create函数 
	 * parm2: pPort,也是传入到sockaddr_in 结构体中,类型同Create函数
	 * */
	BOOL Connect(LPCSTR sIP,UINT nPort);
	
	/* *
	 * Function: 封装closesocket函数,用它来关闭m_hSocket套接字,和C语言的fclose函数类似
	 * */
	void Close();
	
	/* *
	 * Function: 接收一个数据报并保存源地址,一般用于udp协议中.但tcp协议中在connect函数
	 *	      调用后也可使用本函数接收数据
	 * parm1: 接收数据的缓冲区地址
	 * parm2: 接收数据的字节数
	 * parm3: 指定接收端的IP,类型同Accept函数
	 * parm4: 指定接收端IP的端口,类型同Accept函数
	 * */
	int ReceiveFrom(void *dataBuf,int nLen,LPSTR sIP,UINT *nPort);
	
	/* *
	 * Function: 封装WSAGetLastError函数,获取错误编码
	 * */
	static int GetLastError();
	
	/* *
	 * Function: 封装getsockname函数,用来获取与某个套接字关联的本地协议地址
	 * parm1: sIP,传入到 sockaddr_in 结构体变量
	 * parm2: pPort,也是传入到sockaddr_in 结构体中
	 * */
	BOOL GetSockName(LPSTR sIP,UINT &nPort);
	
	/* *
	 * Function: 封装getpeername函数,用来获取与某个套接字关联的外地协议地址
	 * parm1: sIP,传入到 sockaddr_in 结构体变量
	 * parm2: pPort,也是传入到sockaddr_in 结构体中
	 * */
	BOOL GetPeerName(LPSTR sIP, UINT &nPort);
private:
	SOCKET m_hSocket;
};
  • 使用CApp类作为用户管理类
#pragma once
#include "CProtocal.h"
class CApp {//也可以理解为用户管理类
public:
	/* *
   	 * Function: CApp类的主动执行函数
   	 * */
	int Main();
	/* *
	 * Function: 验证用户输入的用户是否存在
	 * */
	BOOL IsUser(const std::string& sUser);
	/* *
	 * Function: 验证用户密码是否正确
	 * */
	BOOL CheckPass(const std::string& sUser, 
			       LPCSTR sPass, std::string& sPath);
	/* *
	 * Function: CApp类的单例模式
	 * */
	static CApp& getInstance();

private:
	/* *
	 * Function: 加载用户信息,可以连接数据库,但这里主要演示
	 * */
	void LoadUsers();
private:
	std::map<std::string, SInfo> m_map;

};
  • 关于FTP服务端的操作类CProtocal
#pragma once
#include "define.h"

class CProtocal {//结合CApp类可理解为用户类
public:
	/* *
	 * Function: 获取Accept连接请求的套接字
	 * */
	CSock_kun& GetSocket();
	/* *
	 * Function: Accpet连接请求验证对方IP允许访问则执行本函数
	 *		   发送"220  Service ready for new user"的状态码
	 * */
	int Start();

private:
	/* *
	 * Function: 接收服务端返回的信息,在前面Accept连接请求允许之后执行
	 * */
	BOOL OnReceive();

	/* *
	 * Function: 解析FTP服务命令
	 * parm1: FTP服务命令名称
	 * parm2: 有些服务会带上文件名称等操作参数
	 * */
	BOOL Process(LPCSTR sKey, LPCSTR sData);

	/* *
	 * Function:
	 * */
	void Run();

	/* *
	 * Function: 验证用户输入的用户是否存在
	 *			 存在则发送"331 账户存在"
	 *			 否则发送  "530 账户不存在"
	 * parm1: LPCSTR sUser传递用户输入的用户名
	 * */
	BOOL OnUser(LPCSTR sUser);

	/* *
	 * Function: 验证用户输入的密码是否正确
	 *			 密码正确发送"230 登录成功"
	 *			 错误密码发送"530 密码错误"
	 * parm1: 传递用户输入的密码
	 * */
	BOOL OnPass(LPCSTR sPass);

	/* *
	 * Function: 可浏览的文件传输格式,以文本和二进制两种格式为准
	 *			 "200 Service ready for new user "
	 * parm1: 文件类型,用枚举类型'A'或'I'表示
	 * */
	BOOL TYPE(LPCSTR sType);

	/* *
	 * Function: 变更当前目录(change work directory),判断"根目录+子目录"是否存在
	 *			 "250 Requested file action okay,completed "表示当前目录已改变
	 *			 "550 Requested action not taken. File unavailable "表示该目录无法访问
	 * parm1:  用户选择的目录
	 * */
	BOOL CWD(LPCSTR sDir);

	/* *
	 * Function: 删除文件
	 *			 "250 Requested file action okay,completed"
	 *			 "553 Requested action not taken. File name not allowed "
	 * parm1: 用户选择的文件名
	 * */
	BOOL DELE(LPCSTR sFile);

	/* *
	 * Function: 新建文件夹
	 *			 "250 Requested file action okay,completed "
	 *			 "553  Requested action not taken. File name not allowed "
	 *  parm1: 用户输入的文件夹名称
	 * */
	BOOL MKD(LPCSTR sDir);

	/* *
	 * Function: FTP中的删除文件夹命令,服务端返回信息:
	 *			 "250 Requested file action okay,completed "
	 *			 "553  Requested action not taken. File name not allowed "
	 * parm1: 用户指定的文件夹名称 
	 * */
	BOOL RMD(LPCSTR sDir);

	/* *
	 * Function: FTP中的重命名文件操作开始
	 *			 "250 Requested file action okay,completed "
	 *			 "553  Requested action not taken. File name not allowed "
	 * parm1: 用户指定的文件名
	 * */
	BOOL RNFR(LPCSTR sFile);

	/* *
	 * Function: 重命名操作结束
	 *			 "250 Requested file action okay,completed "
	 *			 "553  Requested action not taken. File name not allowed "
	 * parm1: 用户指定的文件名
	 * */
	BOOL RNTO(LPCSTR sFile);

	/* *
	 * Function: FTP中上传文件的命令,服务端返回信息:
	 *			 "150 File status okay;about open data connection "
	 *			 "426 Connection closed; transfer aborted "
	 * parm1: 用户指定的文件名
	 * */
	BOOL STOR(LPCSTR sFile);

	/* *
	 * Function: FTP中下载文件的命令,服务端返回信息:
	 *			 "150 File status okay;about open data connection "
	 *			 "426 Connection closed; transfer aborted "
	 * parm1: 同STOR函数,指用户指定的文件名
	 * */
	BOOL RETR(LPCSTR sFile);

	/* *
	 * Function: 返回指定文件的大小,服务端返回信息:
	 *			 "213 xxx"xxx表示该文件的大小
	 *			 "213 0"表示文件不存在
	 * parm1: 用户指定的文件名
	 * */
	BOOL SIZE(LPCSTR sFile);

	/* *
	 * Function: 主动模式,服务端返回信息如下:
	 *			 "200 Command okay "表示成功
	 *			 "425 Can't open data connection "表示连接失败
	 * parm1: 传入一个地址信息,包括IP地址和端口
	 * */
	BOOL PORT(LPCSTR sAddr);

	/* *
	 * Function: 被动模式,服务端返回信息如下:
	 *			 "227 Entering Passive Mode(h1,h2,h3,h4,p1,p2)"
	 *			 "425 Can't open data connection "
	 * */
	BOOL PASV();

	/* *
	 * Function: 返回指定 FTP 连接的当前目录名称,服务端返回信息如下:
	 *			 "250 Requested file action okay,completed "
	 *			 "503 Bad sequence of commands "
	 * */
	BOOL PWD();

	/* *
	 * Function: 列出当前文件夹的文件信息,服务端返回信息如下:
	 *			 "150  File status okay;about open data connection "
	 *			 "226  Closing data connection. Requested file action successful "
	 * */
	BOOL LIST();

	/* *
	 * Function: 获取服务器的操作系统,服务端返回信息如下:
	 *			 "215 Windows"或"215 Linux"等
	 * */
	BOOL SYSTEM();

	/* *
	 * Function: 未定义的操作
	 *			 "502 Command not implemented "
	 * */
	BOOL UnKnown();

	/* *
	 * Function: 创建一个线程,支持多客户端同时访问
	 * parm1: void* p传入的是一个CProtocal对象
	 * */
	static THREAD_RET theProc(void* p);

	/* *
	 * Function: 获取绝对路径
	 * */
	inline std::string GetPath();

	/* *
	 * Function: 获取绝对路径的文件
	 * parm1: 用户选择的文件
	 * */
	inline std::string GetFile(LPCSTR sFile);

	/* *
	 * Function: 获取端口
	 * parm1: 一个有包含IP和端口信息的字符串,例如:192,168,1,12,23,6
	 * */
	static int GetPort(LPSTR s);

	/* *
	 * Function: 处理IP字符串信息中的',',覆盖成'.'
	 * parm1: 一个有包含IP信息的字符串,例如:192,168,1,12
	 * */
	static void ConvertIP(LPSTR s);

private:
	enum class EType { TYPE_A = 'A', TYPE_I = 'I' };
	EType m_eType;
	std::string m_sUser;
	std::string m_sPass;
	std::string m_sPath;//根目录
	std::string m_sCWD = "/";//子目录
	std::string m_sOldName = "/";//子目录
	CSock_kun m_socka;
	CSock_kun m_sockd, m_sockt;
};

全局函数和变量的声明文件define.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <map>
#include <process.h>
#include "CSock_kun.h"

struct SInfo
{
	std::string szPass;
	std::string szPath;
};
  • 程序运行结果:
    在这里插入图片描述

拓展学习链接:

下面的链接拓展也是我自己在学习FTP的时候觉得有帮助的,特地在此分享!

猜你喜欢

转载自blog.csdn.net/Stephen8848/article/details/124908019