转自:https://blog.csdn.net/wumenglu1018/article/details/53749452
FTP概述
文件传输协议(FTP)作为网络共享文件的传输协议,在网络应用软件中具有广泛的应用。FTP的目标是提高文件的共享性和可靠高效地传送数据。
在传输文件时,FTP 客户端程序先与服务器建立连接,然后向服务器发送命令。服务器收到命令后给予响应,并执行命令。FTP 协议与操作系统无关,任何操作系统上的程序只要符合 FTP 协议,就可以相互传输数据。本文主要基于 LINUX 平台,对 FTP 客户端的实现原理进行详尽的解释并阐述如何使用 C 语言编写一个简单的 FTP 客户端。
FTP协议
相比其他协议,如 HTTP 协议,FTP 协议要复杂一些。与一般的 C/S 应用不同点在于一般的C/S 应用程序一般只会建立一个 Socket 连接,这个连接同时处理服务器端和客户端的连接命令和数据传输。而FTP协议中将命令与数据分开传送的方法提高了效率。
FTP 使用 2 个端口,一个数据端口和一个命令端口(也叫做控制端口)。这两个端口一般是21 (命令端口)和 20 (数据端口)。控制 Socket 用来传送命令,数据 Socket 是用于传送数据。每一个 FTP 命令发送之后,FTP 服务器都会返回一个字符串,其中包括一个响应代码和一些说明信息。其中的返回码主要是用于判断命令是否被成功执行了。
命令端口
一般来说,客户端有一个 Socket 用来连接 FTP 服务器的相关端口,它负责 FTP 命令的发送和接收返回的响应信息。一些操作如“登录”、“改变目录”、“删除文件”,依靠这个连接发送命令就可完成。
数据端口
对于有数据传输的操作,主要是显示目录列表,上传、下载文件,我们需要依靠另一个 Socket来完成。
如果使用被动模式,通常服务器端会返回一个端口号。客户端需要用另开一个 Socket 来连接这个端口,然后我们可根据操作来发送命令,数据会通过新开的一个端口传输。
如果使用主动模式,通常客户端会发送一个端口号给服务器端,并在这个端口监听。服务器需要连接到客户端开启的这个数据端口,并进行数据的传输。
下面对 FTP 的主动模式和被动模式做一个简单的介绍。
主动模式 (PORT)
主动模式下,客户端随机打开一个大于 1024 的端口向服务器的命令端口 P,即 21 端口,发起连接,同时开放N +1 端口监听,并向服务器发出 “port N+1” 命令,由服务器从它自己的数据端口 (20) 主动连接到客户端指定的数据端口 (N+1)。
FTP 的客户端只是告诉服务器自己的端口号,让服务器来连接客户端指定的端口。对于客户端的防火墙来说,这是从外部到内部的连接,可能会被阻塞。
被动模式 (PASV)
为了解决服务器发起到客户的连接问题,有了另一种 FTP 连接方式,即被动方式。命令连接和数据连接都由客户端发起,这样就解决了从服务器到客户端的数据端口的连接被防火墙过滤的问题。
被动模式下,当开启一个 FTP 连接时,客户端打开两个任意的本地端口 (N > 1024 和 N+1) 。
第一个端口连接服务器的 21 端口,提交 PASV 命令。然后,服务器会开启一个任意的端口 (P > 1024 ),返回如“227 entering passive mode (127,0,0,1,4,18)”。 它返回了 227 开头的信息,在括号中有以逗号隔开的六个数字,前四个指服务器的地址,最后两个,将倒数第二个乘 256 再加上最后一个数字,这就是 FTP 服务器开放的用来进行数据传输的端口。如得到 227 entering passive mode (h1,h2,h3,h4,p1,p2),那么端口号是 p1*256+p2,ip 地址为h1.h2.h3.h4。这意味着在服务器上有一个端口被开放。客户端收到命令取得端口号之后, 会通过 N+1 号端口连接服务器的端口 P,然后在两个端口之间进行数据传输。
FTP命令
FTP响应码
客户端登录FTP服务器
输入信息
int login()
{
int port=21;
char** pptr;
char username[128];
char host[32];//save space
const char* ipv4 =username;//save space
struct hostent *infor;
/* set connect */
printf("FTP Server IPv4 : ");
scanf("%s",host);
infor = gethostbyname(host);//域名解析
char buf[16];
pptr = infor->h_addr_list;
for(; *pptr !=NULL;pptr++)
ipv4 = inet_ntop(AF_INET , *pptr , buf , 16);
int csock = ftp_connection( ipv4, port);
if(csock < 0)
{
printf("Cannot connet to %s : %d\n",ipv4,port);
return -1;
}
/* login */
printf(" Username : ");
scanf("%s",username);
char* password = getpass(" Password : ");
if(ftp_login(csock , username , password))
return csock;
else
return -1;
}
登录server
int ftp_login(int csock , const char* username , const char* password)
{
char buffer[128];
memset(buffer, 0, sizeof(buffer)) ;
snprintf(buffer , sizeof(buffer) , "USER %s\r\n" , username);
if(!write(csock , buffer , strlen(buffer)))
return 0;
memset(buffer , 0 , sizeof(buffer));
if(read(csock , buffer , 128)<=0)// 331 means need password
return 0;
memset(buffer , 0 , sizeof(buffer));
snprintf(buffer , sizeof(buffer) ,"PASS %s\r\n" , password);
if(!write(csock , buffer , strlen(buffer)))
return 0;
memset(buffer , 0 , sizeof(buffer));
if(read(csock , buffer , 128)<=0)
return 0;
if(strcmp(buffer , "230") >=0 )
return 1;
else
return 0;
}
客户端让 FTP 服务器进入被动模式
int ftp_passive(int csock)
{
if(write(csock , "PASV/r/n" , 6) <= 0)
return -1;
char buffer[128];
if(read(csock , buffer , 128) <= 0)
return -1;
uint16_t port;
uint16_t ip0,ip1,ip2,ip3,port0,port1;
char ipv4[32];
char* fp = strstr(buffer , "(");
sscanf(fp , "(%hu,%hu,%hu,%hu,%hu,%hu" , &ip0 , &ip1 , &ip2 , &ip3 , &port0 , &port1);
sprintf(ipv4,"%hu,%hu,%hu,%hu" , &ip0 , ip1 , &ip2 , &ip3);
port = port0*256 + port1;
return ftp_connection(ipv4 , port);
}
客户端通过被动模式下载文件
int ftp_tell_download(int csock , const char* path)
{
char buffer[1024];
snprintf(buffer , sizeof(buffer) , "RETR %s/r/n" , path);
if(write(csock , buffer , 7+strlen(path)))
return 1;
else
return 0;
}
从上述几个操作我们可以看出,ftp操作主要是和服务器的命令交互,通过控制连接发送命令,然后服务器发送响应码,根据响应码客户端做出相应的操作~~~