[计网] C++ socket 实现miniFTP

本文介绍了一个简单的FTP客户端与服务器的实现方式,包括TCP连接建立、文件传输过程、接收确认机制及具体代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



建立连接


        连接使用 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)
{
	//创建socket
	clientSocket = 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));//初始化为0

	serverChannel.sin_family = AF_INET;//channel协议家族AF_INET
	serverChannel.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; //socket
	int 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();
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值