ネットワークプログラミング_上級_FTPサーバー開発
ファイル転送は、日常生活や仕事で需要の高い機能であり、FTP
ネットワークに接続された 2 台のコンピュータ間でファイルを転送することができ、インターネット環境における最も重要なファイル転送方法です。送信プロセス、関連するコマンドと応答情報の学習と理解の原則を
理解することは避けられません.以下に簡単な紹介があります.FTP
主动模式
被动模式
FTP
FTP
FTP の紹介
FTP
TCP
( File Transfer Protocol ) ファイル転送プロトコルはプロトコルに基づいています.プロトコルは( Trivial File Transfer Protocol ) シンプルなファイル転送プロトコル
に基づいています. これはここで紹介されています.インターネット上で最も広く使用されているファイル転送プロトコルです.プロトコルに基づいているため、接続指向であり、コネクションレス プロトコルよりも安全で信頼性が高くなります.このタイプのプロトコルの特徴は、ファイルにアクセスする前にファイルを取得する必要があることです.ファイルを変更する場合、コピーが変更され、次に変更されたファイルの最終コピーが送信元ノードに送り返されます.プロトコルは、データの送受信を操作するために2つのポートを使用します, 1つと1つ(ポートとも呼ばれます).通常、制御コマンドポートはおよび次の図に示すように、データ ポートは です。UDP
TFTP
TCP
TCP
TCP
本地副本
FTP
数据端口
控制命令端口
21
20
控制命令端口
FTP
主に制御部で使用され
、ユーザーのログイン、パスワードの確認、送信ファイル名、送信方法の設定などの操作はすべてこのポートを使用して接続し、各操作は
データ送信のためのTCP
接続を確立します。
数据端口
通常はポートが使用されます20
が、セキュリティ上の理由から、データ送信用のポート番号の割り当てには乱数が使用されます。
- アクティブモード
アクティブモードでもパッシブモードでも、2 つのTCP
接続を確立する必要があります. 以下は、アクティブモードの接続プロセスです.
- クライアントは、ランダムなポートを介して
n
サーバーのコマンド ポートに接続します21
。 - 次に、クライアントはポートのリッスンを開始し
n+1
、コマンドをサーバーに送信します"PORT n+1"
。 - 次に、サーバー自体のデータ ポートを
20
クライアントによって指定されたデータ ポートに接続しますn+1
。
- パッシブ モード コマンド
接続とデータ接続の両方がクライアントによって開始されます. 最初に、クライアントは 2 つのランダムなローカル ポートを開きn
(n+1
通常、ポートは1024
それよりも上位のポートです)、
- クライアントの最初のポートが
n
サーバーのポートに接続され21
、"PASV"
コマンドが送信されます。 - 次に、サーバーはランダムなポートを開き、コマンドをクライアントに送信します
"PORT"
。 - クライアントは、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服务端与客户端之间的交互行为.
- 在建立
FTP
连接之前双方主机会通过ARP
包确认身份,我这里使用的是Vmware
虚拟机,使用nat
连接模式.\
- 服务端
ip
为192.168.159.88
- 客户端
ip
为192.168.159.1
数据包内容 :
客户端对服务端发起的建立第一条TCP
连接,客户端的随机端口连接服务端的控制命令端口21
,这里也是经典的TCP
三次握手.
- 输入用户名和密码,客户端接收到响应信息,可对照上一部分的表理解.
相关的数据包(输入用户名和密码) :
- 使用命令行
"quote PASV"
可以切换成PASV
被动模式,服务队给出的随机端口是256*36+236 = 9452
被动模式数据包 :
数据包内容 :
使用被动模式下载文件:
主动模式下的数据包:
从服务端下载文件的时候建立第二条TCP连接 :
下载文件时的数据包:
- 命令行输入
"quit"
断开连接的时候,是经典的TCP
四次挥手 :
FTP项目代码
- 用
CSock_kun
类来封装Windows
的Sock 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を学ぶ上で役立つのでシェアします!
- ブログ:
FTP の基本\ - ステーション B ビデオ:
FTP アクティブ モード
FTP パッシブ モード\ VSFTP
ピット:
FTP は 20 秒後にアクティビティがなく、接続がタイムアウトし、ディレクトリの読み取りに失敗しました
Ubuntu 20.04 で VSFTPD を使用して FTP サーバーをセットアップする方法- 書籍:
「図解 TCP/IP」 - 第 8 章 - セクション 8.3 - ファイル転送
「コンピューター ネットワーク 第 7 版」 - 第 6 章 - セクション 6.2 - ファイル転送プロトコル
「Wireshark パケット分析 実際の版 1」 - 第 19 章 - FTP プロトコル サブコントラクト分析