您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“C++如何實現簡單FTP客戶端軟件開發”,內容詳細,步驟清晰,細節處理妥當,希望這篇“C++如何實現簡單FTP客戶端軟件開發”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
簡單FTP客戶端軟件開發(100分)網絡環境中的一項基本應用就是將文件從一臺計算機中復制到另一臺可能相距很遠的計算機中。而文件傳送協議FTP是因特網上使用得最廣泛的文件傳送協議。FTP使用客戶服務器方式。
1)以命令行形式運行
2)該FTP客戶端程序具有以下基本功能:能完成FTP鏈接的打開和關閉操作;能執行文件的上傳和下載;能完成目錄的創建、刪除等有關操作。
3)設計中加入新的功能:登錄,進入上級或下級目錄
要求可以傳輸簡單的文本文件。
1、分為兩個端口,指令交流端口,數據交換端口
2、指令端口連接后一直保持通信,直到獲得退出信號
3、數據交換端口在獲得相關指令后重新建立連接
1、分別創建兩個C++項目
2、進入即可
3、若兩個都在本地,使用本地回環測試IP,127.0.0.1
4、登錄的部分在客戶端中被注釋掉了沒有開啟
Tips:
目前登錄以及help部分仍有兩個小bug,系由端口的命令斷開以及連接部分引起,交由讀者自己研究,若不想管可以不使用這兩個功能
服務端——>Server
#include "Winsock2.h" #include "windows.h" #include <iostream> #include <string> using namespace std; #define RECV_PORT 3312 //接收端口 #define SEND_PORT 4302 //發送端口 #define DATA_PORT 3313 //數據發送端口 #pragma comment(lib, "wsock32.lib") SOCKET sockClient, sockServer; SOCKET dataClient, dataServer; sockaddr_in dataAddr; sockaddr_in severAddr;//服務器地址 sockaddr_in ClientAddr;//客戶端地址 sockaddr_in dataClientAddr;//數據地址 int addrLen; //地址長度 char fileName[20]; //文件名 char order[20]; //命令 char rbuff[1024]; //接收緩沖區 char sbuff[1024]; //發送緩沖區 char namePassword[1024] = "user 123456"; //用戶名和密碼 //***************函數聲明*************** DWORD startSock(); DWORD createSocket(); int sendFileRecord(SOCKET datatcps, WIN32_FIND_DATA *pfd); int sendFileList(SOCKET datatcps); int sendFile(SOCKET datatcps, FILE* file); DWORD connectProcess(); //***************函數聲明*************** DWORD startSock() {//初始化winsock WSADATA WSAData; if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) { cout << "初始化失敗" << endl; return -1; } return 1; } DWORD createSocket() { sockClient = socket(AF_INET, SOCK_STREAM, 0); dataClient = socket(AF_INET, SOCK_STREAM, 0); if (sockClient == SOCKET_ERROR||dataClient == SOCKET_ERROR) { cout << "創建失敗" << endl; WSACleanup(); return -1; } dataAddr.sin_family = AF_INET; severAddr.sin_family = AF_INET; dataAddr.sin_addr.s_addr = htonl(INADDR_ANY);//監聽任意地址 dataAddr.sin_port = htons(DATA_PORT); severAddr.sin_addr.s_addr = htonl(INADDR_ANY);//監聽任意地址 //cout<<htonl(INADDR_ANY)<<endl; severAddr.sin_port = htons(RECV_PORT); if (bind(sockClient, (struct sockaddr FAR*)&severAddr, sizeof(severAddr)) == SOCKET_ERROR||bind(dataClient, (struct sockaddr FAR*)&dataAddr, sizeof(dataAddr)) == SOCKET_ERROR) { //bind函數用于將socket和地址結構綁定== cout << "綁定失敗" << endl; return -1; } return 1; } DWORD mkdir(char fileName[]){ char path[1000]; GetCurrentDirectory(sizeof(path), path);//找到當前進程的當前目錄 strcat(path,"\\"); strcat(path,fileName); cout<<path<<endl; bool flag = CreateDirectory(path,NULL); if (flag) cout<<"創建文件:"<<fileName<<"成功"<<endl; else cout<<"創建文件:"<<fileName<<"失敗"<<endl; } DWORD delFile(char fileName[]){ char path[1000]; GetCurrentDirectory(sizeof(path), path);//找到當前進程的當前目錄 strcat(path,"\\"); strcat(path,fileName); // cout<<path<<endl; bool flag = RemoveDirectory(path); if (flag) cout<<"刪除文件:"<<fileName<<"成功"<<endl; else cout<<"刪除文件:"<<fileName<<"失敗"<<endl; } DWORD connectProcess() { addrLen = sizeof(ClientAddr);//addrLen是對象地址的長度 if (listen(sockClient, 10) < 0|| listen(dataClient, 10)<0) {//讓套接字進入被動監聽狀態,參數2為請求隊列的最大長度 cout << "監聽失敗" << endl; return -1; } cout << "服務器正在監聽中…" << endl; while (1) { //accept取出隊列頭部的連接請求 //sockclient是處于監聽的套接字 //ClientAddr 是監聽的對象地址 sockServer = accept(sockClient, (struct sockaddr FAR*)&ClientAddr, &addrLen); while (1) { memset(rbuff, 0, sizeof(rbuff)); memset(sbuff, 0, sizeof(sbuff)); if (recv(sockServer, rbuff, sizeof(rbuff), 0) <= 0) { continue; } cout << endl << "獲取并執行的命令:" << rbuff << endl; if (strncmp(rbuff, "get", 3) == 0) {//將文件發給客戶端 strcpy(fileName, rbuff + 4); FILE* file;//定義一個文件訪問指針 //處理下載文件請求 file = fopen(fileName, "rb");//二進制打開文件,只允許讀 if (file) { sprintf(sbuff, "get %s", fileName); if (!send(sockServer, sbuff, sizeof(sbuff), 0)) { cout<<"發送失敗"<<endl; fclose(file); return 0; } else {//創建額外數據連接傳送數據 if (!sendFile(dataClient, file)) { return 0; } fclose(file); } }else { strcpy(sbuff, "無法打開文件\n"); cout<<"無法打開文件"<<endl; if (!send(sockServer, sbuff, sizeof(sbuff), 0)) { return 0; } } }//get else if (strncmp(rbuff, "put", 3) == 0) {//從客戶端上傳上來文件 cout<<"debug1"<<endl; FILE* fd; int cnt; strcpy(fileName, rbuff + 4); fd = fopen(fileName, "wb"); if (fd == NULL) { cout << "無法打開文件" << fileName << endl; return 0; } sprintf(sbuff, "put %s", fileName); if (!send(sockServer, sbuff, sizeof(sbuff), 0)) { fclose(fd); return 0; } memset(sbuff, '\0', sizeof(rbuff)); int dataAddLen = sizeof (dataClientAddr); dataServer = accept(dataClient, (struct sockaddr FAR*)&dataClientAddr, &dataAddLen); while ((cnt = recv(dataServer, rbuff, sizeof(rbuff), 0)) > 0) { fwrite(rbuff, sizeof(char), cnt, fd);//把cnt個數據長度為char的數據從rbuff輸入到fd指向的文件 } cout << "成功獲得文件" << fileName << endl; closesocket(dataServer); fclose(fd); }//put else if (strncmp(rbuff, "pwd", 3) == 0) { char path[1000]; GetCurrentDirectory(sizeof(path), path);//找到當前進程的當前目錄 strcpy(sbuff, path); send(sockServer, sbuff, sizeof(sbuff), 0); }//pwd else if (strncmp(rbuff, "ls", 2) == 0) { strcpy(sbuff, rbuff); send(sockServer, sbuff, sizeof(sbuff), 0); sendFileList(dataClient); }//dir else if(strncmp(rbuff, "mkdir", 5)==0){ strcpy(fileName, rbuff + 6); strcpy(sbuff,rbuff); send(sockServer, sbuff, sizeof (sbuff),0);//發送回信息 mkdir(fileName); }//mkdir else if(strncmp(rbuff,"del", 3)==0){ strcpy(fileName, rbuff + 4);//獲得要刪的文件名 strcpy(sbuff,rbuff); send(sockServer, sbuff, sizeof (sbuff),0);//發送回信息 delFile(fileName); }//del else if (strncmp(rbuff, "cd", 2) == 0) { strcpy(fileName, rbuff + 3); strcpy(sbuff, rbuff); send(sockServer, sbuff, sizeof(sbuff), 0); char path[1000]; GetCurrentDirectory(sizeof(path), path);//找到當前進程的當前目錄 strcat(path,"\\"); strcat(path,fileName); SetCurrentDirectory(path);//設置當前目錄 }//cd else if (strncmp(rbuff, "user", 4) == 0) { char tbuff[1024]; strcpy(tbuff, rbuff + 5); strcat(tbuff, " "); memset(rbuff, '\0', sizeof(rbuff)); strcpy(sbuff, "成功獲取用戶名\0"); send(sockServer, sbuff, sizeof(sbuff), 0); recv(sockServer, rbuff, sizeof(rbuff), 0); cout << endl << "獲取并執行的命令:" << rbuff << endl; strcat(tbuff, rbuff + 5); if (strcmp(tbuff, namePassword) == 0) {//驗證是否正確并返回數據給客戶端 send(sockServer, "right\0", sizeof(sbuff), 0); }else { send(sockServer, "wrong\0", sizeof(sbuff), 0); } }//user pass } closesocket(sockServer); } } int sendFile(SOCKET datatcps, FILE* file) { int dataAddLen = sizeof (dataClientAddr); dataServer = accept(datatcps, (struct sockaddr FAR*)&dataClientAddr, &dataAddLen); cout << "正在發送文件…" << endl; memset(sbuff, '\0', sizeof(sbuff)); while(1) {//從文件中循環讀取數據并發送至客戶端 int len = fread(sbuff, 1, sizeof(sbuff), file);//把file指針指向的文件中的內容讀取到sbuff中 //cout<<"sbuff內容:"<<sbuff<<endl; if (send(dataServer, sbuff, len, 0) == SOCKET_ERROR) { cout << "連接失敗" << endl; closesocket(dataServer); return 0; } if (len < sizeof(sbuff)) {//文件傳送結束 break; } } closesocket(dataServer); cout << "發送成功" << endl; return 1; } int sendFileList(SOCKET datatcps) { int dataAddLen = sizeof (dataClientAddr); dataServer = accept(datatcps, (struct sockaddr FAR*)&dataClientAddr, &dataAddLen); HANDLE hff; //建立一個線程 WIN32_FIND_DATA fd; //搜索文件 hff = FindFirstFile("*", &fd); //查找文件來把待操作文件的相關屬性讀取到WIN32_FIND_DATA結構中去 if (hff == INVALID_HANDLE_VALUE) { //發生錯誤 const char *errStr = "列出文件列表時發生錯誤\n"; cout << *errStr << endl; if (send(dataServer, errStr, strlen(errStr), 0) == SOCKET_ERROR) { cout << "發送失敗" << endl; } closesocket(dataServer); return 0; } BOOL flag = TRUE; while (flag) {//發送文件信息 if (!sendFileRecord(dataServer, &fd)) { closesocket(dataServer); return 0; } flag = FindNextFile(hff, &fd);//查找下一個文件 } closesocket(dataServer); return 1; } int sendFileRecord(SOCKET datatcps, WIN32_FIND_DATA *pfd) {//發送當前的文件記錄 char fileRecord[MAX_PATH + 32]; FILETIME ft; //文件的建立時間 FileTimeToLocalFileTime(&pfd -> ftLastWriteTime, &ft);//Converts a file time to a local file time. SYSTEMTIME lastWriteTime; FileTimeToSystemTime(&ft, &lastWriteTime); const char *dir = pfd -> dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ? "<DIR>" : " "; sprintf(fileRecord, "%04d-%02d-%02d %02d:%02d %5s %10d %-20s\n", lastWriteTime.wYear, lastWriteTime.wMonth, lastWriteTime.wDay, lastWriteTime.wHour, lastWriteTime.wMinute, dir, pfd -> nFileSizeLow, pfd -> cFileName ); if (send(datatcps, fileRecord, strlen(fileRecord), 0) == SOCKET_ERROR) { //通過datatcps接口發送fileRecord數據,成功返回發送的字節數 cout << "發送失敗" << endl; return 0; } return 1; } int main(){ if (startSock() == -1 || createSocket() == -1 || connectProcess() == -1) { return -1; } return 1; }
客戶端——>Client
#include <Winsock.h> #include <windows.h> #include <time.h> #include <stdio.h> #include <iostream> using namespace std; #define RECV_PORT 3312 //接收端口 #define SEND_PORT 4302 //發送端口 #define DATA_PORT 3313 //數據接受端口 #pragma comment(lib, "wsock32.lib") //加載ws2_32.dll,它是Windows Sockets應用程序接口, 用于支持Internet和網絡應用程序。 SOCKET sockClient; //客戶端對象 sockaddr_in serverAddr; //服務器地址 sockaddr_in dataAddr; SOCKET dataClient; //數據對象 char inputIP[20]; //存儲輸入的服務器IP char fileName[20]; //文件名 char rbuff[1024]; //接收緩沖區 char sbuff[1024]; //發送緩沖區 bool checkFlag = false; //標志是否通過登陸 //***********************函數聲明*********************** DWORD startSock(); //啟動winsock并初始化 DWORD createSocket(SOCKET &mySocket); //創建socket DWORD callServer(SOCKET &mySocket,sockaddr_in addr); //發送連接請求 int command(); //執行命令 void help(); //菜單 void list(SOCKET &sockfd); //列出遠方當前目錄 DWORD sendTCP(char data[]); //發送要執行的命令至服務端 int user(); //上傳用戶名 int pass(); //上傳密碼 int sendFile(SOCKET &datatcps, FILE* file); //put 傳送給遠方一個文件 //***********************函數聲明*********************** //***********************函數定義*********************** DWORD startSock() { //啟動winsock并初始化 WSADATA WSAData; char a[20]; memset(a, 0, sizeof(a)); if (WSAStartup(MAKEWORD(2, 2), &WSAData) != 0) { //加載winsock版本 cout << "sock初始化失敗" << endl; return -1; } if (strncmp(inputIP, a, sizeof(a)) == 0) { cout << "請輸入要連接的服務器IP:"; cin >> inputIP; } //設置地址結構 serverAddr.sin_family = AF_INET; //表明底層是使用的哪種通信協議來遞交數據的,AF_INET表示使用 TCP/IPv4 地址族進行通信 serverAddr.sin_addr.s_addr = inet_addr(inputIP); //指定服務器IP,十進制轉化成二進制IPV4地址 serverAddr.sin_port = htons(RECV_PORT); //設置端口號,htons用于將主機字節序改為網絡字節序 dataAddr.sin_family = AF_INET; dataAddr.sin_addr.s_addr = inet_addr(inputIP); dataAddr.sin_port = htons(DATA_PORT);//數據端口和控制端口不一樣 return 1; } DWORD createSocket(SOCKET &mySocket) { //創建socket //要使用套接字,首先必須調用socket()函數創建一個套接字描述符,就如同操作文件時,首先得調用fopen()函數打開一個文件。 mySocket = socket(AF_INET, SOCK_STREAM, 0);//當scoket函數成功調用時返回一個新的SOCKET(Socket Descriptor) //SOCK_STREAM(流式套接字):Tcp連接,提供序列化的、可靠的、雙向連接的字節流。支持帶外數據傳輸 if (mySocket == SOCKET_ERROR) { cout << "創建socket失敗" << endl; WSACleanup();//終止Ws2_32.dll 的使用 return -1; } return 1; } DWORD callServer(SOCKET &mySocket,sockaddr_in addr) { //發送連接請求 createSocket(mySocket); if (connect(mySocket, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {//connect()創建與指定外部端口的連接 cout << "連接失敗" << endl; memset(inputIP, 0, sizeof(inputIP)); return -1; } return 1; } void help() { //幫助菜單 cout << " ___________________________________________ " << endl << " | FTP幫助菜單 | " << endl << " | 1、get 下載文件 [輸入格式: get 文件名 ] | " << endl << " | 2、put 上傳文件 [輸入格式:put 文件名] | " << endl << " | 3、pwd 顯示當前文件夾的絕對路徑 | " << endl << " | 4、ls 顯示遠方當前目錄的文件 | " << endl << " | 5、mkdir 新建文件夾 [輸入格式:mkdir 文件名] | " << endl << " | 6、del 刪除文件夾 [輸入格式:del 文件名] | " << endl << " | 7、cd 改變遠方當前目錄和路徑 | " << endl << " | 進入下級目錄: cd 路徑名 | " << endl << " | 進入上級目錄: c .. | " << endl << " | 8、? 或者 help 進入幫助菜單 | " << endl << " | 9、quit 退出FTP | " << endl << " |___________________________________________| " << endl; } DWORD sendTCP(char data[]) { //發送要執行的命令至服務端 int length = send(sockClient, data, strlen(data), 0); if (length <= 0) { cout << "發送命令至服務端失敗" << endl; closesocket(sockClient);//當不使用socket()創建的套接字時,應該調用closesocket()函數將它關閉,就如同調用fclose()函數關閉一個文件,用來進行套接字資源的釋放。 WSACleanup(); return -1; } return 1; } int sendFile(SOCKET &datatcps, FILE* file) { //put 傳送給遠方一個文件 callServer(datatcps,dataAddr); cout << "正在傳輸文件…" << endl; memset(sbuff, '\0', sizeof(sbuff)); while (1) { //從文件中循環讀取數據并發送 int len = fread(sbuff, 1, sizeof(sbuff), file); //fread從file文件讀取sizeof(sbuff)長度的數據到sbuff,返回成功讀取的數據個數 if (send(datatcps, sbuff, sizeof(rbuff), 0) == SOCKET_ERROR) { cout << "與客戶端的連接中斷" << endl; closesocket(datatcps); return 0; } if (len < sizeof(sbuff)) { break; } } closesocket(datatcps); cout << "傳輸完成" << endl; return 1; } void list(SOCKET &sockfd) { //列出遠方當前目錄 callServer(sockfd,dataAddr); int nRead; memset(sbuff, '\0', sizeof(sbuff)); while (1) { nRead = recv(sockfd, rbuff, sizeof(rbuff), 0); //recv通過sockClient套接口接受數據存入rbuff緩沖區,返回接收到的字節數 if (nRead == SOCKET_ERROR) { cout << "讀取時發生錯誤" << endl; exit(1); } if (nRead == 0) { //數據讀取結束 break; } cout<<"nRead長度"<<nRead<<endl; //顯示數據 rbuff[nRead] = '\0'; cout << rbuff << endl; } cout<<"讀取結束"<<endl; closesocket(sockfd); } int user() { char operation[10], name[20]; //操作與文件名 char order[30] = "\0"; //輸入的命令 char buff[80]; //用來存儲經過字符串格式化的order cout << "請輸入用戶名指令(user 用戶名):"; strcpy(operation,"user"); cin >> name; strcat(order, operation), strcat(order, " "), strcat(order, name); sprintf(buff, order); sendTCP(buff); //發送指令 recv(sockClient, rbuff, sizeof(rbuff), 0); //接收信息 cout << rbuff << endl; return 1; } int pass() { char operation[10], name[20]; //操作與文件名 char order[30] = "\0"; //輸入的命令 char buff[80]; //用來存儲經過字符串格式化的order cout << "請輸入密碼指令(pass 密碼):" ; strcpy(operation,"pass"); cin >> name; strcat(order, operation), strcat(order, " "), strcat(order, name); sprintf(buff, order); sendTCP(buff); //發送指令 recv(sockClient, rbuff, sizeof(rbuff), 0); //接收信息 cout << rbuff << endl; if (strcmp(rbuff, "wrong") == 0) { return 0; } return 1; } //***********************函數定義*********************** int command(){ char operation[10], name[20]; //操作與文件名 char order[30] = "\0"; //輸入的命令 char buff[80]; //用來存儲經過字符串格式化的order FILE *fd1, *fd2; //File協議主要用于訪問本地計算機中的文件,fd指針指向要訪問的目標文件 int cnt; //發送連接請求成功,初始化數據 memset(operation, 0, sizeof(operation)); memset(name, 0, sizeof(name)); memset(order, 0, sizeof(order)); memset(buff, 0, sizeof(buff)); memset(rbuff, 0, sizeof(rbuff)); memset(sbuff, 0, sizeof(sbuff)); cout << endl << "請輸入要執行的指令: "; cin >> operation; // cout<<"why:"<<operation<<endl; if (strncmp(operation, "get", 3) == 0 || strncmp(operation, "put", 3) == 0 || strncmp(operation, "cd", 2) == 0 || strncmp(operation, "mkdir", 5) == 0 || strncmp(operation, "del", 3) ==0){ ///需要輸入文件名的功能 cin >> name; } else if (strncmp(operation, "quit", 4) == 0) { ///退出功能 cout << "感謝您的使用" << endl; return 1; } else if (strncmp(operation, "?", 1) == 0 || strncmp(operation, "help", 4) == 0) { ///幫助菜單功能 help(); }else if(strncmp(operation,"ls",2)==0|| strncmp(operation,"pwd",3)==0){ } else{ cout<<"非法輸入"<<endl; return 0; } //將指令整合進order,并存放進buff strcat(order, operation), strcat(order, " "), strcat(order, name); sprintf(buff, order); sendTCP(buff); //發送指令 recv(sockClient, rbuff, sizeof(rbuff), 0); //接收信息 cout << rbuff << endl; //pwd功能在這里已經實現 if (strncmp(rbuff, "get", 3) == 0) { ///下載功能 callServer(dataClient,dataAddr); fd1 = fopen(name, "wb"); //用二進制的方式打開文件,wb表示打開或新建一個二進制文件(只允許寫數據) if (fd1 == NULL) { cout << "打開或者新建 " << name << "文件失敗" << endl; return 1; } memset(rbuff, '\0', sizeof(rbuff)); while ((cnt = recv(dataClient, rbuff, sizeof(rbuff), 0)) > 0) { // cout<<"緩沖區"<<rbuff<<endl<<"長度"<<cnt<<endl; fwrite(rbuff, sizeof(rbuff), 1, fd1); //C 庫函數 size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) 把 ptr 所指向的數組中的數據寫入到給定流 stream 中。 } closesocket(dataClient); fclose(fd1); //關閉文件 }//get else if (strncmp(rbuff, "put", 3) == 0) { ///上傳功能 // cout<<"執行"<<endl; strcpy(fileName, rbuff + 4); fd2 = fopen(fileName, "rb"); //打開一個二進制文件,文件必須存在,只允許讀 if (fd2) { //成功打開 // cout<<"debug0"<<endl; if (!sendFile(dataClient, fd2)) { cout << "發送失敗" << endl; return 1; } fclose(fd2); } else { strcpy(sbuff, "無法打開文件\n"); if (!send(sockClient, sbuff, sizeof(sbuff), 0)) { return 1; } } }//put else if (strncmp(rbuff, "ls", 2) == 0) { ///ls功能 list(dataClient); }//dir return 0; } int main() { while (1) { startSock(); //啟動winsock并初始化 if (callServer(sockClient,serverAddr) == -1) { //發送連接請求失敗 continue; } cout << "發送連接請求成功" << endl; // if (checkFlag == false) {//登陸 // if (user() && pass()) { // checkFlag = true; // } // continue; // } checkFlag = true; if(checkFlag){ help(); } while(checkFlag){ if(command()){ break; } } cout<<"命令輸入結束"<<endl; closesocket(sockClient); //關閉連接 WSACleanup(); //釋放Winsock return 0; } } /* 192.168.0.100 user gyc pass 123456 pwd cd Debug get 110.txt */
讀到這里,這篇“C++如何實現簡單FTP客戶端軟件開發”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。