windows环境下C++ FTP客户端(持续优化)

原始版本

测试环境:VS2019,
win10专业版自带FTP,Xlight,ubuntu这三个都进行了测试,需要对代码进行修改
在代码的最后面有一个
//回到根目录,以便进行下一次目录的操作
下面会调用两次ftp_socket_recv函数,如果是在windows自带的ftp做测试的话,需要注释掉一次,即只调用一次ftp_socket_recv函数

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<WinSock2.h>
#include<cstring>
#include<string>
#include<vector>
#include<fstream>
#include <io.h>  
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable : 4996)
using namespace std;

const int MAX_LEN = 1024 * 1;
const int MAX_CMD_LEN = 128;
//把str发到socket
static inline int ftp_socket_send(SOCKET& socket, const char* str) {
	int len = send(socket, str, strlen(str), 0);
	if (SOCKET_ERROR == len) {
		cout << "error code in ftp_socket_send" << WSAGetLastError() << endl;
		return -1;
	}
	return len;
}
//接收从socket发来的str
static inline int ftp_socket_recv(SOCKET& socket, char* str) {
	int size = recv(socket, str, MAX_LEN - 1, 0);
	if (SOCKET_ERROR == size) {
		cout << "error code in ftp_socket_recv" << WSAGetLastError() << endl;
		return -1;
	}
	str[size] = 0;
	cout << "ftp recv:" << str << endl;
	return size;
}
//从PASV模式返回的字符串解析出用于数据传输的port
static int ftp_get_data_port(char* buff, USHORT* port)
{
	int i = 0, j = 0;
	short port_l = 0, port_h = 0;

	if (buff == NULL || port == NULL)
	{
		return -1;
	}
	// 227 Entering Passive Mode (192,168,1,101,242,34)\r\n"
	// (192,168,186,1,4,0).
	while (buff[i++] != '(');
	while (j < 4)
	{
		if (buff[i++] == ',')
			j++;
	}

	while (buff[i] != ',')
	{
		port_h *= 10;
		port_h += buff[i] - 0x30;
		i++;
	}

	i++;
	while (buff[i] != ')')
	{
		port_l *= 10;
		port_l += buff[i] - 0x30;
		i++;
	}

	printf("data_port : %u\n", port_h << 8 | port_l);
	*port = htons((short)(port_h << 8 | port_l));

	return 0;
}
//切分多级目录至tokens
void split(const string& s, vector<string>& tokens, const string& delimiters = "\\") {
	string::size_type lastPos = s.find_first_not_of(delimiters, 0);
	string::size_type pos = s.find_first_of(delimiters, lastPos);
	while (string::npos != pos || string::npos != lastPos) {
		tokens.emplace_back(s.substr(lastPos, pos - lastPos));
		lastPos = s.find_first_not_of(delimiters, pos);
		pos = s.find_first_of(delimiters, lastPos);
	}
}

/*
	ip:FTP服务器的IP地址
	port:FTP服务器的端口
	user:登录FTP服务器的用户名
	pwd:登录FTP服务器的密码
	upload_file:需要上传的本地图片的名字
	upload_name:该图片上传至FTP后的名字
	folder_name:FTP文件夹的路径,不存在则创建

	本地的文件:upload_file上传至ftp的folder_name之后,名字叫做upload_name
*/
int ftp_upload_file(const char* ip, unsigned int port, const char* user, const char* pwd,
	const char* upload_file, const char* upload_name, const char* folder_name) {


	char buff[MAX_LEN], cmd[MAX_CMD_LEN];
	string file_name(upload_name);
	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSockMsg;
	int nres = WSAStartup(wdVersion, &wdSockMsg);
	if (nres != 0)
	{
		switch (nres)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库\n");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库\n");
			break;
		case WSAEINPROGRESS:
			printf("请重新启动\n");
			break;
		case WSAEPROCLIM:
			printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源\n");
			break;
		}
		return 0;
	}

	//版本校验,这是因为之前如果我们提供的版本号不对,系统可能会给我们采用默认的版本号,所以需要校验
	if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	//socketServer 创建一个服务器的SOCKET,因为客户端只需要连接服务器,需要明确服务器的一些信息 
	//socketData 通过此变量向FTP推送文件
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	SOCKET socketData = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socketServer || INVALID_SOCKET == socketData)
	{
		int a = WSAGetLastError();
		cout << "error code in create SOCKET:" << a << endl;
		WSACleanup();
		return 0;
	}

	//server_msg用于绑定FTP服务器,server_data用于绑定用于上传文件的端口
	struct sockaddr_in server_msg, server_data;
	server_msg.sin_family = AF_INET;
	server_msg.sin_port = htons(port);
	server_msg.sin_addr.S_un.S_addr = inet_addr(ip);
	server_data.sin_family = AF_INET;
	server_data.sin_addr.S_un.S_addr = inet_addr(ip);
	int res_connect = connect(socketServer, (const struct sockaddr*) & server_msg, sizeof(server_msg));
	if (res_connect == SOCKET_ERROR)
	{
		int a = WSAGetLastError();
		cout << "error code in connect to FTP server:" << a << endl;
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	//发送用户名至FTP服务器
	memset(cmd, 0, sizeof(cmd));
	memset(buff, 0, sizeof(buff));
	sprintf(cmd, "USER %s\r\n", user);
	ftp_socket_send(socketServer, cmd);
	ftp_socket_recv(socketServer, buff);
	//发送密码至FTP服务器
	memset(cmd, 0, sizeof(cmd));
	memset(buff, 0, sizeof(buff));
	sprintf(cmd, "PASS %s\r\n", pwd);
	ftp_socket_send(socketServer, cmd);
	ftp_socket_recv(socketServer, buff);

	//返回服务器使用的操作系统
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "SYST\r\n");
	ftp_socket_recv(socketServer, buff);
	//指定数据类型,A=ASCII, E=EBCDIC, I=binary
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "TYPE I\r\n");
	ftp_socket_recv(socketServer, buff);
	ftp_socket_recv(socketServer, buff);
	//用被动方式向ftp请求连接,ftp返回一个端口,用于传上传文件
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "PASV\r\n");
	ftp_socket_recv(socketServer, buff);

	//解析端口,绑定至 server_data
	ftp_get_data_port(buff, &server_data.sin_port);

	string fail_cwd("550 Can't change directory to");
	string fail_cwd_linux("550 Failed to change directory");

	vector<string> tokens;
	split(string(folder_name), tokens);
	//循环创建目录
	for (auto name : tokens) {
		//进入ftp中的folder_name
		memset(cmd, 0, sizeof(cmd));
		memset(buff, 0, sizeof(buff));
		sprintf(cmd, "CWD %s\r\n", name.c_str());
		ftp_socket_send(socketServer, cmd);
		ftp_socket_recv(socketServer, buff);

		string buff_cmp(buff);
		bool cmp_res = buff_cmp.find(fail_cwd) == std::string::npos;
		bool cmp_res_linux = buff_cmp.find(fail_cwd_linux) == std::string::npos;
		/*
			如果不存在folder_name,则首先进行创建,然后在改变在ftp中工作目录为folder_name
			如果windows下的FTP服务器不存在该目录,则返回的字符串以fail_cwd
			如果是Linux下的FTP不存在该目录,则返回的字符串以fail_cwd_linux开头

			如果目录不存在,首先创建,然后再进入该目录
		*/
		if (!cmp_res || !cmp_res_linux) {
			memset(cmd, 0, sizeof(cmd));
			memset(buff, 0, sizeof(buff));
			sprintf(cmd, "MKD %s\r\n", name.c_str());
			ftp_socket_send(socketServer, cmd);
			ftp_socket_recv(socketServer, buff);

			memset(cmd, 0, sizeof(cmd));
			memset(buff, 0, sizeof(buff));
			sprintf(cmd, "CWD %s\r\n", name.c_str());
			ftp_socket_send(socketServer, cmd);
			ftp_socket_recv(socketServer, buff);
		}
	}
	//在ftp中建立文件,名为file_name
	memset(cmd, 0, sizeof(cmd));
	string stor("STOR ");
	stor.append(file_name);
	stor.append("\r\n");
	//sprintf(cmd, "STOR %s\r\n", file_name);
	ftp_socket_send(socketServer, stor.c_str());
	//通过server_data中的信息建立用于上传文件的连接
	res_connect = connect(socketData, (sockaddr*)&server_data, sizeof(server_data));
	if (res_connect == SOCKET_ERROR)
	{
		int a = WSAGetLastError();
		cout << "error code in connect to FTP dafaFile:" << a << endl;
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	ifstream is(upload_file, ifstream::in | ios::binary);
	if (!is.is_open()) {
		cout << "file not found\n";
		closesocket(socketServer);
		closesocket(socketData);
		WSACleanup();
	}
	char szBuffer[1024] = { 0 };
	//循环读文件,直到读完
	do
	{
		is.read(szBuffer, sizeof(szBuffer));
		int nReadBytes = is.gcount();
		if (nReadBytes <= 0)
		{
			//自己封装的一个装数据的类,将读取到的字节加到此类对象中
			break;
		}
		send(socketData, szBuffer, nReadBytes, 0);
	} while (is.good());
	is.close();
	ftp_socket_recv(socketServer, buff);
	closesocket(socketServer);
	closesocket(socketData);
	WSACleanup();
	printf("ftp_upload [%s] [%s] result: %s\n", upload_file, upload_name, buff);

	return 0;
}
//遍历目录下所有文件
void getFiles(const std::string& path, std::vector<std::string>& files)
{
	//文件句柄  
	intptr_t hFile = 0;
	//文件信息,_finddata_t需要io.h头文件  
	struct _finddata_t fileinfo;
	std::string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			//如果是目录,迭代之  
			//如果不是,加入列表  
			if ((fileinfo.attrib & _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

int main() {

	vector<std::string> filenames;//用来存储文件名
	string path = "E:\\zoom";
	getFiles(path, filenames);
	for (auto file : filenames)
	{
		cout << file << std::endl;
		int beg = file.find_first_of('\\'), end = file.find_last_of('\\');
		string everyFolderName = file.substr(beg, end - beg + 1);
		ftp_upload_file("192.168.159.128", 21, "wzh", "wzh", file.c_str(), file.c_str(), everyFolderName.c_str());
	}
	/*
	string file_name("F:\\geek algorithm\\001.jpg");
	string ftp_file_name("002.jpg");
	string folder_name("/CR7/MESSI/KAKA/");
	ftp_upload_file("192.168.159.128", 21, "wzh", "wzh", file_name.c_str(), ftp_file_name.c_str(), folder_name.c_str());
	*/
	return 0;
}

书接上回,linux版本的代码中提到了一个问题,后面进行思考,应该是通过多次请求端口可以解决一个问题,因为请求一个端口相当于是开辟一个流,然后就导致多个文件写在了一个流里
后面验证之后会更新上来

第一次优化

测试环境:VS2019,
win10专业版自带FTP,Xlight,ubuntu这三个都进行了测试,需要对代码进行修改
在代码的最后面有一个
//回到根目录,以便进行下一次目录的操作
下面会调用两次ftp_socket_recv函数,如果是在windows自带的ftp做测试的话,需要注释掉一次,即只调用一次ftp_socket_recv函数

代码优化,多次请求端口用于数据传输
根据上面的思路,每个文件都需要建立两次连接,一次是建立和FTP服务器的连接,一次是建立用于数据传输的连接,然后再把两次连接断开,实际上三次握手和四次挥手这个动作,一个文件就要进行14次,但是现在只和FTP建立一次连接,多次请求用于数据传输的端口,减少了手接触的次数
所以以我目前的知识进行分析,优化前手的接触次数是14 * numofFile
优化后的次数是7 + 7 * numofFile

#include<WinSock2.h>
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
//#include<cstring>
#include<string>
#include<vector>
#include<fstream>
#include <io.h>  
#include<ctime>
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable : 4996)
using namespace std;

char szBuffer[1024 * 1024 * 10] = { 0 };
const int MAX_LEN = 1024 * 1;
const int MAX_CMD_LEN = 128;
//把str发到socket
static inline int ftp_socket_send(SOCKET& socket, const char* str) {
	int len = send(socket, str, strlen(str), 0);
	if (SOCKET_ERROR == len) {
		cout << "error code in ftp_socket_send" << WSAGetLastError() << endl;
		return -1;
	}
	return len;
}
//接收从socket发来的str
static inline int ftp_socket_recv(SOCKET& socket, char* str) {
	int size = recv(socket, str, MAX_LEN - 1, 0);
	if (SOCKET_ERROR == size) {
		cout << "error code in ftp_socket_recv" << WSAGetLastError() << endl;
		return -1;
	}
	str[size] = 0;
	cout << "ftp recv:" << str << endl;
	return size;
}
//从PASV模式返回的字符串解析出用于数据传输的port
static int ftp_get_data_port(char* buff, USHORT* port)
{
	int i = 0, j = 0;
	short port_l = 0, port_h = 0;

	if (buff == NULL || port == NULL)
	{
		return -1;
	}
	// 227 Entering Passive Mode (192,168,1,101,242,34)\r\n"
	// (192,168,186,1,4,0).
	while (buff[i++] != '(');
	while (j < 4)
	{
		if (buff[i++] == ',')
			j++;
	}

	while (buff[i] != ',')
	{
		port_h *= 10;
		port_h += buff[i] - 0x30;
		i++;
	}

	i++;
	while (buff[i] != ')')
	{
		port_l *= 10;
		port_l += buff[i] - 0x30;
		i++;
	}

	printf("data_port : %u\n", port_h << 8 | port_l);
	*port = htons((short)(port_h << 8 | port_l));

	return 0;
}
//切分多级目录至tokens
void split(const string& s, vector<string>& tokens, const string& delimiters = "\\") {
	string::size_type lastPos = s.find_first_not_of(delimiters, 0);
	string::size_type pos = s.find_first_of(delimiters, lastPos);
	while (string::npos != pos || string::npos != lastPos) {
		tokens.emplace_back(s.substr(lastPos, pos - lastPos));
		lastPos = s.find_first_not_of(delimiters, pos);
		pos = s.find_first_of(delimiters, lastPos);
	}
}

void getFiles(const std::string& path, std::vector<std::string>& files)
{
	//文件句柄  
	intptr_t hFile = 0;
	//文件信息,_finddata_t需要io.h头文件  
	struct _finddata_t fileinfo;
	std::string p;
	if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			//如果是目录,迭代之  
			//如果不是,加入列表  
			if ((fileinfo.attrib & _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("\\").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("\\").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}

/*
	ip:FTP服务器的IP地址
	port:FTP服务器的端口
	user:登录FTP服务器的用户名
	pwd:登录FTP服务器的密码
	folder_path:需要将哪个本地目录上传
*/

int ftp_upload_file_1(const char* ip, unsigned int port, const char* user, const char* pwd,
	string folder_path) {
	char buff[MAX_LEN], cmd[MAX_CMD_LEN];
	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSockMsg;
	int nres = WSAStartup(wdVersion, &wdSockMsg);
	if (nres != 0)
	{
		switch (nres)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库\n");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库\n");
			break;
		case WSAEINPROGRESS:
			printf("请重新启动\n");
			break;
		case WSAEPROCLIM:
			printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源\n");
			break;
		}
		return 0;
	}

	//版本校验,这是因为之前如果我们提供的版本号不对,系统可能会给我们采用默认的版本号,所以需要校验
	if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	//socketServer 创建一个服务器的SOCKET,因为客户端只需要连接服务器,需要明确服务器的一些信息 
	//socketData 通过此变量向FTP推送文件
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socketServer )
	{
		int a = WSAGetLastError();
		cout << "error code in create SOCKET:" << a << endl;
		WSACleanup();
		return 0;
	}

	//server_msg用于绑定FTP服务器,server_data用于绑定用于上传文件的端口
	struct sockaddr_in server_msg, server_data;
	server_msg.sin_family = AF_INET;
	server_msg.sin_port = htons(port);
	server_msg.sin_addr.S_un.S_addr = inet_addr(ip);
	server_data.sin_family = AF_INET;
	server_data.sin_addr.S_un.S_addr = inet_addr(ip);
	int res_connect = connect(socketServer, (const struct sockaddr*)&server_msg, sizeof(server_msg));
	if (res_connect == SOCKET_ERROR)
	{
		int a = WSAGetLastError();
		cout << "error code in connect to FTP server:" << a << endl;
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	//发送用户名至FTP服务器
	memset(cmd, 0, sizeof(cmd));
	memset(buff, 0, sizeof(buff));
	sprintf(cmd, "USER %s\r\n", user);
	ftp_socket_send(socketServer, cmd);
	ftp_socket_recv(socketServer, buff);
	//发送密码至FTP服务器
	memset(cmd, 0, sizeof(cmd));
	memset(buff, 0, sizeof(buff));
	sprintf(cmd, "PASS %s\r\n", pwd);
	ftp_socket_send(socketServer, cmd);
	ftp_socket_recv(socketServer, buff);

	//返回服务器使用的操作系统
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "SYST\r\n");
	ftp_socket_recv(socketServer, buff);
	//指定数据类型,A=ASCII, E=EBCDIC, I=binary
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "TYPE I\r\n");
	ftp_socket_recv(socketServer, buff);
	ftp_socket_recv(socketServer, buff);
	//string fail_cwd("550 Can't change directory to");
	//string fail_cwd_linux("550 Failed to change directory");
	vector<std::string> filenames;//用来存储文件名
	getFiles(folder_path, filenames);
	for (auto file : filenames)
	{
		//file实例E:\\zoom\\folder_1\\123.txt
		SOCKET socketData = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (INVALID_SOCKET == socketData)
		{
			int a = WSAGetLastError();
			cout << "error code in create SOCKET_DataFile:" << a << endl;
			WSACleanup();
			return 0;
		}
		cout << file << std::endl;
		int beg = file.find_first_of('\\'), end = file.find_last_of('\\');
		//everyFolderName示例zoom\\folder_1
		string everyFolderName = file.substr(beg, end - beg + 1);
		
		//用被动方式向ftp请求连接,ftp返回一个端口,用于传上传文件
		memset(buff, 0, sizeof(buff));
		ftp_socket_send(socketServer, "PASV\r\n");
		ftp_socket_recv(socketServer, buff);
		string test_recv(buff);
		while (test_recv.find("Passive") == std::string::npos) {
			ftp_socket_recv(socketServer, buff);
			test_recv = string(buff);
		}
		//解析端口,绑定至 server_data
		ftp_get_data_port(buff, &server_data.sin_port);
		vector<string> tokens;
		split(string(everyFolderName), tokens, "\\");
		//循环创建目录,及上面的zoom\\folder_1,先创建zoom,再创建folder_1
		for (auto name : tokens) {
			//进入ftp中的folder_name
			memset(cmd, 0, sizeof(cmd));
			memset(buff, 0, sizeof(buff));
			sprintf(cmd, "CWD %s\r\n", name.c_str());
			ftp_socket_send(socketServer, cmd);
			ftp_socket_recv(socketServer, buff);

			string buff_cmp(buff);
			//bool cmp_res = buff_cmp.find(fail_cwd) == std::string::npos;
			//bool cmp_res_linux = buff_cmp.find(fail_cwd_linux) == std::string::npos;
			bool cmp_final = (buff_cmp.find("550") != std::string::npos) && (buff_cmp.find("change directory") != std::string::npos);
			/*
				如果不存在folder_name,则首先进行创建,然后在改变在ftp中工作目录为folder_name
				如果windows下的FTP服务器不存在该目录,则返回的字符串以fail_cwd
				如果是Linux下的FTP不存在该目录,则返回的字符串以fail_cwd_linux开头

				如果目录不存在,首先创建,然后再进入该目录
			*/
			if (cmp_final) {
				memset(cmd, 0, sizeof(cmd));
				memset(buff, 0, sizeof(buff));
				sprintf(cmd, "MKD %s\r\n", name.c_str());
				ftp_socket_send(socketServer, cmd);
				ftp_socket_recv(socketServer, buff);

				memset(cmd, 0, sizeof(cmd));
				memset(buff, 0, sizeof(buff));
				sprintf(cmd, "CWD %s\r\n", name.c_str());
				ftp_socket_send(socketServer, cmd);
				ftp_socket_recv(socketServer, buff);
			}
		}

		//在ftp中建立文件,名为上面的123.txt
		memset(cmd, 0, sizeof(cmd));
		string stor("STOR ");
		stor.append(file.substr(end).c_str());
		stor.append("\r\n");
		//sprintf(cmd, "STOR %s\r\n", file_name);
		ftp_socket_send(socketServer, stor.c_str());
		//通过server_data中的信息建立用于上传文件的连接
		res_connect = connect(socketData, (sockaddr*)&server_data, sizeof(server_data));
		if (res_connect == SOCKET_ERROR)
		{
			int a = WSAGetLastError();
			cout << "error code in connect to FTP dafaFile:" << a << endl;
			closesocket(socketServer);
			WSACleanup();
			return 0;
		}
		ifstream is(file.c_str(), ifstream::in | ios::binary);
		if (!is.is_open()) {
			cout << "file not found\n";
			closesocket(socketServer);
			closesocket(socketData);
			WSACleanup();
			continue;
		}
		
		do
		{
			is.read(szBuffer, sizeof(szBuffer));
			int nReadBytes = is.gcount();
			if (nReadBytes <= 0)
			{
				//自己封装的一个装数据的类,将读取到的字节加到此类对象中
				break;
			}
			send(socketData, szBuffer, nReadBytes, 0);
		} while (is.good());
		is.close();
		ftp_socket_recv(socketServer, buff);
		//回到根目录,以便进行下一次目录的操作
		memset(cmd, 0, sizeof(cmd));
		memset(buff, 0, sizeof(buff));
		sprintf(cmd, "CWD %s\r\n", "/");
		ftp_socket_send(socketServer, cmd);
		ftp_socket_recv(socketServer, buff);
		closesocket(socketData);
	}
	closesocket(socketServer);
	WSACleanup();
	printf("ftp_upload completed\n");
	return 0;
}

int main() {
	
	vector<std::string> filenames;//用来存储文件名
	string path = "E:\\zoom";
	clock_t end, begin;
	begin = clock();
	ftp_upload_file_1("192.168.75.1", 21, "wzh", "wzh", path);
	end = clock();
	cout << "time consumed:" << (double)(end - begin) / CLOCKS_PER_SEC << endl;
	return 0;
}

测试

本地目录结构,每个folder里都有两个txt文件,每个文件10M,优化后代码耗时在八秒左右,优化前大概9.3秒
在这里插入图片描述

第二次优化

此次优化主要为对代码结构的优化,然后解决了一些已知的霸哥(bug),当然可能由于个人水平有限,所以可能问题定位不太准确,但是也是解决了。(问题是在多次调用的时候,如果间隔太短,那么在调用recv函数的时候,会收到10054的错误码,根据网上的说法,进行了setsockopt设置,而且之前的全局变量会发生重定义的连接错误,所以稍微规范了一下写法,将声明和定义分开)
目前已循环调用5400次,未出现问题。
头文件FtpTool.h

#pragma once
#include<WinSock2.h>
#include<iostream>
#include<string>
#include<vector>
#include<fstream>
#include<io.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma warning(disable : 4996)

extern char szBuffer[1024 * 1024 * 10];
const int MAX_LEN = 1024 * 1;
const int MAX_CMD_LEN = 128;

//from path put every file to vector
void getFiles(const std::string& path, std::vector<std::string>& files);
//send str to socket
inline int ftp_socket_send(SOCKET& socket, const char* str);
//recv str from socket
inline int ftp_socket_recv(SOCKET& socket, char* str);
inline int ftp_socket_recv2(SOCKET& socket, char* str);
//set port from buff
int ftp_get_data_port(char* buff, USHORT* port);
//according delimiters to sp
void split(const std::string& s, std::vector<std::string>& tokens, const std::string& delimiters = "\\");
// ftp_upload_file_1
int ftp_upload_file_1(const char* ip, unsigned int port, const char* user, const char* pwd, std::string folder_path);

源文件FtpTool.cpp

/*
 * @Author: windofbarcelona
 * @Date: 2020-12-11 16:19:22
 * @LastEditTime: 2020-12-21 10:33:49
 * @LastEditors: windofbarcelona
 * @Description: In User Settings Edit
 */

#include"FtpTool.h"
using std::string;
using std::cout;
using std::ifstream;
using std::vector;
using std::ios;

char szBuffer[1024 * 1024 * 10] = { 0 };


/*
path: 需要遍历的目录
files: 遍历后的结果
*/
void getFiles(const std::string& path, std::vector<std::string>& files)
{
	//文件句柄  
	intptr_t hFile = 0;
	//文件信息,_finddata_t需要io.h头文件  
	struct _finddata_t fileinfo;
	std::string p;
	if ((hFile = _findfirst(p.assign(path).append("/*").c_str(), &fileinfo)) != -1)
	{
		do
		{
			//如果是目录,迭代之  
			//如果不是,加入列表  
			if ((fileinfo.attrib & _A_SUBDIR))
			{
				if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
					getFiles(p.assign(path).append("/").append(fileinfo.name), files);
			}
			else
			{
				files.push_back(p.assign(path).append("/").append(fileinfo.name));
			}
		} while (_findnext(hFile, &fileinfo) == 0);
		_findclose(hFile);
	}
}
//send str to socket
static inline int ftp_socket_send(SOCKET& socket, const char* str) {
	int len = send(socket, str, strlen(str), 0);
	if (SOCKET_ERROR == len) {
		cout << "error code in ftp_socket_send" << WSAGetLastError() << std::endl;
		return -1;
	}
	return len;
}
//接收从socket发来的str
static inline int ftp_socket_recv(SOCKET& socket, char* str) {
	int size = recv(socket, str, MAX_LEN - 1, 0);
	if (SOCKET_ERROR == size) {
		cout << "error code in ftp_socket_recv" << WSAGetLastError() << std::endl;
		return -1;
	}
	str[size] = 0;
	//cout << "ftp recv:" << str << std::endl;
	return size;
}

//接收从socket发来的str
static inline int ftp_socket_recv2(SOCKET& socket, char* str) {
	int size = recv(socket, str, MAX_LEN - 1, 0);
	if (SOCKET_ERROR == size) {
		cout << "error code in ftp_socket_recv2" << WSAGetLastError() << std::endl;
		return -1;
	}
	str[size] = 0;
	//cout << "ftp recv:" << str << std::endl;
	return size;
}
//从PASV模式返回的字符串解析出用于数据传输的port
int ftp_get_data_port(char* buff, USHORT* port)
{
	int i = 0, j = 0;
	short port_l = 0, port_h = 0;

	if (buff == NULL || port == NULL)
	{
		return -1;
	}
	// 227 Entering Passive Mode (192,168,1,101,242,34)\r\n"
	// (192,168,186,1,4,0).
	while (buff[i++] != '(');
	while (j < 4)
	{
		if (buff[i++] == ',')
			j++;
	}

	while (buff[i] != ',')
	{
		port_h *= 10;
		port_h += buff[i] - 0x30;
		i++;
	}

	i++;
	while (buff[i] != ')')
	{
		port_l *= 10;
		port_l += buff[i] - 0x30;
		i++;
	}

	//printf("data_port : %u\n", port_h << 8 | port_l);
	*port = htons((short)(port_h << 8 | port_l));

	return 0;
}
//切分多级目录至tokens
void split(const string& s, vector<string>& tokens, const string& delimiters) {
	string::size_type lastPos = s.find_first_not_of(delimiters, 0);
	string::size_type pos = s.find_first_of(delimiters, lastPos);
	while (string::npos != pos || string::npos != lastPos) {
		tokens.emplace_back(s.substr(lastPos, pos - lastPos));
		lastPos = s.find_first_not_of(delimiters, pos);
		pos = s.find_first_of(delimiters, lastPos);
	}
}
/*
	ip:FTP服务器的IP地址
	port:FTP服务器的端口
	user:登录FTP服务器的用户名
	pwd:登录FTP服务器的密码
	folder_path:需要上传的本地文件夹
*/

int ftp_upload_file_1(const char* ip, unsigned int port, const char* user, const char* pwd,
	string folder_path) {
	char buff[MAX_LEN], cmd[MAX_CMD_LEN];
	WORD wdVersion = MAKEWORD(2, 2);
	WSADATA wdSockMsg;
	int nres = WSAStartup(wdVersion, &wdSockMsg);
	if (nres != 0)
	{
		switch (nres)
		{
		case WSASYSNOTREADY:
			printf("重启下电脑试试,或者检查网络库\n");
			break;
		case WSAVERNOTSUPPORTED:
			printf("请更新网络库\n");
			break;
		case WSAEINPROGRESS:
			printf("请重新启动\n");
			break;
		case WSAEPROCLIM:
			printf("请尝试关掉不必要的软件,以为当前网络运行提供充足资源\n");
			break;
		}
		return 0;
	}

	//版本校验,这是因为之前如果我们提供的版本号不对,系统可能会给我们采用默认的版本号,所以需要校验
	if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2)
	{
		WSACleanup();
		return 0;
	}

	//socketServer 创建一个服务器的SOCKET,因为客户端只需要连接服务器,需要明确服务器的一些信息 
	//socketData 通过此变量向FTP推送文件
	SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == socketServer)
	{
		int a = WSAGetLastError();
		std::cout << "error code in create SOCKET:" << a << std::endl;
		WSACleanup();
		return 0;
	}
	bool isReuseaddr = true;
	setsockopt(socketServer, SOL_SOCKET, SO_REUSEADDR, (const char*)&isReuseaddr, sizeof(bool));
	struct linger so_linger;
	so_linger.l_linger = 0;
	so_linger.l_onoff = true;
	setsockopt(socketServer, SOL_SOCKET, SO_LINGER, (const char*)&so_linger, sizeof(so_linger));

	//server_msg用于绑定FTP服务器,server_data用于绑定用于上传文件的端口
	struct sockaddr_in server_msg, server_data;
	server_msg.sin_family = AF_INET;
	server_msg.sin_port = htons(port);
	server_msg.sin_addr.S_un.S_addr = inet_addr(ip);
	server_data.sin_family = AF_INET;
	server_data.sin_addr.S_un.S_addr = inet_addr(ip);
	int res_connect = connect(socketServer, (const struct sockaddr*)&server_msg, sizeof(server_msg));
	if (res_connect == SOCKET_ERROR)
	{
		int a = WSAGetLastError();
		std::cout << "error code in connect to FTP server:" << a << std::endl;
		closesocket(socketServer);
		WSACleanup();
		return 0;
	}
	//发送用户名至FTP服务器
	memset(cmd, 0, sizeof(cmd));
	memset(buff, 0, sizeof(buff));
	sprintf(cmd, "USER %s\r\n", user);
	ftp_socket_send(socketServer, cmd);
	ftp_socket_recv(socketServer, buff);
	//发送密码至FTP服务器
	memset(cmd, 0, sizeof(cmd));
	memset(buff, 0, sizeof(buff));
	sprintf(cmd, "PASS %s\r\n", pwd);
	ftp_socket_send(socketServer, cmd);
	ftp_socket_recv(socketServer, buff);

	//返回服务器使用的操作系统
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "SYST\r\n");
	ftp_socket_recv(socketServer, buff);
	//指定数据类型,A=ASCII, E=EBCDIC, I=binary
	memset(buff, 0, sizeof(buff));
	ftp_socket_send(socketServer, "TYPE I\r\n");
	ftp_socket_recv(socketServer, buff);
	ftp_socket_recv(socketServer, buff);
	vector<std::string> filenames;//用来存储文件名

	getFiles(folder_path, filenames);
	for (auto file : filenames)
	{
		SOCKET socketData = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (INVALID_SOCKET == socketData)
		{
			int a = WSAGetLastError();
			std::cout << "error code in create SOCKET_DataFile:" << a << std::endl;
			WSACleanup();
			return 0;
		}
		cout << file << std::endl;
		int end = file.find_last_of('/');
		string everyFolderName = file.substr(0, end);

		//用被动方式向ftp请求连接,ftp返回一个端口,用于传上传文件
		memset(buff, 0, sizeof(buff));
		ftp_socket_send(socketServer, "PASV\r\n");
		ftp_socket_recv(socketServer, buff);
		string test_recv(buff);
		while (test_recv.find("Passive") == std::string::npos) {
			ftp_socket_recv(socketServer, buff);
			test_recv = string(buff);
		}
		//解析端口,绑定至 server_data
		ftp_get_data_port(buff, &server_data.sin_port);
		vector<string> tokens;
		split(string(everyFolderName), tokens, "/");

		memset(cmd, 0, sizeof(cmd));
		memset(buff, 0, sizeof(buff));
		sprintf(cmd, "PWD \r\n");
		ftp_socket_send(socketServer, cmd);
		ftp_socket_recv(socketServer, buff);
		//循环创建目录
		for (auto name : tokens) {
			//进入ftp中的folder_name
			memset(cmd, 0, sizeof(cmd));
			memset(buff, 0, sizeof(buff));
			sprintf(cmd, "CWD %s\r\n", name.c_str());
			ftp_socket_send(socketServer, cmd);
			ftp_socket_recv(socketServer, buff);

			string buff_cmp(buff);
			bool cmp_final = (buff_cmp.find("550") != std::string::npos) || (buff_cmp.find("change directory") != std::string::npos);
			if (cmp_final) {
				memset(cmd, 0, sizeof(cmd));
				memset(buff, 0, sizeof(buff));
				sprintf(cmd, "MKD %s\r\n", name.c_str());
				ftp_socket_send(socketServer, cmd);
				ftp_socket_recv(socketServer, buff);

				memset(cmd, 0, sizeof(cmd));
				memset(buff, 0, sizeof(buff));
				sprintf(cmd, "CWD %s\r\n", name.c_str());
				ftp_socket_send(socketServer, cmd);
				ftp_socket_recv(socketServer, buff);
			}
		}

		//在ftp中建立文件,名为file_name
		memset(cmd, 0, sizeof(cmd));
		string stor("STOR ");
		stor.append(file.substr(end + 1).c_str());
		stor.append("\r\n");
		//sprintf(cmd, "STOR %s\r\n", file_name);
		ftp_socket_send(socketServer, stor.c_str());
		//通过server_data中的信息建立用于上传文件的连接
		res_connect = connect(socketData, (sockaddr*)&server_data, sizeof(server_data));
		if (res_connect == SOCKET_ERROR)
		{
			int a = WSAGetLastError();
			std::cout << "error code in connect to FTP dafaFile:" << a << std::endl;
			closesocket(socketServer);
			WSACleanup();
			return 0;
		}
		ifstream is(file.c_str(), ifstream::in | ios::binary);
		if (!is.is_open()) {
			cout << "file not found\n";
			closesocket(socketServer);
			closesocket(socketData);
			WSACleanup();
			continue;
		}
		do
		{
			is.read(szBuffer, sizeof(szBuffer));
			int nReadBytes = is.gcount();
			if (nReadBytes <= 0)
			{
				//自己封装的一个装数据的类,将读取到的字节加到此类对象中
				break;
			}
			send(socketData, szBuffer, nReadBytes, 0);
		} while (is.good());
		is.close();
		ftp_socket_recv(socketServer, buff);
		memset(cmd, 0, sizeof(cmd));
		memset(buff, 0, sizeof(buff));
		sprintf(cmd, "CWD %s\r\n", "/");
		ftp_socket_send(socketServer, cmd);
		//ftp_socket_recv(socketServer, buff);
		closesocket(socketData);
	}
	closesocket(socketServer);
	WSACleanup();
	printf("ftp_upload completed\n");
	return 0;
}

测试程序

#include<iostream>
#include<string>
#include"FtpTool.h"
using namespace std;
int main() {
	std::ofstream fout("E:\\C++project\\ftptest.txt", std::ios::app);
	int i = 1;
	while (1) {
		cout << "loop times:" << i << endl;
		fout << "loop times:" << i++ << endl;
		ftp_upload_file_1("192.168.1.103", 21, "ftpadmin", "123", "/zoom");
		Sleep(5000);
	}
	return 0;
}

版权声明:本文为qq_36437446原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。