原始版本
测试环境: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;
}