FTPの基礎_FTPサーバーアプリ開発

ネットワークプログラミング_上級_FTPサーバー開発

ファイル転送は、日常生活や仕事で需要の高い機能であり、FTPネットワークに接続された 2 台のコンピュータ間でファイルを転送することができ、インターネット環境における最も重要なファイル転送方法です。送信プロセス、関連するコマンドと応答情報学習と理解の原則を
理解することは避けられません.以下に簡単な紹介があります.FTP主动模式被动模式FTP
FTP


FTP の紹介

FTPTCP( File Transfer Protocol ) ファイル転送プロトコルはプロトコルに基づいています.プロトコルは( Trivial File Transfer Protocol ) シンプルなファイル転送プロトコル
に基づいています. これはここで紹介されています.インターネット上で最も広く使用されているファイル転送プロトコルです.プロトコルに基づいているため、接続指向であり、コネクションレス プロトコルよりも安全で信頼性が高くなります.このタイプのプロトコルの特徴は、ファイルにアクセスする前にファイルを取得する必要があることです.ファイルを変更する場合、コピーが変更され、次に変更されたファイルの最終コピーが送信元ノードに送り返されます.プロトコルは、データの送受信を操作するために2つのポートを使用します, 1つと1つ(ポートとも呼ばれます).通常、制御コマンドポートはおよび次の図に示すように、データ ポートは です。UDPTFTPTCP
TCP
TCP
本地副本

FTP数据端口控制命令端口
2120
ここに画像の説明を挿入

控制命令端口FTP主に制御部で使用され
、ユーザーのログイン、パスワードの確認、送信ファイル名、送信方法の設定などの操作はすべてこのポートを使用して接続し、各操作は
データ送信のためのTCP接続を確立します。

数据端口通常はポートが使用されます20が、セキュリティ上の理由から、データ送信用のポート番号の割り当てには乱数が使用されます。

  • アクティブモード
    アクティブモードでもパッシブモードでも、2 つのTCP接続を確立する必要があります. 以下は、アクティブモードの接続プロセスです.
  1. クライアントは、ランダムなポートを介してnサーバーのコマンド ポートに接続します21
  2. 次に、クライアントはポートのリッスンを開始しn+1、コマンドをサーバーに送信します"PORT n+1"
  3. 次に、サーバー自体のデータ ポートを20クライアントによって指定されたデータ ポートに接続しますn+1
  • パッシブ モード コマンド
    接続とデータ接続の両方がクライアントによって開始されます. 最初に、クライアントは 2 つのランダムなローカル ポートを開きn(n+1通常、ポートは1024それよりも上位のポートです)、
  1. クライアントの最初のポートがnサーバーのポートに接続され21"PASV"コマンドが送信されます。
  2. 次に、サーバーはランダムなポートを開き、コマンドをクライアントに送信します"PORT"
  3. クライアントは、2 番目のポートを使用してn+1サーバーのランダム ポートに接続し、データを送信します。
  • FTP コマンドおよび応答情報
アクセス制御コマンド
ユーザー名 ユーザー名を入力して下さい
パスワード パスワードを入力する
CWD ディレクトリ名 作業ディレクトリを変更する
終了する 正常終了
転送パラメータ設定コマンド
ポート データ転送に使用する IP アドレスとポートを指定します
PASV サーバーからクライアントへのリンクを確立する代わりに、クライアントはサーバーへのデータ送信用のリンクの確立を開始します。
タイプ 送受信するデータの種類を設定する
ストル ファイル構造を指定する
FTP サービスコマンド
RETR ファイル名 FTP サーバーからファイルをダウンロードする (RETRIEVE)
STORファイル名 ファイルをサーバーにアップロード (STORE)
STOU ファイル名 ファイルをサーバーに送信する 同名のファイルが存在する場合、競合を避けるため、現在のファイル名を適宜変更してアップロードする (STORE UNIQUE)
APPE ファイル名 ファイルをサーバーに送信する 同名のファイルが存在する場合、現在のファイルの内容を既存のファイルに追加する (APPEND)
RNFR ファイル名 RNTO を指定する前に名前ファイルを変更する (RENAME FROM)
RNTO ファイル名 RNFRで指定したファイルのファイル名を変更する(RENAME TO)
ABOR 割り込み処理、異常終了
DELE ファイル名 指定したファイルをサーバーから削除します
RMD ディレクトリ名 ディレクトリを削除
MKD ディレクトリ名 ディレクトリを作成する
PWD 現在のディレクトリの場所を一覧表示する
リスト ファイルリストリクエスト
NLST ファイル名リスト要求 (NAME LIST)
SITE 文字列 サーバーが提供する特別なコマンドを実行する
システム サーバーのオペレーティング システム (SYSTEM) に関する情報を取得する
統計 サーバーFTPの状態を表示
ヘルプ コマンドヘルプ
いいえ ノーオペレーション(NO OPERATION)
情報を提供する
120 サーバーの準備が整った時刻
( nnn 分でサービスが準備完了)
125 データ接続を開き、転送を開始します
( データ接続は既に開いています; 転送開始 )
150 接続を開く
(ファイルステータスは正常です。データ接続を開くことについて)
接続管理関連の応答
200 成功
(コマンド OK)
202 コマンドが実装されていません
( コマンドが実装されていません、このサイトでは余分です )
211 システム ステータス応答
(システム ステータス、またはシステム ヘルプ応答)
212 ディレクトリ ステータス応答
(ディレクトリ ステータス)
213 ファイルステータス応答
(ファイルステータス)
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