说明:这个只是我写出的简单的FTP客户端实现代码,写的很粗糙,当然会有很多问题,不过这个是我自己对于FTP的理解所以记录下来。让各位高手见笑了,请多多指教。
FTP为文件传输协议,使用socket编程实现其客户端,需要了解FTP这个协议。
FTP客户端和服务器端通信使用两个通道:控制通道和数据通道。
一、控制通道
控制通道是FTP客户端和FTP服务器建立一条长连接,客户端通过这条连接发送控制命令,服务器执行命令后通过此链接给客户端响应。
其实现代码
int ConnectToSer(char* ip,int port)//ip为FTP服务器地址,port为21端口,返回值即控制通道
{
struct sockaddr_in seraddr;
int fd;
memset(&seraddr,0x00,sizeof(seraddr));
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
printf("create socket error=[%s]/n",strerror(errno));
return -1;
}
seraddr.sin_family=AF_INET;
inet_aton(ip,&seraddr.sin_addr);
seraddr.sin_port=htons(port);
if(connect(fd,(struct sockaddr*)&seraddr,sizeof(seraddr))<0)
{
printf("connect error=[%s]/n",strerror(errno));
return -1;
}
/***
controlfd即为控制通道描述符,我们以后通过此描述符向服务器发送控制命令
***/
controlfd=fd;
return fd;
}
在控制通道连接建立成功后,我们即可以通过控制通道发送控制命令,比如使用用户名和密码登陆FTP服务器
int DoLogin(char *user,char *pass)
{
char bufs[1024];
memset(bufs,0x00,sizeof(bufs));
sprintf(bufs,"USER %s/r/n",user);
if((ret=write(controlfd,bufs,strlen(bufs)))<0)
{
printf("sendcontrol error=[%s]/n",strerror(errno));
return -1;
}
GetReply();
memset(bufs,0x00,sizeof(bufs));
sprintf(bufs,"PASS %s/r/n",pass);
if((ret=write(controlfd,bufs,strlen(bufs)))<0)
{
printf("sendcontrol error=[%s]/n",strerror(errno));
return -1;
}
GetReply();
return 0;
}
实现很简单,就是将控制命令作为字符串通过控制通道描述符发送给FTP服务器,GetReply()是得到返回信息,比如220 "Welcome to FOOBAR FTP service."。就这样一个简单的控制通道就完成了,我们只要知道IP和用户名、密码就可以成功登陆FTP服务器了。
二、数据通道
数据通道是FTP客户端和FTP服务器之间建立一条专用传输数据通道,有主动模式(PORT)和被动模式(PASV)之分。
1、主动模式(PORT)表示客户端选择合适的端口告诉FTP服务器,让FTP服务器通过这个端口主动连接客户端,由此建立数据通道。思路是:由系统自己选择端口,使用PORT addr1,addr2,addr3,addr4,port1,port2 命令通过控制通道告诉FTP服务器主动模式连接,然后建立监听。
int CreatePortConnect()
{
struct sockaddr_in seraddr,tempaddr;
int fd,len;
char *port,*addr,bufs[1024];
memset(&seraddr,0x00,sizeof(seraddr));
memset(&tempaddr,0x00,sizeof(tempaddr));
memset(bufs,0x00,sizeof(bufs));
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
printf("socket error=[%s]/n",strerror(errno));
return -1;
}
seraddr.sin_family=AF_INET;
seraddr.sin_addr.s_addr=htonl(INADDR_ANY);
seraddr.sin_port=htons(0);//由系统自己选择端口
if(bind(fd,(struct sockaddr *)&seraddr,sizeof(seraddr))<0)
{
printf("bind error=[%s]/n",strerror(errno));
return -1;
}
len=sizeof(seraddr);
if(getsockname(fd,(struct sockaddr *)&seraddr,(unsigned long*)&len)<0)
{
printf("getsockname fd %s/n",strerror(errno));
return -1;
}
port=(char *)&seraddr.sin_port;
len=sizeof(tempaddr);
if(getsockname(controlfd,(struct sockaddr *)&tempaddr,(unsigned long*)&len)<0)
{
printf("getsockname controlfd %s/n",strerror(errno));
return -1;
}
addr=(char *)&tempaddr.sin_addr;
sprintf(bufs,"PORT %d,%d,%d,%d,%d,%d/r/n",addr[0]&(0xff),addr[1]&(0xff),addr[2]&(0xff),addr[3]&(0xff),port[0]&(0xff),port[1]&(0xff));
if((ret=write(controlfd,bufs,strlen(bufs)))<0)
{
printf("sendcontrol error=[%s]/n",strerror(errno));
return -1;
}
GetReply();
if(listen(fd,1)<0)
return -1;
listenfd=fd;
return 0;
}
此处只是通知FTP服务器使用主动模式建立数据通道,在建立监听后,FTP客户端就等着FTP服务器来主动连接啦。当然服务器主动来连接肯定得有数据要使用这个通道传送给客户端。所以我们在accept之前要先送LIST、RETR、STOR等命令,然后建立数据连接来收数据。后面的建立数据连接我就不再写出来了,这个很简单的。
2、被动模式(PASV)是FTP客户端通过控制连接发送PASV命令,告诉服务器使用被动模式。然后服务器返回通知客户端可以连接的IP和端口,FTP客户端通过告诉的IP、端口连接服务器端来建立数据通道。其实PASV这个命令返回的信息和PORT命令发送的参数一样,都是addr1,addr2,addr3,addr4,port1,port2.即将点分制的IP192.168.0.12和端口4777转换成192,168,0,12,(4777/256),(4777%256)。
int CreatePasvConnect()
{
char buf[1024],getBuf[1024];
int iRetCode,done=0,port,fd,len;
struct sockaddr_in seraddr;
sprintf(buf,"PASV /r/n");
if((ret=write(controlfd,buf,strlen(buf)))<0)
{
printf("sendcontrol error=[%s]/n",strerror(errno));
return -1;
}
while(!done ) {
iRetCode = GetLine();
(void)strtok(szBuffer,"/r/n");
if( szBuffer[3] != '-' && iRetCode > 0 )
{
printf("szBuffer=[%s],iRetCode=[%d]/n",szBuffer,iRetCode);
done = 1;
}
}
sprintf(getBuf,"%s",szBuffer);
port=Parse(getBuf);//解析返回的信息,得到端口
fd=socket(AF_INET,SOCK_STREAM,0);
if(fd<0)
{
printf("socket error=[%s]/n",strerror(errno));
return -1;
}
len=sizeof(seraddr);
if(getsockname(controlfd,(struct sockaddr *)&seraddr,(unsigned long*)&len)<0)
{
printf("getsockname error=[%s]/n",strerror(errno));
return -1;
}
seraddr.sin_port=htons(port);
if(connect(fd,(struct sockaddr *)&seraddr,sizeof(seraddr))<0)
{
printf("connect error=[%s]/n",strerror(errno));
return -1;
}
datafd=fd;
return 0;
}
主动模式和被动模式不同之处在于主动模式是服务器端使用客户端指定的端口来主动连接客户端,而被动模式是客户端使用服务器端指定的端口来连接服务器端。
由此简单的FTP客户端实现了,我们可以选择使用主动模式还是被动模式。
以下是我实现的简单示例,登陆FTP服务器,发送LIST命令得到目录列表
1、使用被动模式
2、使用主动模式