登录 |  注册 |  繁體中文


使用Socket实现FTP客户端

分类: 服务器相关 颜色:橙色 默认  字号: 阅读(1217) | 评论(0)

一、概述

FTP 客户端如 FlashFXP,File Zilla 被广泛应用,原理上都是用Socket 来实现。FTP 客户端与服务器端进行数据交换必须建立两个套接字,一个作为命令通道,一个作为数据通道。前者用于客户端向服务器发送命令,如登录,删除某个文件,后者用于接收数据,例如下载或上传文件等。

FTP 使用 2 个端口,一个数据端口和一个命令端口(也叫做控制端口)。这两个端口一般是21 (命令端口)和 20 (数据端口)。控制 Socket 用来传送命令,数据 Socket 是用于传送数据。每一个 FTP 命令发送之后,FTP 服务器都会返回一个字符串,其中包括一个响应代码和一些说明信息。其中的返回码主要是用于判断命令是否被成功执行了。
 
对于有数据传输的操作,主要是显示目录列表,上传、下载文件,我们需要依靠另一个 Socket来完成。
如果使用被动模式,通常服务器端会返回一个端口号。客户端需要用另开一个 Socket 来连接这个端口,然后我们可根据操作来发送命令,数据会通过新开的一个端口传输。
如果使用主动模式,通常客户端会发送一个端口号给服务器端,并在这个端口监听。服务器需要连接到客户端开启的这个数据端口,并进行数据的传输。
 
关于被动模式和主动模式,请参考 https://www.php3.cn/a/282.html 之前写的文章
 
被动模式下,数据端口计算方法:
提交 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 每个命令都有 3 到 4 个字母组成,命令后面跟参数,用空格分开。每个命令都以 "\r\n "结束
 
响应如下
 

实现 FTP 客户端上传下载功能

下面让我们通过一个例子来对 FTP 客户端有一个深入的了解。本文实现的 FTP 客户端有下列功能:
 
客户端和 FTP 服务器建立 Socket 连接。
向服务器发送 USER、PASS 命令登录 FTP 服务器。
使用 PASV 命令得到服务器监听的端口号,建立数据连接。
使用 RETR/STOR 命令下载/上传文件。
在下载完毕后断开数据连接并发送 QUIT 命令退出。

 

/* 命令 ”USER username\r\n” */
sprintf(send_buf,"USER %s\r\n",username);
/*客户端发送用户名到服务器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”331 User name okay, need password.” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”PASS password\r\n” */
sprintf(send_buf,"PASS %s\r\n",password);
/* 客户端发送密码到服务器端 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”230 User logged in, proceed.” */
read(control_sock, read_buf, read_len);


#让服务器进入被动模式,在数据端口监听
/* 命令 ”PASV\r\n” */
sprintf(send_buf,"PASV\r\n");
/* 客户端告诉服务器用被动模式 */
write(control_sock, send_buf, strlen(send_buf));
/*客户端接收服务器的响应码和新开的端口号,
* 正常为 ”227 Entering passive mode (h1,h2,h3,h4,p1,p2)” */
read(control_sock, read_buf, read_len);




/* 连接服务器新开的数据端口, 端口号是 p1*256+p2 */
connect(data_sock,(struct sockaddr *)&server, sizeof(server));
/* 命令 ”CWD dirname\r\n” */
sprintf(send_buf,"CWD %s\r\n", dirname);
/* 客户端发送命令改变工作目录 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”250 Command okay.” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”SIZE filename\r\n” */
sprintf(send_buf,"SIZE %s\r\n",filename);
/* 客户端发送命令从服务器端得到下载文件的大小 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”213 ” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”RETR filename\r\n” */
sprintf(send_buf,"RETR %s\r\n",filename);
/* 客户端发送命令从服务器端下载文件 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码和信息,正常为 ”150 Opening data connection.” */
read(control_sock, read_buf, read_len);
 
/* 客户端创建文件 */
file_handle = open(disk_name, CRFLAGS, RWXALL);
for( ; ; ) {
... ...
/* 客户端通过数据连接 从服务器接收文件内容 */
read(data_sock, read_buf, read_len);
/* 客户端写文件 */
write(file_handle, read_buf, read_len);
... ... 
}
/* 客户端关闭文件 */
rc = close(file_handle);




/* 客户端关闭数据连接 */
close(data_sock);
/* 客户端接收服务器的响应码和信息,正常为 ”226 Transfer complete.” */
read(control_sock, read_buf, read_len);
 
/* 命令 ”QUIT\r\n” */
sprintf(send_buf,"QUIT\r\n");
/* 客户端将断开与服务器端的连接 */
write(control_sock, send_buf, strlen(send_buf));
/* 客户端接收服务器的响应码,正常为 ”200 Closes connection.” */
read(control_sock, read_buf, read_len);
/* 客户端关闭控制连接 */
close(control_sock);


效果如下

(not logged in) (127.0.0.1)> Connected, sending welcome message...
(not logged in) (127.0.0.1)> 220-FileZilla Server version 0.9.36 beta
(not logged in) (127.0.0.1)> 220 hello php3
(not logged in) (127.0.0.1)> USER php3
(not logged in) (127.0.0.1)> 331 Password required for php3
(not logged in) (127.0.0.1)> PASS *********
php3 (127.0.0.1)> 230 Logged on
php3 (127.0.0.1)> PWD 
php3 (127.0.0.1)> 257 "/" is current directory.
php3 (127.0.0.1)> SIZE file.txt
php3 (127.0.0.1)> 213 4096
php3 (127.0.0.1)> PASV
php3 (127.0.0.1)> 227 Entering Passive Mode (127,0,0,1,13,67)
php3 (127.0.0.1)> RETR file.txt
php3 (127.0.0.1)> 150 Connection accepted
php3 (127.0.0.1)> 226 Transfer OK
php3 (127.0.0.1)> QUIT
php3 (127.0.0.1)> 221 Goodbye




姓 名: *
邮 箱:
内 容: *
验证码: 点击刷新 *   

回到顶部