1關於ftp
FTP協議包括兩個組成部分,其一為FTP服務器,其二為FTP客戶端。其中FTP服務器用來存儲文件,用戶可以使用FTP客戶端通過FTP協議訪問位於FTP服務器上的資源。
默認情況下FTP協議使用TCP端口中的 20和21這兩個端口,其中20用於傳輸數據,21用於傳輸控制信息。
FTP支持兩種模式,一種方式叫做Standard (也就是 PORT方式,主動方式),一種是 Passive(也就是PASV,被動方式)。 Standard模式 FTP的客戶端發送 PORT 命令到FTP服務器。Passive模式FTP的客戶端發送 PASV命令到 FTP Server。
Port
FTP 客戶端首先和FTP服務器的TCP 21端口建立連接,通過這個通道發送命令,客戶端需要接收數據的時候在這個通道上發送PORT命令。 PORT命令包含了客戶端用什么端口接收數據。在傳送數據的時候,服務器端通過自己的TCP 20端口連接至客戶端的指定端口發送數據。 FTP server必須和客戶端建立一個新的連接用來傳送數據。
Passive
在建立控制通道的時候和Standard模式類似,但建立連接后發送的不是Port命令,而是Pasv命令。FTP服務器收到Pasv命令后,隨機打開一個高端端口(端口號大於1024)並且通知客戶端在這個端口上傳送數據的請求,客戶端連接FTP服務器此端口,然后FTP服務器將通過這個端口進行數據的傳送,這個時候FTP server不再需要建立一個新的和客戶端之間的連接。
很多防火牆在設置的時候都是不允許接受外部發起的連接的,所以許多位於防火牆后或內網的FTP服務器不支持PASV模式,因為客戶端無法穿過防火牆打開FTP服務器的高端端口;而許多內網的客戶端不能用PORT模式登陸FTP服務器,因為從服務器的TCP 20無法和內部網絡的客戶端建立一個新的連接,造成無法工作。
我這次實現簡化了FTP協議過程,原本ftp使用兩個端口分別傳輸數據和控制信息,我只用了一個端口同時完成這兩個功能。實現了一些簡單命令,如ls、download、upload。
2算法流程
client.c:
server.c:
3代碼及注釋
client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <fcntl.h> #define N 256 void commd_help(); void commd_exit(); void commd_ls(struct sockaddr_in, char *); void commd_get(struct sockaddr_in , char *); void commd_put(struct sockaddr_in , char *); int main(int argc, char *argv[]) { char commd[N]; struct sockaddr_in addr; int len; bzero(&addr, sizeof(addr)); //將&addr中的前sizeof(addr)字節置為0,包括'\0' addr.sin_family = AF_INET; //AF_INET代表TCP/IP協議 addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //將點間隔地址轉換為網絡字節順序 addr.sin_port = htons(8989); //轉換為網絡字節順序 len = sizeof(addr); while(1) { printf("ftp>"); bzero(commd,N); //fgets函數從stdin流中讀取N-1個字符放入commd中 if(fgets(commd,N,stdin) == NULL) { printf("Fgets Error!\n"); return -1; } commd[strlen(commd)-1]='\0'; //fgets函數讀取的最后一個字符為換行符,此處將其替換為'\0' printf("Input Command Is [ %s ]\n",commd); if(strncmp(commd,"help",4) == 0) //比較兩個字符串前4個字節,若相等則返回0 { commd_help(); }else if(strncmp(commd, "exit",4) == 0) { commd_exit(); exit(0); //結束進程 }else if(strncmp(commd, "ls" , 2) == 0) { commd_ls(addr, commd); }else if(strncmp(commd, "get" , 3) == 0) { commd_get(addr, commd); }else if(strncmp(commd, "put", 3) ==0 ) { commd_put(addr, commd); }else { printf("Command Is Error!Please Try Again!\n"); } } return 0; } /* **幫助信息 */ void commd_help() { printf("\n=---------------------歡迎使用FTP--------------------------|\n"); printf("| |\n"); printf("| help:顯示所有FTP服務器命令 |\n"); printf("| |\n"); printf("| exit:離開FTP服務器 |\n"); printf("| |\n"); printf("| ls : 顯示FTP服務器的文件列表 |\n"); printf("| |\n"); printf("| get <file>:從FTP服務器下載文件 |\n"); printf("| |\n"); printf("| put <file>:上傳文件到FTP服務器 |\n"); printf("| |\n"); printf("|-----------------------------------------------------------|\n"); return ; } /* **退出FTP服務器 */ void commd_exit() { printf("Bye!\n"); } /* **顯示文件列表 */ void commd_ls(struct sockaddr_in addr, char *commd) { int sockfd; //創建套接字 if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Socket Error!\n"); exit(1); } if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { printf("Connect Error!\n"); exit(1); } //將commd指向的內容寫入到sockfd所指的文件中,此處即指套接字 if(write(sockfd, commd, N) < 0) { printf("Write Error!\n"); exit(1); } while(read(sockfd, commd, N) > 0) //從sockfd中讀取N字節內容放入commd中, { //返回值為讀取的字節數 printf(" %s ",commd); } printf("\n"); close(sockfd); return ; } /* **實現文件的下載 */ void commd_get(struct sockaddr_in addr, char *commd) { int fd; int sockfd; char buffer[N]; int nbytes; //創建套接字,並進行錯誤檢測 if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Socket Error!\n"); exit(1); } //connect函數用於實現客戶端與服務端的連接,此處還進行了錯誤檢測 if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { printf("Connect Error!\n"); exit(1); } //通過write函數向服務端發送數據 if(write(sockfd, commd, N) < 0) { printf("Write Error!At commd_get 1\n"); exit(1); } //利用read函數來接受服務器發來的數據 if(read(sockfd, buffer, N) < 0) { printf("Read Error!At commd_get 1\n"); exit(1); } //用於檢測服務器端文件是否打開成功 if(buffer[0] =='N') { close(sockfd); printf("Can't Open The File!\n"); return ; } //open函數創建一個文件,文件地址為(commd+4),該地址從命令行輸入獲取 if((fd=open(commd+4, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0) { printf("Open Error!\n"); exit(1); } //read函數從套接字中獲取N字節數據放入buffer中,返回值為讀取的字節數 while((nbytes=read(sockfd, buffer, N)) > 0) { //write函數將buffer中的內容讀取出來寫入fd所指向的文件,返回值為實際寫入的字節數 if(write(fd, buffer, nbytes) < 0) { printf("Write Error!At commd_get 2"); } } close(fd); close(sockfd); return ; } /* **實現文件的上傳 */ void commd_put(struct sockaddr_in addr, char *commd) { int fd; int sockfd; char buffer[N]; int nbytes; //創建套接字 if((sockfd=socket(AF_INET, SOCK_STREAM, 0)) < 0) { printf("Socket Error!\n"); exit(1); } //客戶端與服務端連接 if(connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { printf("Connect Error!\n"); exit(1); } //從commd中讀取N字節數據,寫入套接字中 if(write(sockfd, commd, N)<0) { printf("Wrtie Error!At commd_put 1\n"); exit(1); } //open函數從(commd+4)中,讀取文件路徑,以只讀的方式打開 if((fd=open(commd+4, O_RDONLY)) < 0) { printf("Open Error!\n"); exit(1); } //從fd指向的文件中讀取N個字節數據 while((nbytes=read(fd, buffer, N)) > 0) { //從buffer中讀取nbytes字節數據,寫入套接字中 if(write(sockfd, buffer, nbytes) < 0) { printf("Write Error!At commd_put 2"); } } close(fd); close(sockfd); return ; }
server.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <dirent.h> #include <fcntl.h> #define N 256//文件名和命令名最長為256字節 void commd_ls(int); void commd_get(int, char *); void commd_put(int, char *); int main(int arg, char *argv[]) { int ser_sockfd,cli_sockfd; struct sockaddr_in ser_addr,cli_addr; int ser_len, cli_len; char commd [N]; bzero(commd,N);//將commd所指向的字符串的前N個字節置為0,包括'\0' if((ser_sockfd=socket(AF_INET, SOCK_STREAM, 0) ) < 0) { printf("Sokcet Error!\n"); return -1; } bzero(&ser_addr,sizeof(ser_addr)); ser_addr.sin_family = AF_INET; ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);//本地ip地址 ser_addr.sin_port = htons (8989);//轉換成網絡字節 ser_len = sizeof(ser_addr); //將ip地址與套接字綁定 if((bind(ser_sockfd, (struct sockaddr *)&ser_addr, ser_len)) < 0) { printf("Bind Error!\n"); return -1; } //服務器端監聽 if(listen(ser_sockfd, 5) < 0) { printf("Linsten Error!\n"); return -1; } bzero(&cli_addr, sizeof(cli_addr)); ser_len = sizeof(cli_addr); while(1) { printf("server>"); //服務器端接受來自客戶端的連接,返回一個套接字,此套接字為新建的一個,並將客戶端的地址等信息存入cli_addr中 //原來的套接字仍處於監聽中 if((cli_sockfd=accept(ser_sockfd, (struct sockaddr *)&cli_addr, &cli_len)) < 0) { printf("Accept Error!\n"); exit(1); } //由套接字接收數據時,套接字把接收的數據放在套接字緩沖區,再由用戶程序把它們復制到用戶緩沖區,然后由read函數讀取 //write函數同理 if(read(cli_sockfd, commd, N) < 0) //read函數從cli_sockfd中讀取N個字節數據放入commd中 { printf("Read Error!\n"); exit(1); } printf("recvd [ %s ]\n",commd); if(strncmp(commd,"ls",2) == 0) { commd_ls(cli_sockfd); }else if(strncmp(commd,"get", 3) == 0 ) { commd_get(cli_sockfd, commd+4); }else if(strncmp(commd, "put", 3) == 0) { commd_put(cli_sockfd, commd+4); }else { printf("Error!Command Error!\n"); } } return 0; } /* **顯示文件列表 */ void commd_ls(int sockfd) { DIR * mydir =NULL; struct dirent *myitem = NULL; char commd[N] ; bzero(commd, N); //opendir為用來打開參數name 指定的目錄, 並返回DIR*形態的目錄流 //mydir中存有相關目錄的信息 if((mydir=opendir(".")) == NULL) { printf("OpenDir Error!\n"); exit(1); } while((myitem = readdir(mydir)) != NULL)//用來讀取目錄,返回是dirent結構體指針 { if(sprintf(commd, myitem->d_name, N) < 0)//把文件名寫入commd指向的緩沖區 { printf("Sprintf Error!\n"); exit(1); } if(write(sockfd, commd, N) < 0 )//將commd緩沖區的內容發送會client { printf("Write Error!\n"); exit(1); } } closedir(mydir);//關閉目錄流 close(sockfd); return ; } /* **實現文件的下載 */ void commd_get(int sockfd, char *filename) { int fd, nbytes; char buffer[N]; bzero(buffer, N); printf("get filename : [ %s ]\n",filename); if((fd=open(filename, O_RDONLY)) < 0)//以只讀的方式打開client要下載的文件 { printf("Open file Error!\n"); buffer[0]='N'; if(write(sockfd, buffer, N) <0) { printf("Write Error!At commd_get 1\n"); exit(1); } return ; } buffer[0] = 'Y'; //此處標示出文件讀取成功 if(write(sockfd, buffer, N) <0) { printf("Write Error! At commd_get 2!\n"); close(fd); exit(1); } while((nbytes=read(fd, buffer, N)) > 0)//將文件內容讀到buffer中 { if(write(sockfd, buffer, nbytes) < 0)//將buffer發送回client { printf("Write Error! At commd_get 3!\n"); close(fd); exit(1); } } close(fd); close(sockfd); return ; } /* **實現文件的上傳 */ void commd_put(int sockfd, char *filename) { int fd, nbytes; char buffer[N]; bzero(buffer, N); printf("get filename : [ %s ]\n",filename); if((fd=open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)//以只寫的方式打開文件,若文件存在則清空,若文件不存在則新建文件 { printf("Open file Error!\n"); return ; } while((nbytes=read(sockfd, buffer, N)) > 0)//將client發送的文件寫入buffer { if(write(fd, buffer, nbytes) < 0)//將buffer中的內容寫到文件中 { printf("Write Error! At commd_put 1!\n"); close(fd); exit(1); } } close(fd); close(sockfd); return ; }
效果:
server:
client:
因為我在代碼中將讀取文件的大小設置為不超過256字節,當上傳(或下載)文件大小超過256字節,后面內容則為空。
所以上穿(或下載)一個超過256字節的文件后,查看文件,發現文件部分內容丟失,則可以證明FTP實現成功,文件是從服務器端下載的。