[计网] C++ socket 实现miniFTP

来源:互联网 发布:在淘宝开网店收费吗 编辑:程序博客网 时间:2024/05/16 05:59



建立连接


        连接使用 TCP 连接,服务器和客户端分别创建自己的套接字一端,服务器等待连接,客户端发起连接(并指定服务器 ip)。在两者端口号一致且不被占用的情况下,连接建立。
        在整个过程中,服务器对每一个来访的客户端建立一个连接,在客户未请求与服务器断开时,该连接一直存在,用户可以不断向服务器发出请求。(持久性、流水线型连接 )
        客户端断开后,关闭客户端的套接字部分,服务器继续等待新的连接。服务器一次只能处理一个客户端的连接,不支持并发访问。


PDU 格式


        由于 ftp 应当支持几乎任意类型文件,而几乎所有类型文件都能用二进制来解析,所以我们采用了二进制的格式来读取以及写入文件。在整个过程中,我们并不关心文件的具体内容,也无需在程序中解析文件,而是将其当作数据流看待。
        受到缓存区大小的限制,我们无法一次性传输整个文件,所以我们将文件按缓存区大小拆分成数据包分批发送,我们可以将数据及时从缓存区写入文件,这样就让出了缓存区空间。数据包仅仅包含数据,不包含头部或尾部信息。
        此外,接收文件时,recv()函数将会循环调用,因此,我们需要一个信号来通知什么时候发送完毕。
        一个想法是发送终止信号,这是可行的,但更好的方法是在一开始发送文件总字节数,让接收方根据剩余字节大小判断什么时候接收完毕。因为在写入文件时,我们需要指定写入的字节数,尤其是在发来的数据流字节数不等于缓冲区大小时。写入字节数的错误会导致文件受损。


接收确认


        我们知道 TCP 是可靠传输协议,它采取了一系列措施来保证传输不会出错。所以在使用 TCP 连接时,我们相信数据在链路层上没有出差错,它一定会成功发送到对方手上。但是在客户端接收服务器发来的文件的时候,我们仍然需要服务器发来确认信息。原因在于,虽然我们可以保证链路层不出错,但是我们无法保证应用层不出错。例如,客户端可能会给出错误的文件名,因为接收不到服务器发来的信息,所以会陷入空等状态。


ftpClient.h

#pragma #include<winsock.h>class ftpClient{private:enum {SERVER_PORT = 9999,BUFFER_SIZE = 4096};sockaddr_in serverChannel;char buffer[BUFFER_SIZE];int serverSocket;int clientSocket;bool isConnect;char name[50];bool getFile();bool putFile();bool acknowledge();bool sendRequest(char* instruction);bool connect2Host(const char* hostName);bool getWorkDir();public:ftpClient();~ftpClient();void start();};

ftpClient.cpp

#define _CRT_SECURE_NO_WARNINGS#include"ftpClient.h"#include<cstdio>#include<io.h>#include<cstring>#include<fstream>ftpClient::ftpClient(){WORD wVersionRequested;WSADATA wsaData;int ret;//WinSock初始化:wVersionRequested = MAKEWORD(2, 2);//希望使用的WinSock DLL的版本ret = WSAStartup(wVersionRequested, &wsaData);if (ret != 0){printf("WSAStartup() failed!\n");}//确认WinSock DLL支持版本2.2:if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){WSACleanup();printf("Invalid Winsock version!\n");}isConnect = false;}void ftpClient::start(){char c[100];char d[100];printf("这里是FTP客户端,您可以输入help查看操作方法,输入quit退出客户端\n");while (1) {scanf("%s", c);if (strcmp(c, "help") == 0) {printf("get [fileName]    --    下载文件\n""put [fileName]    --    上传文件\n""ftp [ip]          --    登录FTP\n""pwd               --    显示服务器当前工作文件夹\n""cd  [dirName]     --    更改当前文件夹\n""close             --    关闭与当前ftp的连接\n""quit              --    退出客户端\n");}else if (strcmp(c, "get") == 0) {scanf("%s", d);strcat(c, " ");strcat(c, d);if (!isConnect) {printf("you haven't connected to any server!\n");}else sendRequest(c);}else if (strcmp(c, "put") == 0) {scanf("%s", d);strcat(c, " ");strcat(c, d);if (!isConnect) {printf("you haven't connected to any server!\n");}else sendRequest(c);}else if (strcmp(c, "ftp") == 0) {scanf("%s", d);if (!isConnect&&connect2Host(d)) {isConnect = true;}else if(isConnect){printf("you have already connected to server\n""please close the connection before connect to a new server\n");}}else if (strcmp(c, "pwd") == 0) {if (!isConnect) {printf("you haven't connected to any server!\n");}else sendRequest(c);}else if (strcmp(c, "cd") == 0) {scanf("%s", d);strcat(c, " ");strcat(c, d);if (!isConnect) {printf("you haven't connected to any server!\n");}else sendRequest(c);}else if (strcmp(c, "quit") == 0) {if (isConnect) {strcpy(c, "close");isConnect = false;send(clientSocket, c, strlen(c) + 1, 0);closesocket(clientSocket);}break;}else if (strcmp(c, "close") == 0) {if (isConnect) {isConnect = false;send(clientSocket, c, strlen(c) + 1, 0);closesocket(clientSocket);}}else {printf("syntex error\n");}}}bool ftpClient::connect2Host(const char* hostName){//创建socketclientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (clientSocket < 0) {printf("cannot create socket\n");return false;}else printf("successfully create socket\n");memset(&serverChannel, 0, sizeof(serverChannel));//初始化为0serverChannel.sin_family = AF_INET;//channel协议家族AF_INETserverChannel.sin_addr.S_un.S_addr = inet_addr(hostName);//地址serverChannel.sin_port = htons(SERVER_PORT);//服务器端口//建立连接serverSocket = connect(clientSocket, (sockaddr*)&serverChannel, sizeof(serverChannel));if (serverSocket < 0) {printf("cannot connect to the host\n");return false;}else {printf("successfully connect to the host\n");return true;}}bool ftpClient::sendRequest(char* instruction){int r = send(clientSocket, instruction, strlen(instruction) + 1, 0);if (r == SOCKET_ERROR) {printf("request failed\n");return false;}else {printf("request success\n");char opt[5];int i = 0, j = 0;while (instruction[i] != ' '&&instruction[i] != '\0') {opt[i] = instruction[i];i++;}opt[i] = '\0';i++;while (instruction[i] != '\0') {name[j] = instruction[i];i++, j++;}name[j] = '\0';if (strcmp(opt, "get") == 0) {if (getFile()) {printf("successfully download\n");}else printf("download failed\n");}else if (strcmp(opt, "put") == 0) {if (putFile()) {printf("successfully upload\n");}else printf("upload failed\n");}else if (strcmp(opt, "pwd") == 0) {if (!getWorkDir())printf("get work directory failed\n");}else if (strcmp(opt, "cd") == 0) {printf("operation finished\n");}else {printf("syntex error\n");return false;}return true;}}bool ftpClient::getFile(){memset(buffer, 0, sizeof(buffer));int ret;char length[20];ret = recv(clientSocket, length, sizeof(length), 0);if (ret == SOCKET_ERROR) {return false;}else if (strcmp(length, "NAK") == 0) {return false;}int size = atoi(length);std::ofstream out;out.open(name, std::ios::binary);if (!out) {printf("cannot save the file\n");return false;}while (size>0) {ret = recv(clientSocket, buffer, BUFFER_SIZE, 0);int s = size < BUFFER_SIZE ? size : BUFFER_SIZE;if (ret == SOCKET_ERROR) {out.close();return false;}else if (strcmp(buffer, "NAK") == 0) {out.close();return false;}else {out.write(buffer, s);}size -= BUFFER_SIZE;}out.close();return acknowledge();}bool ftpClient::putFile(){std::ifstream in;//打开文件in.open(name, std::ios::binary);if (!in) {printf("cannot open the file\n");return false;}memset(buffer, 0, sizeof(buffer));//得到文件的字节数in.seekg(0, std::ios_base::end);int sp = in.tellg();int total_size = 0;int r;char length[20];sprintf(length, "%d", sp);//发送字节r = send(clientSocket, length, sizeof(length), 0);if (r == SOCKET_ERROR) {return false;}while (sp > 0) {in.clear();in.seekg(total_size, std::ios_base::beg);memset(buffer, 0, sizeof(buffer));//读取文件in.read(buffer, sizeof(buffer));int size = sp < BUFFER_SIZE ? sp : BUFFER_SIZE;total_size += size;//发送文件r = send(clientSocket, buffer, size, 0);sp -= size;if (r == SOCKET_ERROR) {in.close();return false;}}in.close();}bool ftpClient::getWorkDir() {printf("getWorkDir\n");memset(buffer, 0, sizeof(buffer));int ret;char length[20];ret = recv(clientSocket, length, sizeof(length), 0);if (ret == SOCKET_ERROR) {return false;}int size = atoi(length);while (size>0) {ret = recv(clientSocket, buffer, BUFFER_SIZE, 0);if (ret == SOCKET_ERROR) {return false;}else {printf("%s", buffer);}size -= BUFFER_SIZE;}return true;}bool ftpClient::acknowledge(){int ret = recv(clientSocket, buffer, BUFFER_SIZE, 0);if (ret > 0) {if (strcmp(buffer, "NAK") == 0)return false;else if (strcmp(buffer, "ACK") == 0)return true;}}ftpClient::~ftpClient(){if (isConnect) {isConnect = false;char c[6];strcpy(c, "close");send(clientSocket, c, strlen(c) + 1, 0);closesocket(clientSocket);}}

main.cpp

#define _CRT_SECURE_NO_WARNINGS#define _WINSOCK_DEPRECATED_NO_WARNINGS#pragma(lib,"ws2_32.lib")#include"ftpClient.h"#include<stdio.h>int main(){ftpClient a;a.start();return 0;}

ftpServer.h

#pragma once#include<winsock.h>class ftpServer{private:enum {SERVER_PORT = 9999,BUFFER_SIZE = 4096,QUEUE_SIZE = 10};char buffer[BUFFER_SIZE];sockaddr_in serverChannel;char name[50];char workDir[100]; //store like C:\Users MARK:字符串末没有斜线!!int serverSocket; //socketint clientSocket;bool sendFile();bool receiveFile();bool doPwd();bool doCd();bool isValidPath(char* path);public:ftpServer();bool start();//开启服务器};

ftpServer.cpp

#define _CRT_SECURE_NO_WARNINGS#include"ftpServer.h"#include<cstdio>#include<cstdlib>#include<fstream>#include<cstring>ftpServer::ftpServer(){WORD wVersionRequested;WSADATA wsaData;int ret;//WinSock初始化:wVersionRequested = MAKEWORD(2, 2);//希望使用的WinSock DLL的版本ret = WSAStartup(wVersionRequested, &wsaData);if (ret != 0){printf("WSAStartup() failed!\n");}//确认WinSock DLL支持版本2.2:if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2){WSACleanup();printf("Invalid Winsock version!\n");}//workDir初始化为当前路径system("cd > tempFile");std::ifstream in("tempFile", std::ifstream::in);in >> workDir;in.close();}bool ftpServer::start(){int on = 1;//初始化服务器memset(&serverChannel, 0, sizeof(serverChannel));serverChannel.sin_family = AF_INET;serverChannel.sin_addr.s_addr = htonl(INADDR_ANY);serverChannel.sin_port = htons(SERVER_PORT);//创建套接字this->serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (serverSocket < 0) {printf("cannot create socket\n");return false;}else printf("successfully create socket\n");setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR,(char*)&on, sizeof(on));//绑定int b = bind(serverSocket, (sockaddr*)&serverChannel,sizeof(serverChannel));if (b < 0) {printf("bind error\n");return false;}else printf("successfully bind\n");//监听int l = listen(serverSocket, QUEUE_SIZE);if (l < 0) {printf("listen failed\n");return false;}else printf("successfully listen\n");int len = sizeof(serverChannel);//服务器等待连接while (1) {printf("waiting for connection...\n");//接受一个连接clientSocket = accept(serverSocket, (sockaddr*)&serverChannel,&len);if (clientSocket < 0) {printf("accept failed\n");}else {printf("successfully connect\n");while (1) {memset(buffer, 0, sizeof(buffer));int ret;ret = recv(clientSocket, buffer, BUFFER_SIZE, 0);if (ret == SOCKET_ERROR) {printf("receive failed\n");}else {char opt[50];printf("successfully receive\n");int i = 0, j = 0;printf("buffer = %s\n", buffer);while (buffer[i] != ' '&&buffer[i] != '\0') {opt[i] = buffer[i];i++;}opt[i] = '\0';if (buffer[i] != '\0') {i++;}while (buffer[i] != '\0') {name[j] = buffer[i];i++, j++;}name[j] = '\0';if (strcmp(opt, "get") == 0) {char ret[4];if (!sendFile()) {strcpy(ret, "NAK");send(clientSocket, ret, sizeof(ret), 0);}else {strcpy(ret, "ACK");send(clientSocket, ret, sizeof(ret), 0);}}else if (strcmp(opt, "put") == 0) {receiveFile();}else if (strcmp(opt, "pwd") == 0) {doPwd();}else if (strcmp(opt, "cd") == 0) {doCd();}else if (strcmp(opt, "close") == 0) {break;}else {printf("syntex error\n");}}}}}return true;}bool ftpServer::sendFile(){std::ifstream in;char path[100];strcpy(path, workDir);strcat(path, "\\");strcat(path, name);in.open(path, std::ios::binary);if (!in) {printf("cannot open the file\n");return false;}memset(buffer, 0, sizeof(buffer));in.seekg(0, std::ios_base::end);int sp = in.tellg();int total_size = 0;int r;char length[20];sprintf(length, "%d", sp);r = send(clientSocket, length, sizeof(length), 0);if (r == SOCKET_ERROR) {printf("send failed\n");return false;}else {printf("send success\n");}while (sp > 0) {in.clear();in.seekg(total_size, std::ios_base::beg);memset(buffer, 0, sizeof(buffer));in.read(buffer, sizeof(buffer));int size = sp < BUFFER_SIZE ? sp : BUFFER_SIZE;total_size += size;r = send(clientSocket, buffer, size, 0);sp -= size;if (r == SOCKET_ERROR) {printf("send failed\n");return false;}else {printf("send success\n");}}in.close();return true;}bool ftpServer::receiveFile(){char path[100];strcpy(path, workDir);strcat(path, "\\");strcat(path, name);memset(buffer, 0, sizeof(buffer));int ret;char length[20];ret = recv(clientSocket, length, sizeof(length), 0);if (ret == SOCKET_ERROR) {printf("receive failed\n");return false;}else {printf("successfully receive\n");}int size = atoi(length);std::ofstream out;out.open(path, std::ios::binary);if (!out) {printf("cannot save the file\n");return false;}while (size>0) {int s = size < BUFFER_SIZE ? size : BUFFER_SIZE;ret = recv(clientSocket, buffer, BUFFER_SIZE, 0);if (ret == SOCKET_ERROR) {printf("receive failed\n");break;}else {printf("successfully receive\n");out.write(buffer, s);}size -= BUFFER_SIZE;}out.close();return true;}bool ftpServer::doPwd() {char temCMD[150];memset(temCMD, 0, sizeof(temCMD));strcat(temCMD, "echo ");strcat(temCMD, workDir);strcat(temCMD, " > tempFile");system(temCMD);memset(temCMD, 0, sizeof(temCMD));strcat(temCMD, "dir /b ");strcat(temCMD, workDir);strcat(temCMD, " >> tempFile");system(temCMD);std::ifstream in("tempFile", std::fstream::in);if (!in) {printf("cannot open the file\n");return false;}memset(buffer, 0, sizeof(buffer));in.seekg(0, std::ios_base::end);int sp = in.tellg();int total_size = 0;int r;char length[20];sprintf(length, "%d", sp);r = send(clientSocket, length, sizeof(length), 0);if (r == SOCKET_ERROR) {printf("send failed\n");return false;}else {printf("send success\n");}while (sp > 0) {in.clear();in.seekg(total_size, std::ios_base::beg);memset(buffer, 0, sizeof(buffer));in.read(buffer, sizeof(buffer));int size = sp < BUFFER_SIZE ? sp : BUFFER_SIZE;total_size += size;printf("transfer size = %d\n", total_size);r = send(clientSocket, buffer, size, 0);sp -= size;if (r == SOCKET_ERROR) {printf("send failed\n");return false;}else {printf("send success\n");}}in.close();return true;}bool ftpServer::isValidPath(char* path) {char temCMD[100];memset(temCMD, 0, sizeof(temCMD));strcat(temCMD, "cd ");strcat(temCMD, path);int res = system(temCMD);return res == 0;}bool ftpServer::doCd() {for (int i = 0; name[i] != '\0'; ++i) {if (name[i] == '/')name[i] = '\\';}if (name[0] == '.'&&name[1] == '.') {char temDir[100];strcpy(temDir, workDir);for (int i = sizeof(temDir); i >= 0; --i) {if (temDir[i] == '\\') {temDir[i] = '\0';break;}}strcat(temDir, name + 2);if (isValidPath(temDir)) {strcpy(workDir, temDir);}else {return false;}}else if (name[0] == '.'&&name[1] != '.') {char temDir[100];strcpy(temDir, workDir);strcat(temDir, name + 1);if (isValidPath(temDir)) {strcpy(workDir, temDir);}else {return false;}}else if (name[1] == ':') {if (isValidPath(name)) {strcpy(workDir, name);}else {return false;}}else {char temDir[100];strcpy(temDir, workDir);strcat(temDir, "\\");strcat(temDir, name);if (isValidPath(temDir)) {strcpy(workDir, temDir);}else {return false;}}return true;}

main.cpp

#include"ftpServer.h"#pragma(lib,"ws2_32.lib")int main(){ftpServer f;f.start();}


0 0
原创粉丝点击