文件傳送是各類計算機網絡都實現的基本功能,文件傳送協議是一種最基本的應用層協議按照客戶/服務器的模式進行工做,提供交互式的訪問,是INTERNET使用最普遍的協議之一。ios
本實驗的目的是,學會利用已有網絡環境設計並實現簡單應用層協議,掌握TCP/IP 網絡應用程序基本的設計方法和實現技巧。web
一、實驗內容數據庫
咱們的計算機網絡實驗環境創建在TCP/IP 網絡體系結構之上。各計算機除了安裝TCP/IP 軟件外,還安裝了TCP/IP 開發系統。實驗室各計算機具有Windows環境中套接字socket 的編程接口功能,可爲用戶提供全網範圍的進程通訊功能。本實驗要求學生利用這些功能,設計和實現一個簡單的文件傳送協議。編程
二、具體要求數組
用socket 編程接口編寫兩個程序,分別爲客戶程序(client.c)和服務器程序(server.c),該程序應能實現下述命令功能:瀏覽器
get:取遠方的一個文件緩存
put:傳給遠方一個文件服務器
pwd:顯示遠主當前目錄網絡
dir:列出遠方當前目錄多線程
cd :改變遠方當前目錄
? :顯示你提供的命令
quit :退出返回
咱們的計算機網絡實驗環境創建在TCP/IP 網絡體系結構之上。各計算機除了安裝TCP/IP 軟件外,還安裝了TCP/IP 開發系統。實驗室各計算機具有Windows環境中套接字socket 的編程接口功能,可爲用戶提供全網範圍的進程通訊功能。本實驗要求利用這些功能,設計和實現一個簡單的文件傳送協議。
用socket 編程接口編寫兩個程序,分別爲客戶程序(client.c)和服務器程序(server.c),該程序應能實現下述命令功能:
get:取遠方的一個文件
put:傳給遠方一個文件
pwd:顯示遠主當前目錄
dir:列出遠方當前目錄
cd :改變遠方當前目錄
? :顯示你提供的命令
quit :退出返回程序
1.服務器端調用socket的accept()方法,等待客戶端聯接。
2.客戶端發送須要從服務端得到的文件夾路徑給服務器端。
3.服務器端根據客戶端發來的路徑遍歷整個路徑下的文件及文件夾,並將須要傳送到文件記錄全路徑記錄到一個String類型的數組中,並將文件的個數發送給客戶端。
4.客戶端接收服務器端發來的文件個數,並向服務器端發送關閉聯接請求,並關閉客戶端鏈接。
5.服務器端收取關閉鏈接請求,將socket關閉,進入傳輸文件循環中。
6.服務器端調用socket的accept()方法,等待客戶端聯接。
7.客戶端根據剛纔取得的要傳輸的文件數,進行循環獲取文件,記得每次取完文件要關閉socket鏈接,這樣就不會將多個文件寫入到同一個文件中。
經過Winsock可實現點對點或廣播通訊程序,實際這二者之間的區別不大,編程時其程序流程所用代碼幾乎相同,不一樣的地方在於目標地址選擇的不一樣。
因爲Winsock的服務是以動態連接庫Winsock DLL形式實現的,所以先調用
一、WSAStartup對Winsock DLL進行初始化
二、應用程序關閉套接字後,還應調用WSACleanup終止對Winsock DLL的使用
TCP協議和IP協議指兩個用在Internet上的網絡協議(或數據傳輸的方法)。它們分別是傳輸控制協議和互連網協議。這兩個協議屬於衆多的TCP/IP 協議組中的一部分。TCP/IP協議組中的協議保證Internet上數據的傳輸,提供了幾乎如今上網所用到的全部服務。
文件傳輸協議是從一個系統向另外一個系統傳遞文件的標準方法。它的目標在RFC 0765中寫得很清楚。FTP的目標是1)促進文件和程序的共享,2)鼓勵間接和含蓄的使用遠程計算機,3)使用戶沒必要面對主機間使用的不一樣的文件存儲系統,4)有效和可靠地傳輸文件。
FTP,儘管用戶能夠直接經過終端來使用,是設計成讓別的程序使用的。FTP文件傳輸應用在客戶/服務環境。請求機器啓動一個FTP客戶端軟件。這就給目標文件服務器發出了一個請求。典型地,這個要求被送到端口21。一個鏈接創建起來後,目標文件服務器必須運行一個FTP服務軟件。
客戶端:
(1)處理help命令
經過void help()函數實現對菜單欄的輸出,提供用戶指引界面。
(2)處理lsl命令
經過調用void ftpclient::showLocalFileList()函數從而對當前客戶端目錄下文件進行顯示。
(3)處理put命令,傳送文件
經過調用void ftpclient::ftpPutFile(const char* fileName)int SendFile(SOCKET datatcps, FILE* file)函數,對制定傳送本地文件,並調用upLoadFile(file)函數對數據進行逐個讀取,並經過socket套接口對文件進行有效傳送。
(4)服務器和客戶端間聯通
經過ftp的構造函數啓動winsock並經過ftpclient的構造函數建立套接字、void ftpclient::ftpconnect(const char * dstAddr, int port)綁定套接字,並經過bool sendCommand(string &bufferSend)發送鏈接指令,服務器處於監聽狀態。從而對服務器與客戶端IP及端口間進行鏈接,從而構成客戶端/服務器模式,實現對文件有效傳送。
(5)主函數
主函數實現對客戶端指令的輸入和辨析,從而調用相應功能函數,完成指定指令。
服務器:
(1)服務器和客戶端間聯通
經過DWORD StartSock()啓動winsock並經過DWORD CreateSocket()建立套接字,經過ftp的構造函數啓動winsock並經過ftpserver的構造函數建立套接字、void ftpserver::ftpconnect(const char * dstAddr, int port)函數綁定套接字,並經過void ftpserver::applicationRun()函數發送鏈接指令,開啓服務器監聽狀態。從而對服務器與客戶端IP及端口間進行鏈接,從而構成客戶端/服務器模式,實現對文件有效傳送。
(2)主函數
調用相應函數,實現與客戶端之間交互。
(3)處理dir命令
經過bool sendFileList(SOCKET & accSocket)函數並調用recv(acceptSocket, buffer, sizeof(buffer), 0),經過acceptSocket套接口接受數據存入buffer緩衝區,並經過數據的返回和讀取命令,從而對當前服務器端目錄下文件進行顯示。
(4)處理pwd命令
經過bool sendPath(SOCKET & accSocket)函數並調用send()函數,對當前服務器端目錄路徑進行顯示。
(5)處理cd命令
經過bool changePath(SOCKET & accSocket, const char* pathName)函數中isPath(accSocket, pathName)函數對從客戶端接收到的字符串地址進行解析並判斷是否爲本地的路徑,若爲本地的路徑則經過SetCurrentDirectory(pathName)函數切換路徑。
(6)處理get命令
經過bool ftpPutFile(SOCKET & accSocket, const char * fileName)函數中先判斷客戶端請求的文件路徑類型,若路徑有效且可讀取到文件則先將文件名發送給客戶端,以後再經過sendFile(accSocket, file)函數將整個文件內容發給客戶端。
--------------ftp.h------------ #pragma once #include <iostream> #include <string> #include <sstream> #include<cstdint> #include<WinSock2.h> #include<WS2tcpip.h> #pragma comment(lib,"ws2_32.lib") #include<Windows.h> using namespace std; class ftp { public: ftp(); ~ftp(); virtual void ftpconnect(const char* dstAddr, int port)=0;//鏈接 private: WSAData wsaData;//WINSOCK協議庫信息 uint16_t versionRequest;//版本 }; ---ftpclient.h------- #pragma once #include "ftp.h" class ftpclient:private ftp { public: ftpclient(); ~ftpclient(); virtual void ftpconnect(const char* dstAddr, int port);//鏈接 void ftpGetFile();//下載操做 void ftpPutFile(const char* fileName);//上傳操做 bool checkCommand(string& command, string ¶m,bool loadFlag);//命令檢查 void readAnswer();//讀取並顯示服務器應答 bool sendCommand(string &bufferSend);//命令發送 void showLocalFileList();//顯示本地路徑 private: SOCKET controlSocket;//客戶端socket sockaddr_in serverAddr;//服務器地址 char command[10];//命令 char receiveBuffer[1024];//接收緩存 bool downFile(FILE* file);//下載文件 int isPath(SOCKET& accSocket, const char * pathName);//判斷是不是路徑 bool upLoadFile(FILE* file);//上傳文件 }; ---------------ftp.cpp----------------------- #include "ftp.h" ftp::ftp() { versionRequest = MAKEWORD(2, 2); if (WSAStartup(versionRequest, &wsaData))//請求WINSOCK服務 { cout << "WINSOCK服務初始化失敗" << endl; } if (versionRequest != wsaData.wVersion) { cerr << "請求WINSOCK服務失敗" << endl; } } ftp::~ftp() { //關閉WSA WSACleanup(); } ------------------------ftpclient.cpp----------------------- #include "ftpclient.h" ftpclient::ftpclient() { controlSocket = socket(AF_INET, SOCK_STREAM, 0); if (controlSocket == SOCKET_ERROR) { cerr << "SERVERSocket申請失敗" << WSAGetLastError() << endl; this->~ftpclient(); } } ftpclient::~ftpclient() { closesocket(this->controlSocket); } void ftpclient::ftpconnect(const char * dstAddr, int port) { this->serverAddr.sin_addr.S_un.S_addr = inet_addr(dstAddr); this->serverAddr.sin_family = AF_INET; this->serverAddr.sin_port = htons(port); if (connect(controlSocket,(sockaddr*)&serverAddr, sizeof(sockaddr)) == SOCKET_ERROR) { cerr << "套接字本地綁定失敗:" << WSAGetLastError() << endl; this->~ftpclient(); } //接收歡迎詞和登陸界面標識 recv(this->controlSocket, this->receiveBuffer, sizeof(this->receiveBuffer), 0); cout << this->receiveBuffer << endl; recv(this->controlSocket, this->receiveBuffer, sizeof(this->receiveBuffer), 0); cout << this->receiveBuffer; } void ftpclient::ftpGetFile() { if (recv(this->controlSocket, this->receiveBuffer, sizeof(this->receiveBuffer), 0) == SOCKET_ERROR) { cerr << "接收下載迴應出錯:" << WSAGetLastError() << endl; this->~ftpclient(); }else if(!strcmp(this->receiveBuffer,"目標是一個路徑")){ cout << this->receiveBuffer << endl; }else if (!strcmp(this->receiveBuffer, "未找到文件")) { cout << this->receiveBuffer << endl; }else {//若是能夠下載的話會返回文件名 FILE *file; file = fopen(this->receiveBuffer, "wb");//二進制寫入模式 if (file == NULL) { cout << "沒法寫入文件" << endl; }else {//開始下載 if (downFile(file)) cout << "下載完成" << endl<<""; fclose(file);//關閉文件流 } } } void ftpclient::ftpPutFile(const char* fileName) { int status = isPath(this->controlSocket,fileName); if (status == 1) { cerr << "選中的目標是一個路徑:" << fileName << endl; } else if (status == 2) {//文件能夠上傳 FILE *file; file = fopen(fileName, "rb");//二進制寫入模式 if (file == NULL) { cout << "沒法讀取文件" << endl; }else {//開始上傳 if (upLoadFile(file)) cout << "上傳完成" << endl << ""; fclose(file);//關閉文件流 } } } bool ftpclient::downFile(FILE * file) { int Size = 0; cout << "正在下載文件:" << this->receiveBuffer << endl; while (1) { Size = recv(this->controlSocket, this->receiveBuffer, sizeof(this->receiveBuffer), 0); if (Size == SOCKET_ERROR) { cerr << "下載文件時出錯:" << WSAGetLastError() << endl; return false; } fwrite(this->receiveBuffer, sizeof(char), Size, file);//寫 if (Size < sizeof(this->receiveBuffer)) break;//若是緩衝區沒有被寫滿說明已經下載完了 } return true; } int ftpclient::isPath(SOCKET & accSocket, const char * pathName) { HANDLE serachFile; WIN32_FIND_DATA fd; string error; serachFile = FindFirstFile(pathName, &fd);//查找全部文件 if (serachFile == INVALID_HANDLE_VALUE) {//查找文件發生錯誤 if (send(accSocket, "找不到目標", 11, 0) == SOCKET_ERROR) { cout << "發送文件錯誤信息時發生錯誤" << endl; return -1;//未找到該文件,且發送迴應錯誤 } else { return 0;//未找到該文件,錯誤信息發送成功 } } else { if (fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) {//判斷是否是一個路徑 return 1;//是路徑 } else { return 2;//不是路徑 } } } bool ftpclient::upLoadFile(FILE * file) { char buffSend[1024]; cout << "正在上傳文件..." << endl; int Size = 1; while (1) { Size = fread(buffSend, 1, 1024, file); if (send(this->controlSocket, buffSend, Size, 0) == SOCKET_ERROR) { cout << "數據傳送鏈接斷開" << endl; return false; } //若是buffSend沒有裝滿,則說明已經讀到文件尾了 if (Size < sizeof(buffSend))break; } return true; } void ftpclient::showLocalFileList() { HANDLE serachFile; WIN32_FIND_DATA fd; serachFile = FindFirstFile("*", &fd);//查找全部文件 if (serachFile == INVALID_HANDLE_VALUE) {//查找文件發生錯誤 cout << "列出文件列表錯誤" << endl; return; } BOOL findMoreFiles = TRUE; while (findMoreFiles) { char fileRecord[MAX_PATH + 32]; FILETIME fileTime;//FILETIME結構持有的64位無符號的文件的日期和時間值 FileTimeToLocalFileTime(&fd.ftLastWriteTime, &fileTime);//讀取最近寫入事件的日期 SYSTEMTIME lastTime; FileTimeToSystemTime(&fileTime, &lastTime);//轉換成本地事件的結構 char *dir = fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY ? "<dir>" : " ";//判斷目標文件是否爲文件夾 sprintf(fileRecord,"%04d-%02d-%02d %02d:%02d %5s%10dB %-20s\n",//右對齊輸出文件名字 lastTime.wYear,//寫入順序未年月日時分,文件是不是路徑標誌,文件大小,文件名 lastTime.wMonth, lastTime.wDay, lastTime.wHour, lastTime.wMinute, dir, fd.nFileSizeLow, fd.cFileName); findMoreFiles = FindNextFile(serachFile, &fd);//得到下一個文件的信息 //睡眠50ms,緩衝發送太快會出現丟包狀況 cout << fileRecord; } cout << ">>"; } bool ftpclient::checkCommand(string & command, string & param, bool loadFlag) { cin >> command; if (loadFlag || (command == "ftp")) {//判斷登陸狀態或者是否是登陸命令 if ((command == "ls")|| (command == "lsl") || (command == "pwd") || (command == "quit") || (command == "help")) {//判斷是否是無參數命令 if (cin.rdbuf()->in_avail() > 1) {//緩衝區內還有數據 cout << "非法的命令輸入" << endl<<">>"; command.clear(); cin.clear();//清空緩衝區 cin.sync(); return false; }else { return true; } }else if ((command == "cd") || (command == "get") || (command == "put") || (command == "ftp")) {//有參數命令 if (cin.rdbuf()->in_avail() <3) {//緩衝區內沒有數據 cout << "非法的命令輸入" << endl<<">>"; command.clear(); cin.clear();//清空流 cin.sync(); return false; }else { cin >> param; return true; } }else { cout << "找不到命令:"<<command << endl<<">>"; cin.clear(); cin.sync(); return false; } }else { cout << "請先登陸FTP服務器" << endl; command.clear(); cin.clear(); cin.sync(); return false; } } void ftpclient::readAnswer() { int Size = 1; while (1) { if (recv(this->controlSocket,this->receiveBuffer,sizeof(this->receiveBuffer), 0) ==SOCKET_ERROR) { cout << "發送命令時發生錯誤" << WSAGetLastError() << endl; break; } if (!strcmp(this->receiveBuffer, ">>")) {//接收完畢 cout <<endl<< this->receiveBuffer; break; } cout << this->receiveBuffer; } } bool ftpclient::sendCommand(string & bufferSend) { if (send(this->controlSocket, bufferSend.c_str(), bufferSend.size() + 1, 0) == SOCKET_ERROR) { cout << "發送命令時發生錯誤" << WSAGetLastError() << endl; return false; } else { return true; } } -------------------------main.cpp--------------------------- #include"ftp.h" #include"ftpclient.h" void help(); int main() { string command; string bufferSend;//發送緩存 string param; help(); ftpclient client; bool loadFlag = false;//登陸標誌 while (1) { //保存輸入的指令 if (client.checkCommand(command, param,loadFlag) == false) {//檢查輸入參數 continue;//若是沒有登陸的話會一直執行到這裏直到登陸爲止。 }else{ //生成完整的包含命令和參數的字符串 bufferSend = command + ' ' + param+'\0'; if (command == "ftp") { //發起鏈接 client.ftpconnect(param.c_str(), 4523); loadFlag = true; }else if (command == "quit") {//退出命令 client.~ftpclient(); break; }else if (command == "help") { help(); cout <<">>"; continue; }else if(command == "lsl"){ client.showLocalFileList(); }else {//非登陸命令或者退出命令且合法的命令,發送至服務端. if (client.sendCommand(bufferSend) == false){ cerr << "執行命令出錯" << WSAGetLastError << endl; }else if ((command =="get")){//若是是下載文件 client.ftpGetFile(); client.readAnswer();//接收剩餘應答信息 }else if((command == "put")){ client.ftpPutFile(param.c_str()); Sleep(30); client.readAnswer();//同上 }else { client.readAnswer();//接收執行命令返回的結果 } } } //清空內容 command.clear(); param.clear(); } std::system("pause"); return 0; } void help() { cout << "-----------------命令列表---------------------------------" << endl << " 'ftp + 服務器ip' ………………………登陸 " << endl << " 'help' ……………………………………幫助列表 " << endl << " 'ls' ………………………………………列出遠方當前文件目錄 " << endl << " 'lsl' ……………………………………顯示本地當前目錄" << endl << " 'cd + <DIR>' ……………………………改變遠方當前目錄" << endl << " 'pwd'………………………………………顯示遠主當前目錄路徑" << endl << " 'get + 文件'……………………………取遠方的一個文件" << endl << " 'put + 文件'……………………………傳給遠方一個文件 " << endl << " 'quit'………………………………………退出 !" << endl << "-----------------------------------------------------------" << endl; } 4.3 服務器源代碼及解析 --------------------------ftp.h------------------------------ #pragma once #include <iostream> #include <string> #include <sstream> #include<cstdint> #include<WinSock2.h> #include<WS2tcpip.h> #pragma comment(lib,"ws2_32.lib") #include<Windows.h> using namespace std; class ftp { public: ftp(); ~ftp(); virtual void ftpconnect(const char* dstAddr, int port)=0;//鏈接 private: WSAData wsaData;//WINSOCK協議庫信息 uint16_t versionRequest;//版本 }; ------------------------------ftpserver.h------------------------------ #pragma once #include "ftp.h" class ftpserver :public ftp { public: ftpserver(); ~ftpserver(); virtual void ftpconnect(const char* dstAddr, int port);//鏈接 void applicationRun(); private: SOCKET serverSocket,accpetSocket;//服務端socket,鏈接socket sockaddr_in clientAddr;//客戶端地址 char command[10];//命令 char receiveBuffer[1024];//接收緩存 friend bool active(SOCKET &accSocket, const char* command, const char* param);//執行操做 friend unsigned long WINAPI ftpServerFuntion(void *param);//創建鏈接後的線程函數 friend char* isparamEmpty(char *command, char *param);//解析客戶端發來的命令 friend int isPath(SOCKET &accSocket, const char * pathName);//判斷是不是一個路徑 //pwd命令 friend bool sendPath(SOCKET &accSocket);//顯示服務端的路徑 //dir命令 friend bool sendFileList(SOCKET &accSocket);//發送目錄文件列表 friend bool SendFileRecord(SOCKET &datatcps, WIN32_FIND_DATA *pfd);//發送單個文件的信息 //cd命令 friend bool changePath(SOCKET &accSocket,const char* pathName);//改變目錄 //get命令 friend bool ftpPutFile(SOCKET &accSocket, const char* fileName);//推送文件操做 friend bool sendFile(SOCKET &accSocket, FILE *file);//發送文件 //put命令 friend bool ftpGetFile(SOCKET &accSocket, const char* fileName);//上載文件操做 }; ---------------------------------ftp.cpp-------------------------- #include "ftp.h" ftp::ftp() { versionRequest = MAKEWORD(2, 2); if (WSAStartup(versionRequest, &wsaData))//請求WINSOCK服務 { cout << "WINSOCK服務初始化失敗" << endl; } if (versionRequest != wsaData.wVersion) { cerr << "請求WINSOCK服務失敗" << endl; } } ftp::~ftp() { //關閉WSA WSACleanup(); } ---------------------------ftpserver.cpp------------------- #include "ftpserver.h" ftpserver::ftpserver() { serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket == SOCKET_ERROR) { cerr << "SERVERSocket申請失敗" << WSAGetLastError() << endl; this->~ftpserver(); } } ftpserver::~ftpserver() { closesocket(this->serverSocket); } void ftpserver::ftpconnect(const char * dstAddr, int port) { this->clientAddr.sin_addr.S_un.S_addr = INADDR_ANY; this->clientAddr.sin_family = AF_INET; this->clientAddr.sin_port = htons(port); if (bind(serverSocket, (sockaddr*)&clientAddr, sizeof(sockaddr)) == SOCKET_ERROR) { cerr << "套接字本地綁定失敗:" <<WSAGetLastError()<< endl; this->~ftpserver(); } } void ftpserver::applicationRun() { if (listen(serverSocket, 5) == SOCKET_ERROR) { cerr<<"監聽鏈接出錯"<< WSAGetLastError() << endl; this->~ftpserver(); } else { cout << "server start" << endl; cout << "listenling the connect request" << endl; } int addrLength = sizeof(sockaddr); while (1) { accpetSocket = accept(serverSocket, (sockaddr*)&clientAddr, &addrLength);//申請鏈接 CreateThread(NULL, 0, ftpServerFuntion, (void*)accpetSocket, 0, NULL); } } unsigned long WINAPI ftpServerFuntion(void * param) { char buffer[1024]; char* recvCommand; char* recvParam = NULL; char* noParam = "noParamCommand"; SOCKET acceptSocket = (SOCKET)param; cout << "創建新鏈接" << endl; send(acceptSocket, "歡迎登陸FTP服務器", 19, 0); Sleep(50);//睡眠50ms while (1) { send(acceptSocket, ">>", 3, 0);//發送執行框 if (recv(acceptSocket, buffer, sizeof(buffer), 0) == SOCKET_ERROR)//接收客戶端發來的命令 { cerr << "接收發生錯誤" << WSAGetLastError() << endl; closesocket(acceptSocket); break; } else { recvCommand = buffer; recvParam = isparamEmpty(recvCommand, recvParam);//解析發送過來的命令 if (!strcmp(recvParam, "\0")) recvParam = noParam;//無參數命令須要作標記 if (active(acceptSocket, recvCommand, recvParam) == true) {//傳入選擇一個操做 cout << "send successful" << endl; }else { cerr << "執行操做時發生錯誤" << WSAGetLastError() << endl; closesocket(acceptSocket); break; } } Sleep(50); } return 0; } char *isparamEmpty(char *command, char *param) { char *temp = NULL; temp = command; while (*temp != '\0') { if (*temp == ' ') { *temp = '\0'; param = ++temp; } temp++; } return param; } int isPath(SOCKET& accSocket,const char * pathName) { HANDLE serachFile; WIN32_FIND_DATA fd; string error; serachFile = FindFirstFile(pathName, &fd);//查找全部文件 if (serachFile == INVALID_HANDLE_VALUE) {//查找文件發生錯誤 if (send(accSocket, "找不到目標", 11, 0) == SOCKET_ERROR) { cout << "發送文件錯誤信息時發生錯誤" << endl; return -1;//未找到該文件,且發送迴應錯誤 }else { return 0;//未找到該文件,錯誤信息發送成功 } }else { if (fd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) {//判斷是否是一個路徑 return 1;//是路徑 }else { return 2;//不是路徑 } } } bool active(SOCKET &accSocket,const char* command, const char* param) { if (!strcmp(param, "noParamCommand")) {//無參數命令 if (!strcmp(command, "ls")) {//顯示當前目錄的文件列表 if (sendFileList(accSocket) == true) { return true; }else { return false; } }else if (!strcmp(command, "pwd")) {//獲取當前目錄,並放至回覆報文中 if (sendPath(accSocket) == true) { return true; }else { return false; } } }else {//有參數命令 if (!strcmp(command, "get")) {//下載文件 if (ftpPutFile(accSocket,param) == true) { return true; }else { return false; } }else if (!strcmp(command, "put")) {//上傳文件 if (ftpGetFile(accSocket, param) == true) { return true; }else { return false; } }else if (!strcmp(command, "cd")) {//進入目錄 if (changePath(accSocket,param) == true) { return true; }else { return false; } } } } bool sendFileList(SOCKET & accSocket) { HANDLE serachFile; WIN32_FIND_DATA fd; serachFile = FindFirstFile("*", &fd);//查找全部文件 if (serachFile == INVALID_HANDLE_VALUE) {//查找文件發生錯誤 const char* error = "列出文件列表發生錯誤!"; cout <<error << endl; if (send(accSocket, error, strlen(error)+1, 0) == SOCKET_ERROR) { cout << "發送文件列表錯誤信息時發生錯誤" << endl; return false; } } BOOL findMoreFiles = TRUE; while (findMoreFiles) { if (SendFileRecord(accSocket, &fd) == false) {//獲取一個文件的信息併發送 return false; } findMoreFiles = FindNextFile(serachFile, &fd);//得到下一個文件的信息 //睡眠50ms,緩衝發送太快會出現丟包狀況 Sleep(50); } return true; } bool SendFileRecord(SOCKET & datatcps, WIN32_FIND_DATA * pfd) { char fileRecord[MAX_PATH + 32]; FILETIME fileTime;//FILETIME結構持有的64位無符號的文件的日期和時間值 FileTimeToLocalFileTime(&pfd->ftLastWriteTime, &fileTime);//讀取最近寫入事件的日期 SYSTEMTIME lastTime; FileTimeToSystemTime(&fileTime, &lastTime);//轉換成本地事件的結構 char *dir = pfd->dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY ? "<dir>" : " ";//判斷目標文件是否爲文件夾 sprintf(fileRecord,"%04d-%02d-%02d %02d:%02d %5s%10dB %-20s\n",//右對齊輸出文件名字 lastTime.wYear,//寫入順序未年月日時分,文件是不是路徑標誌,文件大小,文件名 lastTime.wMonth, lastTime.wDay, lastTime.wHour, lastTime.wMinute, dir, pfd->nFileSizeLow, pfd->cFileName); if (send(datatcps, fileRecord,strlen(fileRecord)+1, 0) == SOCKET_ERROR) { cout << "發送文件列表時發生錯誤" << endl; return false; } return true; } bool sendPath(SOCKET & accSocket) { char buffSend[1024]; if (!GetCurrentDirectory(1024, buffSend))//獲取當前目錄路徑 strcpy(buffSend, "pwd--沒法得到當前路徑"); if (send(accSocket, buffSend, 1024, 0) == SOCKET_ERROR) { cout << "send error!" << endl; return false; } else { return true; } } bool changePath(SOCKET & accSocket, const char* pathName) { int status = isPath(accSocket, pathName); if ( status == 1) { //若是操做的文件是路徑的話 SetCurrentDirectory(pathName);//更改路徑 if (sendPath(accSocket) == true) { cout << "切換路徑成功" << endl; return true; } else { cout << "切換路徑時發生錯誤" << endl; return false; } }else if(status == 2){ if (send(accSocket, "目標不是路徑", 13, 0) == SOCKET_ERROR) { cout << "發送切換路徑錯誤信息時發生錯誤" << endl; return false; }else { return true; } } return true; } bool ftpPutFile(SOCKET & accSocket, const char * fileName) { int status = isPath(accSocket, fileName);//先判斷是否是一個參數 if (status == 1) {//目標是一個路徑 if (send(accSocket,"目標是一個路徑", 15, 0) == SOCKET_ERROR) { cout << "發送文件錯誤信息時發生錯誤" << endl; return false; }else { return false; } }else if(status == 2){//可下載的文件 FILE *file = fopen(fileName, "rb");//二進制讀取模式 if (file == NULL) { if (send(accSocket, "沒法下載文件",13, 0) == SOCKET_ERROR) {//先發送文件名給客戶端 cout << "發送文件錯誤信息時發生錯誤" << endl; } fclose(file); return false; }else {//成功讀取文件 if (send(accSocket, fileName, strlen(fileName) + 1, 0) == SOCKET_ERROR) {//先發送文件名給客戶端 cout << "發送文件錯誤信息時發生錯誤" << endl; return false; }else { if (sendFile(accSocket, file)) {//發送文件 cout << "文件發送完成" << endl; fclose(file); return true; }else { fclose(file); return false; } } } }else { if (send(accSocket, "未找到文件", 11, 0) == SOCKET_ERROR) { cout << "發送文件錯誤信息時發生錯誤" << endl; return false; }else { return false; } } } bool sendFile(SOCKET & accSocket, FILE * file) { char buffSend[1024]; cout << "正在發送文件..." << endl; int Size = 1; while (1) { Size = fread(buffSend, 1, 1024, file); if (send(accSocket,buffSend,Size,0) == SOCKET_ERROR) { cout << "數據傳送鏈接斷開" << endl; return false; } //若是buffSend沒有裝滿,則說明已經讀到文件尾了 if (Size < sizeof(buffSend))break; } return true; } bool ftpGetFile(SOCKET & accSocket, const char * fileName) { int Size = 0; char buff[1024]; FILE *file; cout << "正在上載文件:" << fileName << endl; file = fopen(fileName, "wb");//寫 while (1) { Size = recv(accSocket,buff, sizeof(buff), 0); if (Size == SOCKET_ERROR) { cerr << "下載文件時出錯:" << WSAGetLastError() << endl; return false; } fwrite(buff, sizeof(char), Size, file);//寫 if (Size < sizeof(buff)) break;//若是緩衝區沒有被寫滿說明已經下載完了 } fclose(file); return true; } ----------------main.cpp-------------------- #include"ftp.h" #include"ftpserver.h" int main() { ftpserver server; server.ftpconnect(INADDR_ANY,4523); server.applicationRun(); return 0; }
(1)客戶端登陸ftp+ip執行效果如圖:
服務器端的反應如圖:
(2)?命令效果,如圖所示:
C/S:即客戶端和服務器結構。客戶端和服務器分別承擔不一樣的任務。Client將用戶的需求提交給Server,再將Server返回的結果以必定的形式提交給用戶。Server的任務是按接收Client提出的服務請求,進行相應的處理,並將結果返回給Client。
在CS結構下,服務器程序一般在一個固定的地址監聽客戶端的請求。服務器進程一般下處於「休眠」狀態,直到客戶端對該服務發出鏈接請求,將其「喚醒」。此時,服務進程「醒來」並對客戶端的請求做出適當的反應。
這種請求應答的過程如圖所示。
BS( Browser/Server),即瀏覽器與服務器結構。客戶端運行瀏覽器,瀏覽器以超文本形式向web服務器提出訪問數據庫請求。Web服務器接受客戶端請求後,將該請求轉換爲SQL語法,並對數據庫進行訪問,而後將結果返回給Web服務器。Web服務器再將該結果轉換爲HTML文檔,返回客戶端瀏覽器,以網頁的形式顯示出來。BS結構中,Web瀏覽器是客戶端最主要的軟件,系統功能實現的核心部分集中到服務器上。
下面對CS和BS兩種結構進行簡單比較
CS結構充分利用客戶端和服務器的硬件優點,將任務合理分配到客戶端和服務器端下降系統的通訊開銷。不少任務在客戶端處理後再提交給服務器,因此服務器運行負荷較輕客戶端響應速度很快。但此結構要求客戶端安裝專用的客戶端軟件
B/S最大的優勢就是不須要安裝任何專門的軟件,只要客戶端安裝瀏覽器便可。BS的系統擴展容易,維護和升級方式簡單。
2. 查找資料,如何在本題目中添加「斷點續傳」功能?以及該功能的實現基本原理如何?
實現:如下載文件爲例:1)向服務器發送「REST + 本地文件長度」命令,告訴服務器,客戶端要斷點下載了,服務器必須支持REST命令;2)向服務器發送「RETR + 文件名」命令,通知服務器要下載的文件名,這時服務器開始定位文件指針讀文件併發送數據;3)客戶端定位本地文件指針(文件末尾); 4)兩端的準備工做都作完了之後,客戶端建立socket,以被動或非被動方式創建數據通道,循環調用recv接收數據並追加入本地文件。
原理:因爲FTP是順序接收文件的,因此,只要計算已接收的數據大小,就能夠知道斷點在文件的偏移量,續傳時,客戶端將已接收的數據量傳遞給服務器,服務器使用這個做爲偏移量繼續讀取文件,將剩餘的數據發送至客戶端,客戶端僅需將接收到的數據追加至原文件尾部便可.
3.咱們已經有了FTP後,爲什麼在郵件服務器之間傳輸郵件(郵件也是一種文件)時,還須要SMTP協議?以及爲什麼須要HTTP協議?
SMTP(Simple Mail Transfer Protocol)是簡單郵件傳輸協議,是一種提供可靠且有效電子郵件傳輸的協議。SMTP是創建在FTP文件傳輸服務上的一種郵件服務,主要用於傳輸系統之間的郵件信息並提供與來信有關的通知。
超文本傳輸協議(HTTP,HyperText Transfer Protocol)是互聯網上應用最爲普遍的一種網絡協議。全部的WWW文件都必須遵照這個標準。設計HTTP最初的目的是爲了提供一種發佈和接收HTML頁面的方法。在Internet上的Web服務器上存放的都是超文本信息,客戶機須要經過HTTP協議傳輸所要訪問的超文本信息。HTTP包含命令和傳輸信息,不只可用於Web訪問,也能夠用於其餘因特網/內聯網應用系統之間的通訊,從而實現各種應用資源超媒體訪問的集成。
4.考慮如何用多線程實現FTP?
每一個線程建立一個 TCP 鏈接,對應一個文件,採用循環接收,應答等機制來傳送文件數據。N個這樣的線程就對應而來正在下載N個文件