由于工作与学习的需要,笔者用C语言实现了一个简版的基于FTP协议的客户端,实现了诸如ls命令,cd命令,pwd命令,以及新建于删除目录,和上传本体文件/下载文件到本地的功能,功能较为简单,但对理解FTP的机制有不错的帮助。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//重定义socket地址类型
typedef struct sockaddr* SP;
//向服务器端发送指定序列
int _send(int cli_sock,char* buf,size_t buf_size,\
char* ORDER,size_t ORDER_size)
{
sprintf(buf,"%s",ORDER); //将要发送给服务器的指令写入buf缓冲区
send(cli_sock,buf,strlen(buf),0); //将buf内的指令发送给服务器
}
//接受服务器端反馈信息
int _recive(int cli_sock, char* buf, size_t buf_size,size_t ret,size_t _ret)
{
recv(cli_sock,buf,buf_size,0); //从服务器端接受,接受内存容存入buf
printf("recv:%s\n",buf);
sscanf(buf,"%d",&ret); //buf内的内容写入变量ret
if(ret != _ret)
{
printf("操作失败!请检查命令并重试!\n");
return EXIT_FAILURE;
}
}
//显示当前目录
int _PWD(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
_send(cli_sock,buf,buf_size,"PWD\n",4); //调用发送函数
_recive(cli_sock,buf,buf_size,ret,257); //调用接受函数
}
//进入指定目录
char* _CWD(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
static char path[20] = {};
scanf("%s",path); //定义一个输入缓冲区并接收
sprintf(buf,"CWD %s\n",path); //将输入缓冲区的内容写入buf
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,250);
return path;
}
//创建新目录
int _MKD(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
char path[20] = {}; //原理同上
scanf("%s",path);
sprintf(buf,"MKD %s\n",path);
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,257);
}
//建立数据传输通道
int _PASV(int cli_sock,char* buf,size_t buf_size,size_t ret,\
struct sockaddr_in cli_addr,size_t addrlen)
{
_send(cli_sock,buf,buf_size,"PASV\n",5); //向服务器发送PASV指令并接收返回值
_recive(cli_sock,buf,buf_size,ret,227);
unsigned char ip1,ip2,ip3,ip4,port1,port2;
sscanf(buf,"227 Entering Passive Mode (%hhu,%hhu,%hhu,%hhu,%hhu,%hhu",&ip1,&ip2,&ip3,&ip4,&port1,&port2); //抓取ip信息
char ip[16] = {};
sprintf(ip,"%hhu.%hhu.%hhu.%hhu",ip1,ip2,ip3,ip4);
short port = port1*256+port2; //计算出端口信息
printf("ip=%s port=%hd\n",ip,port);
int cli_pasv = socket(AF_INET,SOCK_STREAM,0); //建立数据传输专用socket
if(0 > cli_pasv)
{
perror("socket");
return EXIT_FAILURE;
}
cli_addr.sin_port = htons(port);
cli_addr.sin_addr.s_addr = inet_addr(ip);
if(connect(cli_pasv,(SP)&cli_addr,addrlen))
{
perror("connect");
return EXIT_FAILURE;
}
return cli_pasv;
}
//查看当前目录详情列表
int _LS(int cli_sock,char* buf,size_t buf_size,size_t ret,\
struct sockaddr_in cli_addr,size_t addrlen)
{
int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen); //建立数据传输通道
_send(cli_sock,buf,buf_size,"LIST -al\n",9); //向服务器发送指令
char buf1[1000000] = {};
recv(cli_pasv,buf1,sizeof(buf1),0);
printf("%s\n",buf1);
close(cli_pasv);
bzero(buf,buf_size);
_recive(cli_sock,buf,buf_size,ret,150);
bzero(buf,buf_size);
_recive(cli_sock,buf,buf_size,ret,226);
}
//删除目录
int _RMD(int cli_sock,char* buf,size_t buf_size,size_t ret,\
struct sockaddr_in cli_addr,size_t addrlen)
{
char* arr = _CWD(cli_sock,buf,buf_size,ret);
_PWD(cli_sock,buf,buf_size,ret);
sprintf(buf,"RMD %s\n",arr);
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,250);
_LS(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
}
//上传
int _STOR(int cli_sock,char* buf,size_t buf_size,size_t ret,\
struct sockaddr_in cli_addr,size_t addrlen)
{
int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
char path[20] = {};
scanf("%s",path);
sprintf(buf,"STOR %s\n",path);
send(cli_sock,buf,strlen(buf),0);
bzero(buf,buf_size);
_recive(cli_sock,buf,buf_size,ret,150);
int fd = open(path,O_RDONLY); //打开本地文件
if(0 > fd)
{
perror("open");
return EXIT_FAILURE;
}
while(ret = read(fd,buf,buf_size)) //边读取边上传至服务器
{
send(cli_pasv,buf,ret,0);
}
close(fd);
close(cli_pasv);
bzero(buf,buf_size);
_recive(cli_sock,buf,buf_size,ret,226);
}
//下载文件
int _RETR(int cli_sock,char* buf,size_t buf_size,size_t ret,\
struct sockaddr_in cli_addr,size_t addrlen)
{
char path[20] = {};
scanf("%s",path);
sprintf(buf,"SIZE %s\n",path);
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,213);
sprintf(buf,"MDTM %s\n",path);
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,213);
int cli_pasv = _PASV(cli_sock,buf,buf_size,ret,cli_addr,addrlen);
sprintf(buf,"RETR %s\n",path);
send(cli_sock,buf,strlen(buf),0);
int fd = open(path,O_RDWR|O_CREAT|O_TRUNC, 0666); //以写和创建的方式打开一个本地文件
if(0 > fd)
{
perror("open");
return EXIT_FAILURE;
}
size_t ret_size = 0;
while(ret_size = recv(cli_pasv,buf,buf_size,0)) //边写入边下载
{
write(fd,buf,ret_size);
}
close(cli_pasv);
bzero(buf,buf_size);
_recive(cli_sock,buf,buf_size,ret,150);
bzero(buf,buf_size);
_recive(cli_sock,buf,buf_size,ret,226);
}
//结束ftp进程
int _QUIT(int cli_sock,char* buf,size_t buf_size,size_t ret)
{
_send(cli_sock,buf,buf_size,"QUIT\n",5);
_recive(cli_sock,buf,buf_size,ret,221);
}
//主函数
int main(int argc,const char* argv[])
{
int cli_sock = socket(AF_INET,SOCK_STREAM,0);
if(0 > cli_sock)
{
perror("socket");
return EXIT_FAILURE;
}
struct sockaddr_in cli_addr = {};
cli_addr.sin_family = AF_INET;
cli_addr.sin_port = htons(21);
cli_addr.sin_addr.s_addr = inet_addr("ip"); //输入你的目标ip
socklen_t addrlen = sizeof(cli_addr);
if(connect(cli_sock,(SP)&cli_addr,addrlen))
{
perror("connect");
return EXIT_FAILURE;
}
char buf[4096] = {};
size_t buf_size = sizeof(buf),ret = 0;
_recive(cli_sock,buf,buf_size,ret,220);
printf("连接服务器成功\n");
char name[20] = {};
printf("请输入用户名:"); //向服务器端发送用户名
scanf("%s",name);
sprintf(buf,"USER %s\n",name);
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,331); //验证用户名是否正确
char pass[20] = {};
printf("请输入密码:"); //向服务器发送密码
scanf("%s",pass);
sprintf(buf,"PASS %s\n",pass);
send(cli_sock,buf,strlen(buf),0);
_recive(cli_sock,buf,buf_size,ret,230); //验证
printf("登录成功!\n");
_PWD(cli_sock,buf,buf_size,ret);
char arr[20] = {};
printf("Remote system type is UNIX.\nUsing binary mode to transfer files.\n");
for(;;)
{
printf("ftp>>>");
scanf("%s",arr);
if(0 == strcmp(arr,"pwd"))
_PWD(cli_sock,buf,buf_size,ret); //pwd命令
if(0 == strcmp(arr,"cd"))
_CWD(cli_sock,buf,buf_size,ret); //cd命令
if(0 == strcmp(arr,"mk"))
_MKD(cli_sock,buf,buf_size,ret); //新建目录命令
if(0 == strcmp(arr,"rm"))
_RMD(cli_sock,buf,buf_size,ret,cli_addr,addrlen); //删除目录命令
if(0 == strcmp(arr,"ls"))
_LS(cli_sock,buf,buf_size,ret,cli_addr,addrlen); //ls命令
if(0 == strcmp(arr,"stor"))
_STOR(cli_sock,buf,buf_size,ret,cli_addr,addrlen); //上传命令
if(0 == strcmp(arr,"retr"))
_RETR(cli_sock,buf,buf_size,ret,cli_addr,addrlen); //下载命令
if(0 == strcmp(arr,"bye"))
{
_QUIT(cli_sock,buf,buf_size,ret); //bye命令
return 0;
}
}
}