FTP客户端搭建(linux环境)
為了從FTP服務器下載文件,需要要實現一個簡單的FTP客戶端。
FTP(文件傳輸協議) 是 TCP/IP 協議組中的應用層協議。
FTP協議使用字符串格式命令字,每條命令都是一行字符串,以“\r\n”結尾。
客戶端發送格式是:命令+空格+參數+"\r\n"的格式
服務器返回格式是以:狀態碼+空格+提示字符串+"\r\n"的格式,代碼只要解析狀態碼就可以了。
讀寫文件需要登陸服務器,特殊用戶名:anonymous,表示匿名。注意大小寫敏感。
從FTP服務器下載文件的基本流程如下:
1. 建立TCP連接,該協議默認使用21端口,當然可以指定其它端口,取決于服務器的配置。
2. 連接成功之后,服務器會發送一行歡迎文字,例如:220 welcome.
? ? 其中左邊的數字220表示就緒狀態,220后面有一個空格,空格后面是提示文字。
? ? 在解析命令應答的時候,只需要獲取前面的數字即可。
3. 收到歡迎信息后,就要開始登陸了,先用USER命令發送用戶名,服務器返回331狀態。
? ? 然后再用PASS命令發送登陸密碼,服務器返回530表示密碼錯誤,返回230表示密碼正確。
? ? 發送:USER anonymous
? ? 接收:331 Anonymous login ok, send your complete email address as your password
? ? 發送:PASS anonymous
? ? 接收:230 Anonymous access granted, restrictions apply
4. 登陸成功之后,再發送一條TYPE I命令,進入二進制模式,這樣獲取文件數據的時候,就會以二進制字節流發送。
? ? 避免以ASCII碼格式發送文件數據。
5. 獲取文件長度
? ?發送:SIZE /path/filename
? ?失敗:550 /path/filename: No such file or directory
? ?成功:213 [filesize]
? ?返回[filesize]是十進制數字,表示該文件在大小,字節為單位
6. 下載文件
? ?下載文件前,先發送PASV命令,進入被動模式,這樣FTP服務器就會開放一個新的端口,用于接收文件數據。
? ?客戶端成功連接到這個數據端口后,再發送RETR命令請求下載文件,這時文件數據就會從新的端口發送過來,文件傳輸完畢,服務器自動關閉數據端口。
? ?發送:PASV
? ?接收:227 Entering Passive Mode (145,24,145,107,207,235).
? ?后面括號內的5個數字,分別表示IP地址和端口號,端口號分為高8位和低8位,高8位在前
? ?這里的例子表示IP地址為145.24.145.107,端口號為53227(計算公式:207 * 256 + 235)。
? ?發送:RETR /path/filename
? ?接收:150 Opening BINARY mode data connection for /path/filename (54 bytes)
? ?>>>從數據端口接收文件數據
? ?接收:226 Transfer complete
?
7. 上傳文件
? 上傳文件與下載文件類似,也是發送PASV命令,獲取到數據端口,然后發送STOR命令請求上傳文件。
? 不同的是上傳文件是往這個數據端口寫文件數據,寫完數據后,客戶端主動斷開與數據端口的TCP連接,服務器會返回一條上傳成功的狀態。
? 發送:PASV
? 接收:227 Entering Passive Mode (145,24,145,107,207,235).
? 發送:STOR /path/filename
? 接收:150 Opening BINARY mode data connection for /path/filename
? ?>>>往數據端口寫數據
? 接收:226 Transfer complete
=======================================
FTP常見的狀態碼如下:
110重新啟動標記答復。
120服務已就緒,在nnn分鐘后開始。
125數據連接已打開,正在開始傳輸。
150文件狀態正常,準備打開數據連接。
2xx-肯定的完成答復一項操作已經成功完成。客戶端可以執行新命令。
200命令確定。
202未執行命令,站點上的命令過多。
211系統狀態,或系統幫助答復。
212目錄狀態。
213文件狀態。
214幫助消息。
215NAME系統類型,其中,NAME是AssignedNumbers文檔中所列的正式系統名稱。
220服務就緒,可以執行新用戶的請求。
221服務關閉控制連接。如果適當,請注銷。
225數據連接打開,沒有進行中的傳輸。
226關閉數據連接。請求的文件操作已成功(例如,傳輸文件或放棄文件)。
227進入被動模式(h1,h2,h3,h4,p1,p2)。
230用戶已登錄,繼續進行。
250請求的文件操作正確,已完成。
257已創建“PATHNAME”。
3xx-肯定的中間答復該命令已成功,但服務器需要更多來自客戶端的信息以完成對請求的處理。
331用戶名正確,需要密碼。
332需要登錄帳戶。
350請求的文件操作正在等待進一步的信息。
4xx-瞬態否定的完成答復該命令不成功,但錯誤是暫時的。如果客戶端重試命令,可能會執行成功。
421服務不可用,正在關閉控制連接。如果服務確定它必須關閉,將向任何命令發送這一應答。
425無法打開數據連接。 426Connectionclosed;transferaborted.
450未執行請求的文件操作。文件不可用(例如,文件繁忙)。
451請求的操作異常終止:正在處理本地錯誤。
452未執行請求的操作。系統存儲空間不夠。
5xx-永久性否定的完成答復該命令不成功,錯誤是永久性的。如果客戶端重試命令,將再次出現同樣的錯誤。
500語法錯誤,命令無法識別。這可能包括諸如命令行太長之類的錯誤。
501在參數中有語法錯誤。
502未執行命令。
503錯誤的命令序列。
504未執行該參數的命令。
530未登錄。
532存儲文件需要帳戶。
550未執行請求的操作。文件不可用(例如,未找到文件,沒有訪問權限)。
551請求的操作異常終止:未知的頁面類型。
552請求的文件操作異常終止:超出存儲分配(對于當前目錄或數據集)。
553未執行請求的操作。不允許的文件名。
?
下面的函數實現了ftp_download和ftp_upload兩個接口,在需要使用FTP功能的地方調用者兩個接口就可以實現FTP通信;
參考程序:
APP_FTP.c
#include "APP_FTP.h"#define COMMAND_LEN 1024 static char m_send_buffer[1024]; static char m_recv_buffer[1024]; pthread_mutex_t mutex_cmd = PTHREAD_MUTEX_INITIALIZER;/*初始化互斥鎖*/struct ftp_property_t {char *download_src; //下載源URLchar *download_desc; //下載目的URLchar *upload_src; //上傳源URLchar *upload_desc; //上傳目的URLchar *ip_addr; //ftp服務端ipint port; //ftp服務端端口char *username; //usernamechar *password; //password; };static struct ftp_property_t ftp_property;static int m_socket_cmd; static int m_socket_data; int socket_create(void) {char buf[512];int sockfd;//打開套接字,得到套接字描述符sockfd = socket(AF_INET, SOCK_STREAM,0);if(sockfd<0){perror("socket error\n");exit(EXIT_FAILURE);}return sockfd; }int socket_connect(int sock, const char *addr, int port) {struct sockaddr_in server_addr = {0};int ret;//調用connect鏈接遠端服務器server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port);inet_pton(AF_INET,addr,&server_addr.sin_addr); //將IP地址從字符串格式轉換成網絡地址格式//鏈接ret = connect(sock,(struct sockaddr *)&server_addr,sizeof(server_addr));if(ret < 0){perror("connect error");//close(sock);return ret;}printf("[FTP]服務器鏈接成功!\n\n"); }void socket_close(int sock) {pthread_mutex_lock(&mutex_cmd);close(sock);pthread_mutex_unlock(&mutex_cmd); }int socket_send(int sock, void *data, int len) {int ret;pthread_mutex_lock(&mutex_cmd);ret = send(sock,data,len,0);pthread_mutex_unlock(&mutex_cmd);if(ret<0){perror("send error");}return ret; }int socket_recv(int sock, void *data, int len) {int ret;//讀取數據pthread_mutex_lock(&mutex_cmd);ret = recv(sock,data,len,0);pthread_mutex_unlock(&mutex_cmd);if(ret<0){perror("recv error\n");close(sock);}return ret; }//命令端口,發送命令 static int ftp_send_command(char *cmd) {int ret;char buf[1024];memset(buf,'\0',1024);memcpy(buf,cmd,strlen(cmd));printf("[FTP]send command: %s\r\n", buf);ret = socket_send(m_socket_cmd, buf, (int)strlen(buf));if(ret < 0){printf("[FTP]failed to send command: %s",buf);return 0;}return 1; }//命令端口,接收應答 static int ftp_recv_respond(char *resp, int len) {int ret;int off;len -= 1;memset(resp,'\0',len);for(off=0; off<len; ){printf("%d ",off);ret = socket_recv(m_socket_cmd, &resp[off], 1);if(ret < 0){printf("[FTP]recv respond error(ret=%d)!\r\n", ret);return 0;}off+=ret;if(resp[off-1] == '\n'){break;}}resp[off+1] = 0;printf("[FTP]respond:%s", resp);return atoi(resp); }//設置FTP服務器為被動模式,并解析數據端口 static int ftp_enter_pasv(char *ipaddr, int *port) {int ret;char *find;int a=0,b=0,c=0,d=0;int pa=0,pb=0;ret = ftp_send_command("PASV\r\n");if(ret != 1){return 0;}ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret != 227){return 0;}//memcpy(m_recv_buffer,"12345(6)",sizeof("12345(6)"));find = strrchr(m_recv_buffer, '(');sscanf(find, "(%d,%d,%d,%d,%d,%d)", &a, &b, &c, &d, &pa, &pb);sprintf(ipaddr, "%d.%d.%d.%d", a, b, c, d);*port = pa * 256 + pb;return 1; }//上傳文件 int _ftp_upload(char *src, char *des) {int ret,i;char ipaddr[32];int port;char buf[102400] = {0};FILE *fp = NULL;//查詢數據地址ret=ftp_enter_pasv(ipaddr, &port);if(ret != 1){return 0;}ret=socket_connect(m_socket_data, ipaddr, port);if(ret < 0){return 0;}//準備上傳sprintf(m_send_buffer, "STOR %s\r\n", des);ret = ftp_send_command(m_send_buffer);if(ret != 1){return 0;}ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret != 150){socket_close(m_socket_data);return 0;}//讀取文件fp = fopen(src,"ab+");if(fp == NULL){perror("Open error");return 0;}//獲取文件大小struct stat statbuf;stat(src, &statbuf);int len = statbuf.st_size;for(i=0; i<len; ){//開始上傳memset(buf,'\0', sizeof(buf));//ret = fread(buf,1,10240,fp);ret = fread(buf,1,sizeof(buf),fp);ret = socket_send(m_socket_data, buf, ret);if(ret < 0){ printf("[FTP]send data error:%d!\r\n",ret);socket_close(m_socket_data);return 0;}i+=ret;printf("[FTP]upload: %d/%d.\r\n",i,len);if(feof(fp)){break;}}socket_close(m_socket_data);fclose(fp);//上傳完成,等待回應ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret == 226){printf("[FTP]upload complete,save to%s!\r\n",des);}return (ret==226); }//下載文件 int _ftp_download(char *src,char *des ,int len) {int i;int ret;char ipaddr[32];int port;char buf[102400];FILE *fp = NULL;//查詢數據地址ret = ftp_enter_pasv(ipaddr, &port);if(ret != 1){return 0;}//連接數據端口ret = socket_connect(m_socket_data, ipaddr, port);if(ret < 0){printf("[FTP]failed to connect data port\r\n");return 0;}//準備下載sprintf(m_send_buffer, "RETR %s\r\n", src);ret = ftp_send_command(m_send_buffer);if(ret != 1){return 0;}ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret != 150){socket_close(m_socket_data);return 0;}fp = fopen(des,"ab+");if(fp == NULL){perror("Open error");return 0;}//開始下載,讀取完數據后,服務器會自動關閉連接for(i=0; i<len; i+=ret){memset(buf,'\0',sizeof(buf));//ret = socket_recv(m_socket_data, ((char *)buf) + i, len);ret = socket_recv(m_socket_data, buf, sizeof(buf));printf("[FTP]download %d/%d.\r\n", i + ret, len);if(ret < 0){printf("[FTP]download was interrupted.\r\n");break;}if(fwrite(buf,1,ret,fp) < ret){printf("[FTP]fwrite error.\r\n");fclose(fp);return 0;};}socket_close(m_socket_data);fclose(fp);ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret == 226){//下載完成printf("[FTP]download %d/%d bytes complete,Save to %s.\r\n", i, len,des);}else{printf("[FTP]download falied.\r\n");return 0;}return (ret==226); }//返回文件大小 int ftp_filesize(char *name) {int ret;int size;memset(m_send_buffer, '\0', sizeof(m_send_buffer));sprintf(m_send_buffer,"SIZE %s\r\n",name);ret = ftp_send_command(m_send_buffer);if(ret != 1){return 0;}ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret != 213){return 0;}size = atoi(m_recv_buffer + 4);return size; }//登陸服務器 int ftp_login(char *addr, int port, char *username, char *password) {int ret,i;printf("[FTP]connect...\r\n");//3次鏈接服務器不成功就取消連接for ( i = 0; i < 3; i++){ret = socket_connect(m_socket_cmd, addr, port);if(ret >= 0){break;}else if(ret < 0 && i < 3 ){sleep(3);printf("[FTP]Trying connect again...\r\n");}else{printf("[FTP]connect server failed!\r\n");return 0;}}printf("[FTP]connect ok.\r\n");//等待歡迎信息ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);//1024if(ret != 220){printf("[FTP]bad server, ret=%d!\r\n", ret);socket_close(m_socket_cmd);return 0;}printf("[FTP]login...\r\n");//發送USERsprintf(m_send_buffer, "USER %s\r\n", username);ret = ftp_send_command(m_send_buffer);if(ret != 1){socket_close(m_socket_cmd);return 0;}ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret != 331){socket_close(m_socket_cmd);return 0;}//發送PASSsprintf(m_send_buffer, "PASS %s\r\n", password);ret = ftp_send_command(m_send_buffer);if(ret != 1){socket_close(m_socket_cmd);return 0;}ret = ftp_recv_respond(m_recv_buffer, 24);if(ret != 230){socket_close(m_socket_cmd);return 0;}printf("[FTP]login success.\r\n");//設置為二進制模式ret = ftp_send_command("TYPE I\r\n");if(ret != 1){socket_close(m_socket_cmd);return 0;}ret = ftp_recv_respond(m_recv_buffer, COMMAND_LEN);if(ret != 200){socket_close(m_socket_cmd);return 0;}return 1;}void ftp_quit(void) {ftp_send_command("QUIT\r\n");socket_close(m_socket_cmd); }void ftp_init(void) {m_socket_cmd = socket_create();m_socket_data= socket_create(); }/*** @brief Function:API,從ftp服務器下載文件* @param src:FTP服務器URL* @param des:本地的保存位置-* @param Host:FTP服務器IP地址 ** @param Port:FTP服務器端口 * @param username:FTP服務器賬戶* @param password:FTP服務器密碼*/ int ftp_download(char *src, char *des,char *Host,int Port,char *username,char *password) {memset(&ftp_property,0,sizeof(ftp_property));ftp_property.download_src = src;ftp_property.download_desc = des;ftp_property.ip_addr = Host;ftp_property.username = username;ftp_property.password = password;ftp_property.port = Port;printf("[FTP]ftp_download:\r\n[src:%s]\r\n[des:%s]\r\n[IPAddr:%s]\r\n[Port:%u]\r\n[username:%s]\r\n[password:%s]\r\n", \ftp_property.download_src,ftp_property.download_desc,ftp_property.ip_addr,ftp_property.port,ftp_property.username,ftp_property.password);int ret = 0;ftp_init();ret = ftp_login(ftp_property.ip_addr, ftp_property.port, ftp_property.username,ftp_property.password);if(ret == 0){ftp_quit();return;}int size = ftp_filesize(ftp_property.download_src);if(ret == 0){ftp_quit();return;}ret = _ftp_download(ftp_property.download_src,ftp_property.download_desc, size);if(ret == 0){ftp_quit();return;}ftp_quit(); /*if(pthread_create(&Ftp_download_thread, NULL, ftp_download_thread, NULL) != 0){ perror("線程\"socket_net_dila\"創建失敗!\r\n");}*/ }/*** @brief Function:API,向ftp服務器上傳文件* @param src:本地URL* @param des:服務器保存位置* @param Host:FTP服務器IP地址 ** @param Port:FTP服務器端口 * @param username:FTP服務器賬戶* @param password:FTP服務器密碼*/ int ftp_upload(char *src, char *des,char *Host,int Port,char *username,char *password) {int ret = 0;memset(&ftp_property,0,sizeof(ftp_property));ftp_property.upload_src = src;ftp_property.upload_desc = des;ftp_property.ip_addr = Host;ftp_property.username = username;ftp_property.password = password;ftp_property.port = Port;printf("[FTP]ftp_upload:\r\n[src:%s]\r\n[des:%s]\r\n[IPAddr:%s]\r\n[Port:%u]\r\n[username:%s]\r\n[password:%s]\r\n", \ftp_property.upload_src,ftp_property.upload_desc,ftp_property.ip_addr,ftp_property.port,ftp_property.username,ftp_property.password);ftp_init();ret = ftp_login(ftp_property.ip_addr, ftp_property.port, ftp_property.username,ftp_property.password);if(ret == 0)return;ret = _ftp_upload(ftp_property.upload_src,ftp_property.upload_desc);if(ret == 0)return;ftp_quit();//if(pthread_create(&Ftp_upload_thread, NULL, ftp_upload_thread, NULL) != 0){ // perror("線程\"socket_net_dila\"創建失敗!\r\n");//} }void main(void) {return 0; }APP_FTP.h
#ifndef __APP_THREAD_FTP__ #define __APP_THREAD_FTP__#include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <sys/socket.h> #include <linux/if.h> #include <fcntl.h> #include <arpa/inet.h> int ftp_download(char *src, char *des,char *Host,int Port,char *username,char *password); int ftp_upload(char *src, char *des,char *Host,int Port,char *username,char *password); #endif // !__APP_THREAD_FTP__文章參考了(60條消息) C語言實現的簡易FTP客戶端_星沉地動的博客-CSDN博客_c語言實現ftp
在他的基礎上對接口進行了進一步封裝和優化
總結
以上是生活随笔為你收集整理的FTP客户端搭建(linux环境)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 计算机三级会保研加分吗,366所高校有保
- 下一篇: VMware vCenter Serve