FTP为File Transfer Protocol的缩写,即文件传输协议,是TCP/IP 协议族中的协议之一。FTP是一个用于在计算机网络上在客户端和服务器之间进行文件传输的应用层协议。RFC 959定义了FTP协议规范。是互联网上使用的最广泛的文件传输协议,用于Internet上的控制文件的双向传输。FTP提供交互式的访问,允许客户指明文件类型与格式,并允许文件具有存取权限。FTP屏蔽了各计算机系统的细节,因而适合于在异构网络中计算机之间传送文件。
1、文件传送协议FTP只提供文件传送的一些基本的服务,它使用TCP可靠的运输方式。
2、FTP的主要功能是减少或消除在不同操作系统下处理文件的不兼容性。
3、FTP使用客户服务器方式。一个FTP服务器进程可同时为多个客户进程提供服务,FTP的服务器进程由二大部分组成:一个是主进程,负责接受新的请求;另外有诺干个从属进程,负责处理单个请求。
为了更透彻的理解和掌握FTP,在叙述FTP协议内容的同时,以“实战 ” 的方式抓取FTP客户端和FTP服务端之间交互的网络包,以此和FTP协议相互印证,加深理解。本节主要介绍抓取FTP网络包的环境搭建。实验环境将以linux系统虚拟机的一个编译界面作为FTP客户端,以linux系统虚拟机的另一个编译界面作为FTP服务器。
函数原型
int socket(int domain,int type, int protocol);
参数解读
常用的domain类型有:
常用的socket类型有:
SOCK_STREAM
TCP:流式套接字提供可靠的、面向连接的通信流:它使用TCP协议,从而保证了数据传输的正确性和顺序性。
SOCK_DGRAM
UDP:数据报套接字定义了一种无连按的服,数据通过相互独立的报文进行传输, 是无序的,并且不保证是可靠,无差错的。它使用数据报协议UDP。
SOCK_RAW
允许程序使用低层协议,原始科接字允许对底层协议如IP或ICMP进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发。
0
表示选择type类型对应得默认协议。
IPPROTO_TCP
——TCP传输协议IPPTOTO_UDP
——UDP传输协议IPPROTO_SCTP
——STCP传输协议IPPROTO_TIPC
——TIPC传输协议返回值
成功时返回新的套接字描述符,失败返回 -1。
函数原型
int atoi(const char *nptr);
参数解读
const char *nptr:需要进行转换的字符串
返回值
返回转换之后得到的int型数据。
函数原型
uint16_t htons(uint16_t hostshort);
参数解读
uint16_t hostshort:16位无符号整数。
返回值
转换之后的TCP/IP网络字节序。
函数原型
int inet_aton(const char *cp, struct in_addr *inp);
参数解读
返回值
调用成功返回非0,调用失败返回0。
函数原型
char *inet_ntoa(struct in_addr in);
参数解读
struct in_addr in:sockaddr_in类型结构体里的in_addr类型结构体
返回值
指向字符串类型IP地址的字符串指针。
函数原型
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数解读
返回值
调用成功返回0,调用失败返回-1。
函数原型
int listen(int sockfd, int backlog);
参数解读
返回值
调用成功返回0,调用失败返回-1。
函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数解读
返回值
调用成功返回0,调用失败返回-1。
函数原型
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
参数解读
返回值
调用成功返回套接字描述符,调用失败返回-1。
函数原型
pid_t fork(void);
fork函数调用成功,返回两次PID
(1)返回值为0,代表当前进程是子进程
(2)返回值为非负数,代表当前进程是父进程
(3)调用失败,则返回-1
函数原型
int open(const char *pathname, int flags, mode_t mode);
参数解读
返回值
调用成功返回一个文件描述符,调用失败返回-1。
函数原型
int write(int fd, const void *buf, size_t count);
参数解读
返回值
写入成功时,返回写入的字节数,写入数据为空返回0,写入失败返回-1。
函数原型
int lseek(int fd, off_t offset, int whence);
参数解读
返回值
调用成功返回光标偏移字节,调用失败返回-1。
int close(int fd)
参数解读
fd:文件描述符
返回值
调用成功返回0,调用失败时返回-1。
函数原型
char *strtok(char *str, const char *delim);
参数解读
返回值
第一次传入字符串和分隔符时,返回分割的字符串
第二次传入NULL和分隔符时,返回分隔的字符串
直到最后一次传入NULL和空字符串时,返回最后一个分割的字符串
其中第一次字符串被分割后,值和大小为第一次分割出来的字符串
函数原型
int strcmp(const char *s1, const char *s2);
参数解读
返回值
如果两个字符串相等返回0,两个字符串不相等返回其他值。
函数原型
char *strstr(const char *s1, const char *s2);
参数解读
返回值
如果字符串s1包含字符串s2,返回包含字符及后面字符,如果字符串s1不包含字符串s2则返回NULL。
函数原型
char *strcat(char *str1, const char *str2);
参数解读
返回值
返回指向字符串2拼接到字符串1的字符串地址的指针。
函数原型
char *strcpy(char *str1, const char *str2);
参数解读
返回值
返回指向字符串2复制到字符串1尾端的字符串地址的指针。
函数原型
void *memset(void *s, int c, size_t n);
参数解读
返回值
返回一个指向内存s的指针。
函数原型
int system(const char *command);
参数解读
const char *command:命令
返回值
调用/bin/sh失败时返回127,其他失败原因返回-1
const char *command参数为NULL时返回非0值
调用成功返回执行shell命令后的返回值
函数原型
FILE *popen(const char *command, const char *type);
参数:
返回值
调用成功返回文件流,调用失败返回NULL。
函数原型
int fread(void *ptr, size_t size, size_t nmemb,FILE *stream);
参数解读
返回值
读入成功时,返回读取的字节数,读入失败时,返回-1。
函数原型
int fflush(FILE *stream);
参数解读
FILE *stream:文件流
返回值
调用成功返回0,调用失败返回EOF。
函数原型
int chdir(const char *path);
参数解读
const char *path:新的当前工作目录,绝对路径或者相对路径
返回值
调用成功返回0,调用失败返回-1。
函数原型
int access(const char * pathname,int mode);
参数解读
返回值
如果文件具有某种权限返回0,不具有某种权限返回-1。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LS 1
#define CD 2
#define PWD 3
#define PUT 5
#define GET 15
struct Msg
{
int type;
char cmd[50];
char data[5120];
};
int cmd_distinguish(char *cmd)
{
int ret;
cmd = strtok(cmd,"\n"); //由于客户端获取用户输入时,会使用enter键,因此传输到服务器的命令都带有换行符,需要进行命令分割
if(!strcmp("ls",cmd)){
ret = LS;
}else if(strstr(cmd,"cd") != NULL){
ret = CD;
}else if(strstr(cmd,"get") != NULL){
ret = GET;
}else if(strstr(cmd,"put") != NULL){
ret = PUT;
}else if(!strcmp("pwd",cmd)){
ret = PWD;
}else{
ret = 0;
}
return ret;
}
//将带有文件名、路径名的命令进行处理分割
//如命令“get 1.c”,该函数就会返回1.c
char *str_decompose(char *cmd)
{
strtok(cmd," ");
cmd = strtok(NULL," ");
return cmd;
}
void cmd_handle(int ret,struct Msg msg,int c_fd)
{
FILE *fp = NULL;
int n_fread;
char file_path[20] = "./"; //当前路径
switch(ret){
case PWD:
fp = popen("pwd","r"); //调用终端执行pwd命令,并将数据存放到fp文件流中
memset(msg.data,0,sizeof(msg.data));
n_fread = fread(msg.data,1,sizeof(msg.data),fp);//读取文件流的数据
msg.type = 0;
write(c_fd,&msg,sizeof(msg)); //向客户端发送数据
fflush(stdout); //刷新标准输出缓冲区,把输出缓冲区里的东西强制打印到标准输出设备上
break;
case LS:
fp = popen("ls","r");
memset(msg.data,0,sizeof(msg.data));
n_fread = fread(msg.data,1,sizeof(msg.data),fp);
msg.type = 0;
write(c_fd,&msg,sizeof(msg));
fflush(stdout);
break;
case CD:
if(chdir(str_decompose(msg.cmd)) <0){ //修改当前工作目录
perror("chdir");
}
break;
case GET:
strcat(file_path,str_decompose(msg.cmd));
if(access(file_path,F_OK) < 0){ //判断服务器是否拥有客户端想要获取的文件
perror("access");
memset(msg.data,0,sizeof(msg.data));
msg.type = 0;
strcpy(msg.data,"Target file does not exist");
write(c_fd,&msg,sizeof(msg));
}else if(access(file_path,R_OK) < 0){ //判断服务器是否对该文件有读权限
memset(msg.data,0,sizeof(msg.data));
msg.type = 0;
strcpy(msg.data,"The target file does not have read permission");
write(c_fd,&msg,sizeof(msg));
}else{ //获取该文件的内容,发送给客户端
int file_fd = open(file_path,O_RDONLY,0600);
if(file_fd < 0){
perror("open");
}
int file_size = lseek(file_fd,0,SEEK_END);
if(file_size < 0){
perror("lseek");
}
lseek(file_fd,0,SEEK_SET);
memset(msg.data,0,sizeof(msg.data));
msg.type = GET;
if(read(file_fd,msg.data,file_size) < 0){
perror("read");
}
close(file_fd);
write(c_fd,&msg,sizeof(msg));
}
break;
case PUT:
strcat(file_path,str_decompose(msg.cmd));
int file_fd = open(file_path,O_RDWR|O_CREAT,0666); //创建一个文件,写入客户端想要存放的文件的数据
if(file_fd < 0){
perror("open");
}
if(write(file_fd,msg.data,strlen(msg.data)) < 0){
perror("write");
}
close(file_fd);
break;
}
}
int main(int argc,char **argv)
{
struct sockaddr_in so_addr;
struct sockaddr_in cl_addr;
if(argc != 3){
printf("Insufficient or inconsistent parameters\n");
exit(-1);
}
memset(&so_addr,0,sizeof(struct sockaddr_in));
memset(&cl_addr,0,sizeof(struct sockaddr_in));
//1.socket
int s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1){
printf("socket is failusr\n");
perror("socket");
exit(-1);
}
so_addr.sin_family = AF_INET;
so_addr.sin_port = htons(atoi(argv[2])); //将端口号转化成网络字节序
inet_aton(argv[1],&so_addr.sin_addr); //将IP地址转化为网络字节序
//2.bind
int bd = bind(s_fd,(struct sockaddr *)&so_addr,sizeof(struct sockaddr_in));
if(bd == -1){
printf("bind is failusr\n");
perror("bind");
exit(-1);
}
//3.listen
int ln = listen(s_fd,10);
if(ln == -1){
printf("listen is failusr\n");
perror("listen");
exit(-1);
}
int c_fd;
int mark = 0;
int clean = sizeof(struct sockaddr_in);
pid_t pid;
while(1){
//4.accept
c_fd = accept(s_fd,(struct sockaddr *)&cl_addr,&clean);
if(c_fd == -1){
printf("accept is failusr\n");
perror("accept:");
exit(-1);
}
mark++;
printf("get No.%d connect: %s\n",mark,inet_ntoa(cl_addr.sin_addr));
//连接成功后创建子进程接受客户端命令
pid = fork();
if(pid < 0){
perror("fork");
exit(-1);
}
else if(pid == 0){
char *fp = NULL;
struct Msg msg;
int ret = 0;
int c_read;
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
c_read = read(c_fd,&msg,sizeof(msg)); //读取客户端发送的命令
if(c_read <= 0){
perror("read");
break;
}else{
printf("get size is %d's cmd from NO.%d client:%s\n",c_read,mark,msg.cmd);
ret = cmd_distinguish(msg.cmd); //对客户端传送的命令进行识别
cmd_handle(ret,msg,c_fd); //对相关命令进行处理
}
}
}
close(c_fd);
}
close(s_fd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct Msg
{
int type;
char cmd[50];
char data[5120];
};
#define LLS 1
#define LCD 2
#define LPWD 3
#define CD 4
#define LQUIT 7
#define PUT 8
#define CMD 9
#define QUIT 12
#define LS 13
#define PWD 14
#define GET 15
int cmd_distinguish(char *cmd)
{
int ret;
cmd = strtok(cmd,"\n");
if(!strcmp("lls",cmd)){
ret = LLS;
}else if(strstr(cmd,"lcd") != NULL){
ret = LCD;
}else if(!strcmp("lpwd",cmd)){
ret = LPWD;
}else if(!strcmp("ls",cmd)){
ret = LS;
}else if(!strcmp("lquit",cmd)){
ret = LQUIT;
}else if(strstr(cmd,"cd") != NULL){
ret = CD;
}else if(strstr(cmd,"get") != NULL){
ret = GET;
}else if(strstr(cmd,"put") != NULL){
ret = PUT;
}else if(!strcmp("pwd",cmd)){
ret = PWD;
}else{
ret = 0;
}
return ret;
}
char *str_decompose(char *cmd)
{
strtok(cmd," ");
cmd = strtok(NULL," ");
return cmd;
}
int cmd_handle(int ret,struct Msg msg,int c_fd)
{
FILE *fp = NULL;
int n_fread;
char file_path[20] = "./";
char tmp_cmd[50];
switch(ret){
case LPWD:
printf("--------------------------------------\n");
printf("\n");
system("pwd"); //调用终端执行pwd命令,并将数据输出到标准输出上
printf("\n");
printf("--------------------------------------\n");
printf("\n");
break;
case LLS:
printf("--------------------------------------\n");
printf("\n");
system("ls"); //调用终端执行ls命令,并将数据输出到标准输出上
printf("\n");
printf("--------------------------------------\n");
printf("\n");
break;
case LCD:
if(chdir(str_decompose(msg.cmd)) < 0){//修改当前工作目录
perror("chdir");
}
printf("\n");
break;
case LQUIT:
printf("--------------------------------------\n");
printf("\n");
printf("client is quit\n");
printf("\n");
printf("--------------------------------------\n");
printf("\n");
exit(0);//退出当前进程
break;
case LS:
msg.type = 0;
write(c_fd,&msg,sizeof(msg)); //将用户输入发送给服务器,让服务器根据命令进行相应的行为
break;
case PWD:
msg.type = 0;
write(c_fd,&msg,sizeof(msg)); //将用户输入发送给服务器,让服务器根据命令进行相应的行为
break;
case CD:
msg.type = 0;
write(c_fd,&msg,sizeof(msg)); //将用户输入发送给服务器,让服务器根据命令进行相应的行为
break;
case GET:
msg.type = 0;
write(c_fd,&msg,sizeof(msg)); //将用户输入发送给服务器,让服务器根据命令进行相应的行为
break;
case PUT:
memset(tmp_cmd,0,sizeof(tmp_cmd));
strcpy(tmp_cmd,msg.cmd); //put 1.c ->tmp.cmd
strcat(file_path,str_decompose(msg.cmd)); // ./1.c->file_path
memset(msg.cmd,0,sizeof(msg.cmd));
strcpy(msg.cmd,tmp_cmd);
if(access(file_path,F_OK) < 0){ //判断客户端是否拥有想要存放到服务器的文件
printf("--------------------------------------\n");
printf("\n");
printf("Target file does not exist\n");
printf("\n");
printf("--------------------------------------\n");
printf("\n");
}else if(access(file_path,R_OK) < 0){ //判断客户端是否对该文件有读权限
printf("--------------------------------------\n");
printf("\n");
printf("The target file does not have read permission\n");
printf("\n");
printf("--------------------------------------\n");
printf("\n");
}else{ //获取该文件的内容,发送给服务器
int pfile_fd = open(file_path,O_RDONLY,0666);
if(pfile_fd < 0){
perror("open");
}
int file_size = lseek(pfile_fd,0,SEEK_END);
if(file_size < 0){
perror("lseek");
}
lseek(pfile_fd,0,SEEK_SET);
memset(msg.data,0,sizeof(msg.data));
if(read(pfile_fd,msg.data,file_size) < 0){
perror("read");
}
close(pfile_fd);
write(c_fd,&msg,sizeof(msg));
printf("--------------------------------------\n");
printf("\n");
printf("Target file is on server\n");
printf("\n");
printf("--------------------------------------\n");
printf("\n");
}
break;
default:
printf("--------------------------------------\n");
printf("\n");
printf("Input error, please re-enter\n");
printf("\n");
printf("--------------------------------------\n");
printf("\n");
break;
}
return ret;
}
//对服务器发送的数据进行处理
void from_socket_data_handle(struct Msg msg,int c_fd)
{
struct Msg socket_msg;
int n_read;
int file_fd;
char file_path[20] = "./";
n_read = read(c_fd,&socket_msg,sizeof(socket_msg));
if(n_read == -1){
perror("read socket");
exit(-1);
}else if(socket_msg.type == GET){ //如果是GET指令返回的数据,就存进文件中
strcat(file_path,str_decompose(msg.cmd));
file_fd = open(file_path,O_RDWR|O_CREAT,0666);
write(file_fd,socket_msg.data,strlen(socket_msg.data));
fflush(stdout);
close(file_fd);
printf("--------------------------------------\n");
printf("\n");
printf("%s\n","Get target file from server\n");
printf("--------------------------------------\n");
printf("\n");
}else{ //如果是其他数据就直接打印出来
printf("--------------------------------------\n");
printf("\n");
printf("%s\n",socket_msg.data);
printf("\n");
printf("--------------------------------------\n");
printf("\n");
}
}
int main(int argc,char **argv)
{
struct sockaddr_in c_addr;
memset(&c_addr,0,sizeof(struct sockaddr_in));
//1.socket
int c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1){
printf("socket is failusr\n");
perror("socket");
exit(-1);
}
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(atoi(argv[2])); //将端口号转化成网络字节序
inet_aton(argv[1],&c_addr.sin_addr); //将IP地址转化为网络字节序
//2.connect
if(connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)) == -1){
perror("connect");
exit(-1);
}
int n_read;
struct Msg msg;
int ret = 0;
int mark = 0;
int tmp = 0;
while(1){
memset(msg.cmd,0,sizeof(msg.cmd));
memset(msg.data,0,sizeof(msg.data));
fgets(msg.cmd,sizeof(msg.cmd),stdin); //获取用户输入的命令
ret = cmd_distinguish(msg.cmd); //对用户键入的命令进行识别
tmp = cmd_handle(ret,msg,c_fd); //对命令进行处理
if(tmp > CMD){
from_socket_data_handle(msg,c_fd); //对服务器传送的数据进行处理
}
fflush(stdout);
}
close(c_fd);
return 0;
}