02文件传输程序
1. FTP传输协议具体内容
1.1 实验目的
文件传送是各种计算机网络都实现的基本功能,文件传送协议是一种最基本的应用层协议按照客户/服务器的模式进行工作,提供交互式的访问,是INTERNET使用最广泛的协议之一。
本实验的目的是,学会利用已有网络环境设计并实现简单应用层协议,掌握TCP/IP 网络应用程序基本的设计方法和实现技巧。
1.2 实验内容和要求
1、实验内容
我们的计算机网络实验环境建立在TCP/IP 网络体系结构之上。各计算机除了安装TCP/IP 软件外,还安装了TCP/IP 开发系统。实验室各计算机具备Windows环境中套接字socket 的编程接口功能,可为用户提供全网范围的进程通信功能。本实验要求学生利用这些功能,设计和实现一个简单的文件传送协议。
2、具体要求
用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 :退出返回程序
2.设计方案
1.服务器端调用socket的accept()方法,等待客户端联接。
2.客户端发送需要从服务端获得的文件夹路径给服务器端。
3.服务器端根据客户端发来的路径遍历整个路径下的文件及文件夹,并将需要传送到文件记录全路径记录到一个String类型的数组中,并将文件的个数发送给客户端。
4.客户端接收服务器端发来的文件个数,并向服务器端发送关闭联接请求,并关闭客户端连接。
5.服务器端收取关闭连接请求,将socket关闭,进入传输文件循环中。
6.服务器端调用socket的accept()方法,等待客户端联接。
7.客户端根据刚才取得的要传输的文件数,进行循环获取文件,记得每次取完文件要关闭socket连接,这样就不会将多个文件写入到同一个文件中。
3.原理
3.1 Winsock编程原理
通过Winsock可实现点对点或广播通信程序,实际这两者之间的区别不大,编程时其程序流程所用代码几乎相同,不同的地方在于目标地址选择的不同。
由于Winsock的服务是以动态链接库Winsock DLL形式实现的,因此先调用
1、WSAStartup对Winsock DLL进行初始化
2、应用程序关闭套接字后,还应调用WSACleanup终止对Winsock DLL的使用
3.2 TCP/IP原理
TCP协议和IP协议指两个用在Internet上的网络协议(或数据传输的方法)。它们分别是传输控制协议和互连网协议。这两个协议属于众多的TCP/IP 协议组中的一部分。TCP/IP协议组中的协议保证Internet上数据的传输,提供了几乎现在上网所用到的所有服务。
3.3 FTP工作原理
文件传输协议是从一个系统向另一个系统传递文件的标准方法。它的目标在RFC 0765中写得很清楚。FTP的目标是1)促进文件和程序的共享,2)鼓励间接和含蓄的使用远程计算机,3)使用户不必面对主机间使用的不同的文件存储系统,4)有效和可靠地传输文件。
FTP,尽管用户可以直接通过终端来使用,是设计成让别的程序使用的。FTP文件传输应用在客户/服务环境。请求机器启动一个FTP客户端软件。这就给目标文件服务器发出了一个请求。典型地,这个要求被送到端口21。一个连接建立起来后,目标文件服务器必须运行一个FTP服务软件。
4.代码分析
4.1 主要函数分析
客户端:
(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)函数将整个文件内容发给客户端。
4.2 客户端源代码及解析
--------------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; }
5.运行结果
(1)客户端登录ftp+ip执行效果如图:
服务器端的反应如图:
(2)?命令效果,如图所示:
6.思考题
- 本题目采用的是C/S模式下实现文件传输协议,考虑当前应用广泛的B/S模式,这两种编程模式优缺点如何?
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个文件