socket和ftp,ftp客户端和服务端传递ftp命令时
利用Socket通信实现FTP客户端程序
利用Socket通信实现FTP客户端程序软件工程师高是IBM中国系统与技术中心的软件工程师,从事测试工具的开发工作。顾,现任IBM中国系统与技术中心软件工程师,从事测试工具的开发工作。软件工程师顾是IBM中国系统与技术中心的软件工程师,从事测试工具的开发工作。简介:FlashFXP、File Zilla等FTP客户端应用广泛。原则上都是由底层套接字实现的。对于FTP客户端和服务器之间的数据交换,必须建立两个套接字,一个作为命令通道,另一个作为数据通道。前者用于客户端向服务器发送命令,如登录和删除文件,后者用于接收数据,如下载或上传文件。详细阐述了如何调用系统接口发送FTP命令来实现文件上传、下载等FTP客户端功能,使读者对FTP客户端的原理有一个深入的了解。
本文标签:ftp,linux网络编程,网络,套接字,使用,客户端程序,教学,通信实现,通用编程
标记这篇文章!发布日期:2011年4月7日
级别:初级
浏览次数:22620次
评论:0(查看添加评论-登录)
平均分(94分)
给这篇文章打分。
FTP概述
文件传输协议(FTP)作为网络上共享文件的传输协议,广泛应用于网络应用软件中。FTP的目标是提高文件共享和传输数据的可靠性和效率。
传输文件时,FTP客户端程序首先与服务器建立连接,然后向服务器发送命令。收到命令后,服务器响应并执行命令。FTP协议与操作系统无关。只要任何操作系统上的程序都符合FTP协议,就可以互相传输数据。本文基于LINUX平台,详细阐述了FTP客户端的实现原理,并说明了如何用C语言编写一个简单的FTP客户端。
回到顶端
文件传送协议
与其他协议(如HTTP)相比,FTP更复杂。与一般C/S应用不同的是,一般C/S应用通常只建立一个Socket连接,同时处理服务器和客户端的连接命令和数据传输。FTP协议中命令和数据分开传输的方法提高了效率。
FTP使用两个端口,一个数据端口和一个命令端口(也称为控制端口)。这两个端口一般是21(命令端口)和20(数据端口)。控制套接字用于传输命令,数据套接字用于传输数据。在每个FTP命令被发送后,FTP服务器将返回一个字符串,包括一个响应代码和一些解释信息。返回码主要用于判断命令是否被成功执行。
命令端口
一般来说,客户端有一个套接字连接FTP服务器的相关端口,负责发送FTP命令和接收返回的响应信息。有些操作,如“登录”、“更改目录”、“删除文件”,可以通过这个连接发送命令来完成。
数据端口
对于数据传输的操作,我们主要是显示目录列表,上传下载文件,需要依靠另一个Socket来完成。
如果使用被动模式,通常服务器会返回一个端口号。客户端需要打开另一个Socket连接到这个端口,然后我们就可以根据操作发送命令,数据会通过一个新打开的端口进行传输。
如果使用主动模式,通常客户端会向服务器发送一个端口号,它会在这个端口监视器中。服务器需要连接客户端打开的数据端口,传输数据。
下面简单介绍一下FTP的主动模式和被动模式。
活动模式(端口)
在主动模式下,客户端随机打开一个大于1024的端口发起连接到服务器的命令端口P,即端口21,同时打开N-1端口监视器,向服务器发送“port N-1”命令,服务器从自己的数据端口(20)主动连接到客户端指定的数据端口(N-1)。
FTP客户端只是告诉服务器它自己的端口号,并让服务器连接到客户端指定的端口。对于客户端的防火墙来说,这是由外向内的连接,可能会被屏蔽。
被动模式(PASV)
为了解决服务器向客户发起的连接问题,还有另一种FTP连接模式,即被动模式。命令连接和数据连接由客户端发起,解决了从服务器到客户端数据端口的连接被防火墙过滤的问题。
在被动模式下,当FTP连接打开时,客户端会打开两个任意的本地端口(N 1024和N 1)。
第一个端口连接到服务器的端口21,并提交PASV命令。然后,服务器会打开任意端口(P 1024),返回“227进入被动模式(127,0,0,1,4,18)”。它返回227开头的信息。括号中有六个用逗号分隔的数字。前四个是服务器的地址,后两个是服务器的地址。倒数第二个乘以256,加上最后一个数字。这是FTP服务器为传输数据而开放的端口。如果获得227进入被动模式(H1、H2、H3、H4、P1、P2),则端口号为p1*256 p2,ip地址为h1.h2.h3.h4,这意味着服务器上有一个开放的端口。客户端收到获取端口号的命令后,会通过端口N 1连接到服务器的端口P,然后在两个端口之间传输数据。
使用的主要FTP命令
每个FTP命令由3到4个字母组成,后跟参数,用空格分隔。每个命令都以 rn 结尾。
要下载或上传文件,首先登录FTP服务器,然后发送命令,最后退出。在这个过程中,使用的主要命令是用户、通过、大小、休息、CWD、RETR、PASV、港口和退出。
用户:指定用户名。它通常是控制连接后发出的第一个命令。"用户gaoleyirn ":用户名是gaoleyi登录名。
通过:指定用户密码。该命令紧跟在用户命令之后。" PASS gaoleyirn ":密码是gaoleyi。
SIZE:从服务器返回指定文件的大小。" SIZE file.txtrn ":如果file.txt存在,则返回文件的大小。
CWD:改变工作目录。例如“CWD dirnamern”。
PASV:让服务器在数据端口监视器中进入被动模式。例如“PASVrn”。
端口:告诉FTP服务器客户端监听的端口号,让FTP服务器以主动模式连接客户端。例如“端口h1、h2、h3、h4、p1、p2”。
RETR:下载文件。" RETR文件. txt rn ":下载文件file.txt
STOR:上传文件。" STOR文件. txtrn ":上传文件file.txt
REST:这个命令不传输文件,但是跳过指定点之后的数据。该命令后面应该跟有其他需要文件传输的FTP命令。" REST 100rn ":将文件传输的偏移量重新指定为100字节。
退出:关闭与服务器的连接。
FTP响应代码
客户端发送FTP命令后,服务器返回响应代码。
响应代码由三位数代码表示:
第一个数字给出了命令状态的一般指示,例如成功、失败或不完整的响应。
第二个数字是响应类型的分类,例如2表示与连接相关的响应,3表示用户身份验证。
第三个数字提供了更详细的信息。
第一个数字的含义如下:
1表示服务器已正确接收到信息,但尚未进行处理。
2表示服务器已经正确处理了信息。
3表示服务器已正确接收到信息并正在处理它。
4表示信息暂时错误。
5表示信息永远是错误的。
第二个数字的含义如下:
0表示语法。
1表示系统状态和信息。
2表示连接状态。
3表示与用户认证相关的信息。
4表示未定义。
5表示与文件系统相关的信息。
套接字编程的几个重要步骤
套接字编程的主要步骤如下:
Socket()创建一个Socket connect()来连接服务器write()和read()进行会话Close()Close Socket服务器端编程。主要步骤如下:
Socket()创建一个套接字bind() listen()来侦听accept()以接收连接请求write()和read()来关闭会话()以关闭套接字返回到页面顶部。
实现FTP客户端上传下载功能。
下面我们通过一个例子来深入了解FTP客户端。本文实现的FTP客户端具有以下功能:
在客户端和FTP服务器之间建立套接字连接。发送用户并向服务器传递命令以登录FTP服务器。使用PASV命令获取服务器监控的端口号并建立数据连接。使用RETR/STOR命令下载/上传文件。下载后,断开数据连接,发送退出命令退出。本例中使用的FTP服务器是filezilla。在整个交互过程中,控制连接始终处于连接状态,每次传输一个文件,数据连接先打开后关闭。
在客户端和FTP服务器之间建立套接字连接。
当客户端与服务器连接时,服务器将返回一个响应代码220和一些欢迎信息。
图一。客户端连接到服务器
清单1。客户端连接到FTP服务器并接收欢迎消息。
SOCKET control _ sock
struct hostent * hp
struct sockaddr_in服务器;
memset( server,0,sizeof(struct sockaddr _ in));
/*初始化套接字*/
control_sock=socket(AF_INET,SOCK_STREAM,0);
HP=gethostbyname(server _ name);
memcpy( server.sin_addr,hp- h_addr,HP-h _ length);
server.sin _ family=AF _ INET
server . sin _ port=htons(port);
/*连接到服务器端*/
connect(control_sock,(struct sockaddr *) server,sizeof(server));
/*客户端从服务器接收一些欢迎消息*/
read(control_sock,read_buf,read _ len);
客户端登录FTP服务器
当客户端发送用户名和密码,服务器通过验证后,会返回一个响应码230。然后,客户端可以向服务器发送命令。
图二。客户端登录到FTP服务器
2.客户端发送用户名和密码并登录到FTP服务器。
/* command "用户用户名 r n" */
sprintf(send_buf,用户%srn ,用户名);
/*客户端将用户名发送给服务器*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端收到服务器的响应代码和信息,通常是“331用户名没问题,需要密码。”*/
read(control_sock,read_buf,read _ len);
/*命令" pass password r n" */
sprintf(send_buf, PASS %srn ,password);
/*客户端将密码发送给服务器*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和信息,通常是“230用户登录,已处理”*/
read(control_sock,read_buf,read _ len);
让客户端FTP服务器进入被动模式
在下载/上传文件之前,客户端发送命令使服务器进入被动模式。服务器将打开数据端口并监听。并返回响应代码227和数据连接的端口号。
图3。客户端将服务器置于被动模式。
清单3。让服务器在数据端口监视器中进入被动模式
/* command "pasv r n" */
sprintf(send_buf, PASV r n );
/*客户端告诉服务器使用被动模式*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和新开放的端口号,
*正常是“227进入被动模式(H1、H2、H3、H4、P1、P2)”*/
read(control_sock,read_buf,read _ len);
客户端以被动模式下载文件。
当客户端发送下载文件的命令时。服务器将返回响应代码150,并将文件内容发送到数据连接。
图4。客户端从FTP服务器下载文件
清单4。客户端连接到FTP服务器的数据端口并下载文件。
/*连接到服务器新打开的数据端口*/
connect(data_sock,(struct sockaddr *) server,sizeof(server));
/*命令" cwd目录名 r n" */
sprintf(send_buf, CWD %srn ,dirname);
/*客户端发送命令更改工作目录*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和信息,通常是“250 command okay”*/
read(control_sock,read_buf,read _ len);
/* command "size文件名 r n" */
sprintf(send_buf, SIZE %srn ,filename);
/*客户端发送命令从服务器获取下载文件的大小*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和信息,通常是“213大小”*/
read(control_sock,read_buf,read _ len);
/* command "retrfilename r n" */
sprintf(send_buf, RETR %srn ,文件名);
/*客户端发送命令从服务器下载文件*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应码和信息,一般是“150打开数据连接。”*/
read(control_sock,read_buf,read _ len);
/*客户端创建文件*/
file_handle=open(disk_name,CRFLAGS,rwx all);
for(;) {
.
/*客户端通过数据连接从服务器接收文件内容*/
read(data_sock,read_buf,read _ len);
/*客户端编写文件*/
write(file_handle,read_buf,read _ len);
.
/*客户端关闭文件*/
RC=close(file _ handle);
客户端退出服务器。
当客户端完成下载后,发送命令退出服务器并关闭连接。服务器将返回一个响应代码200。
图5。客户端从FTP服务器退出
清单5。客户端关闭数据连接,退出FTP服务器并关闭控制连接。
/*客户端关闭数据连接*/
close(data _ sock);
/*客户端接收服务器的响应代码和信息,通常为“226传输完成”*/
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 closesconnection”*/
read(control_sock,read_buf,read _ len);
/*客户端关闭控制连接*/
close(control _ sock);
至此,文件下载已经完成。需要注意的是,发送FTP命令时,命令后面应该跟“rn”,否则服务器不会返回信息。回车符“rn”是FTP命令的结束符号。当服务器收到此符号时,它认为客户端发送的命令已经结束并开始处理。否则,我们将继续等待。
我们来看看FTP服务器这一端的响应:
清单6。客户端下载文件时FTP服务器的响应输出
(未登录)(127.0.0.1)已连接,正在发送欢迎消息.
(未登录)(127.0.0.1) 220-FileZilla服务器版本0.9.36测试版
(未登录)(127.0.0.1) 220你好高乐易
(未登录)(127.0.0.1)用户高乐易
(未登录)(127.0.0.1)高乐易需要331密码
(未登录)(127.0.0.1)通过********
高勒伊(127.0.0.1) 230登陆
高勒伊(127.0.0.1) PWD
gaoleyi (127.0.0.1) 257 /是当前目录。
高乐易(127.0.0.1)大小file.txt
高乐意(127.0.0.1) 213 4096
高勒伊(127.0.0.1) PASV
高勒伊(127.0.0.1) 227进入被动模式(127,0,0,1,13,67)
高乐易(127.0.0.1) RETR file.txt
gaoleyi (127.0.0.1) 150连接被接受
高乐意(127.0.0.1) 226转账OK
高乐义(127.0.0.1)退出
高乐意(127.0.0.1) 221再见
首先,服务器在准备就绪时返回220。客户端收到服务器的响应码后,发送“用户用户名”和“密码”命令登录。然后,服务器返回的响应代码以230开头,表示客户端已经登录。此时,客户端发送PASV命令,使服务器进入被动模式。服务器返回如“227进入被动模式(127,0,0,1,13,67)”,客户端从中获取端口号,然后连接到服务器的数据端口。接下来,客户端发送下载命令,服务器返回响应代码150,并从数据端口发送数据。最后服务器返回“226传输完成”,表示数据传输完成。
请注意,客户端不应该一次发送多个命令。例如,如果我们想打开一个目录并显示它,我们必须发送CWD迪纳尔PASV的列表。发送CWD目录名后等待响应代码,然后发送以下代码。当PASV返回时,我们打开另一个套接字并连接到相关端口。然后发送列表,返回125,开始接收数据,最后返回226表示完成。
在传输多个文件的过程中,需要注意的是,必须重新使用PASV来为每个新的传输获取新的端口号。收到数据后,应该关闭数据连接,这样服务器将返回2XX成功响应。然后,客户端可以继续传输下一个文件。
上传文件和下载文件相比,登录验证和切换被动模式是一样的。它只需要改变发送到服务器的命令,并通过数据连接发送文件内容。
客户端以被动模式上传文件到服务器。
当客户端发送上传文件的命令时,服务器将从数据连接接收文件。
图6。客户端连接到FTP服务器的数据端口,上传文件。
客户端在主动模式下将文件上传到服务器。
到目前为止,本文描述了客户端使用被动模式上传和下载文件。下面描述了客户端在主动模式下下载文件。
图7。在活动模式下从FTP服务器下载文件
清单7。示例C程序,用于在活动模式下从FTP服务器下载文件
.
SOCKET data _ sock
data_sock=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in名称;
name.sin _ family=AF _ INET
name . sin _ addr . s _ addr=htons(in addr _ ANY);
server _ port=p1 * 256 p2
length=sizeof(name);
name . sin _ port=htons(server _ port);
bind(server_sock,(struct sockaddr *) name,length);
client_name中的struct sockaddr _ in
length=sizeof(client _ name);
/*客户端开始监听端口p1*256 p2 */
listen(server_sock,64);
/* command "port r n" */
sprintf(send_buf,端口1287,0,0,1,%d,%drn ,p1,p2);
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和信息,通常为“200端口命令成功”*/
read(control_sock,read_buf,read _ len);
sprintf(send_buf, RETR文件名. txt r n );
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收到服务器的响应码和信息,一般是“150打开数据通道进行文件传输。”*/
read(control_sock,read_buf,read _ len);
/* ftp客户端接受来自服务器的连接请求*/
data_sock=accept(server_sock,(struct sockaddr *) client_name,length);
.
file_handle=open(disk_name,ROFLAGS,rwx all);
for(;) {
.
read(data_sock,read_buf,read _ len);
write(file_handle,read_buf,read _ len);
.
close(file _ handle);
通过客户端端口命令告诉服务器连接其p1*256 p2端口。然后在这个端口监听,等待FTP服务器连接,然后通过这个数据端口传输文件。端口模式传输数据时,FTP客户端实际上相当于一个服务器,FTP服务器主动连接自己。
超文本传送协议(Hyper Text Transport Protocol的缩写)
由于网络的不稳定性,在传输文件的过程中,连接可能会断开。这时候客户端需要支持断点续传的功能,下次就可以从上次终止的地方开始传文件了。需要命令REST。如果文件在断开连接之前已经传输了512个字节。那么断点续传的起始位置是512,服务器会跳过传输文件的前512个字节。
清单8。从FTP服务器断点继续下载文件
.
/*命令" rest offset r n" */
sprintf(send_buf, REST %ldrn ,offset);
/*客户端发送命令指定下载文件的偏移量*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和信息,
*正常是“350恢复原位。发送store或retrieve以启动传输。*/
read(control_sock,read_buf,read _ len);
.
/* command "retrfilename r n" */
sprintf(send_buf, RETR %srn ,文件名);
/*客户端发送命令从服务器下载文件,并跳过文件的第一个偏移字节*/
write(control_sock,send_buf,strlen(send _ buf));
/*客户端接收服务器的响应代码和信息,*客户端
*正常是“接受150连接,在偏移位置恢复”*/
read(control_sock,read_buf,read _ len);
.
file_handle=open(disk_name,CRFLAGS,rwx all);
/*指向文件写入的初始位置*/
lseek(file_handle,offset,SEEK _ SET);
.
回到顶端
结束语
本文从应用实现的角度介绍了FTP协议。结合具体实例,分析了如何利用主动模式和被动模式实现FTP客户端上传下载文件,以及如何在断点处继续传输。通过本文,读者可以对FTP客户端的原理有一个深入的了解。