Прежде всего, я предлагаю вам прекратить использовать Dev-C++. Он действительно устарел, и последняя бета была опубликована более 10 лет назад:
21 февраля 2005 г.: выпуск Dev-C++ 5 Beta 9.2 (4.9.9.2)!
Вам следует попробовать пакет NetBeans C/C++ и компилятор/компоновщик (gcc/g++/make) из Cygwin. Дополнительные сведения о настройке такой среды разработки см. по этой ссылке здесь. Также вы можете попробовать Eclipse CDT или даже Visual Studio Express или новейшая Сообщество Visual Studio.
Теперь ваша программа и код.
Хотя ваша программа работает, я обнаружил несколько мелких ошибок, из-за которых она не работала должным образом, когда скомпилировала и запустила ее.
Вы хотите загрузить файл на FTP-сервер и сохранить его с уникальным именем файла по удаленному пути.
Вы можете просто использовать STOU
команду ftp вместо STOR
и ftp-сервер сохранит файл с уникальным именем в текущем рабочем каталоге. Итак, вам придется изменить процедуру sendFileRequest
, чтобы использовать CWD
для перехода в каталог, в который мы хотим сохранить файл, а затем просто использовать STOU
без каких-либо параметров, а сервер обработает все остальное и сохранит с уникальным именем файла. Например, сервер Pure-FTP создает такое имя файла, как pureftpd.583c3777.dd.0000
.
Процедура sendFileRequest
станет такой:
void sendFileRequest(SOCKET _FSoc){
char cwdCmd[MAX_PATH] = "CWD /web/share/tmp/\r\n";
send(_FSoc, cwdCmd, strlen(cwdCmd), 0);
Sleep(1000);
//char stor[] = "STOR /web/share/tmp/test.txt\r\n";
char stor[] = "STOU\r\n";
send(_FSoc, stor, strlen(stor), 0);
Sleep(1000);
}
Осторожно, некоторые ftp-серверы не поддерживают команду STOU
.
Теперь, если вы хотите контролировать имена файлов и генерировать имена на основе некоторых критериев, вам нужно будет прочитать список файлов в каталоге, в который вы хотите загрузить файл, а затем создать уникальное имя файла, которого нет в списке. файлов удаленного каталога.
Мы хотим выполнить команду ftp, чтобы вернуть имена файлов текущего каталога. Команда NLST
— это то, что нам нужно, поскольку она отправляет буфер, содержащий текстовые данные. с разделителями перевода строки для каждого файла; список всех имен файлов/папок (только имена файлов, без данных) в текущем рабочем каталоге. Нам также необходимо открыть соединение для передачи данных в пассивном режиме для получения этих данных. Также я иногда замечал, что хотя файл был создан с уникальным именем пути к удаленному серверу, данные вообще не передавались. Это происходит из-за того, что я выполнял программу очень быстро, а предыдущие сеансы ftp не закрывались должным образом с помощью QUIT
команда. Когда я добавил команду QUIT
в конце процесса, все проблемы с передачей файлов с нулевыми данными исчезли.
Проблемы и замечания на уровне кода
Я изменил TYPE I
image/binary на TYPE A
text, потому что большинство ftp-серверов исправляют перевод строки CR LF
при передаче текстовых файлов из формата unix в формат windows и наоборот.
Вы должны всегда закрывать подключения к данным с помощью shutdown
и/или closesocket
к соединению сокета данных, иначе сервер может прервать передачу ваших данных.
У вас есть функция для преобразования std::string
в целое число, но вы также можете использовать std::stoi
, если вы укажете компилятору использовать стандарты C++11 с помощью параметра -std=c++11
.
Когда вы используете recv
для чтения ответа сервера, вы должны всегда обнулять строку буфера ответа, используя возвращаемое значение recv
, или обрабатывать только то количество байтов, которое возвращает recv
.
Программа не имеет поведения чтения состояния, что может привести к множеству ошибок и полному сбою. Правильное поведение ftp-приложения всегда должно зависеть от ответов, которые сервер отправляет на командное соединение. Например, если ftp-сервер достиг максимального количества клиентов, ваша программа не будет знать об этом и будет продолжать выдавать команды до тех пор, пока не выйдет из строя или не завершится. Программа не должна и не может продолжать работу при возникновении таких ошибок.
Параметры компилятора
-std=c++11
за std::stoi
Параметры компоновщика
-lws2_32
для включения cygwin libws2_32.a
, необходимого для winsock2.h
Псевдокод
- Инициализировать подключение команды ftp-сервера
- Отправить данные для входа на фтп
- Generate unique filename
- Read the remote directory filenames
- Генерировать случайные имена, пока у нас не будет имени в списке файлов
- Отправить файл с уникальным именем файла на сервер
Для уникального имени файла я просто использовал массив символов, заполненный символами [A-Za-z0-0]
. Вы также можете выбрать, будете ли вы использовать расширение локального файла для проверки и добавить его в конец уникального имени файла:
string generateUniqueFilename(SOCKET _FSoc,
int port,
string remotePath,
bool useLocalFileExtension,
string localFile)
Например, установка useLocalFileExtension
на true
при наличии локального файла .txt
приведет к уникальному имени файла с расширением .txt
O3xh8p939YN4hV.txt
.
Моя последняя программа
#include <winsock2.h>
#include <windows.h>
#include <sstream>
#include <iostream>
#include <stdio.h>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <vector>
#define REMOTE_FTP_HOST_IP "x.x.x.x"
#define REMOTE_FTP_USERNAME "username"
#define REMOTE_FTP_PASSWORD "password"
#define ECHO_FTP_RESPONSE 1 // Set to 0 to disable ftp responses output
using namespace std;
// Declarations ---------------------------------------------------
void echoResponse(string);
void echoResponse(LPSTR);
void recvResponseAndEcho(SOCKET, LPSTR, int);
void recvResponseAndEcho(SOCKET);
string generateRandomFilename(int);
int readSocketData(int, LPSTR, int);
string generateUniqueFilename(SOCKET, int, string, bool, string);
void sendLogIn(SOCKET socket);
void sendFileRequest(SOCKET, string, string);
void sendTypeICmd(SOCKET);
void sendTypeACmd(SOCKET);
int sendPasvCmd(SOCKET);
void sendQuitCmd(SOCKET);
BOOL ftpSocket(string, int);
int sendFile(string, string);
// Program entry point --------------------------------------------
int main(){
sendFile("C:\\test.txt", "/remote/ftp/path/");
return EXIT_SUCCESS;
}
void echoResponse(string response) {
if(ECHO_FTP_RESPONSE) {
string respclean = response.erase(response.find_last_not_of(" \t\n\r") + 1);
cout << respclean << endl;
}
}
void echoResponse(LPSTR response) {
if(ECHO_FTP_RESPONSE) {
string respstr = response;
echoResponse(respstr);
}
}
void recvResponseAndEcho(SOCKET socket, LPSTR buffer, int bufsize) {
int nread = recv(socket, buffer, bufsize, 0);
*(buffer + nread) = 0;
echoResponse(buffer);
}
void recvResponseAndEcho(SOCKET socket) {
char srvResponse[4096];
recvResponseAndEcho(socket, (LPSTR)&srvResponse, sizeof(srvResponse));
}
// Generate o random filename using an array of charactres [A-Za-z0-9]
string generateRandomFilename(int length) {
string rndFilename;
char fileChars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789";
int fileCharsLength = strlen(fileChars);
srand(time(NULL));
for(int i = 0; i < length; i++)
rndFilename += fileChars[rand() % fileCharsLength];
return rndFilename;
}
int readSocketData(int port, LPSTR buffer, int buffer_size) {
SOCKET sock;
SOCKADDR_IN pasvserver;
int connectionerror2;
int trycount2 = 2;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock == INVALID_SOCKET){
WSACleanup();
return 0;
}
pasvserver.sin_family = AF_INET;
pasvserver.sin_port = htons(port);
pasvserver.sin_addr.s_addr = inet_addr(REMOTE_FTP_HOST_IP);
connectionerror2 = connect(sock, (LPSOCKADDR)&pasvserver, sizeof(struct sockaddr));
while(connectionerror2 == SOCKET_ERROR){
connectionerror2 = connect(sock, (LPSOCKADDR)&pasvserver, sizeof(struct sockaddr));
trycount2++;
if(trycount2 = 10) {
closesocket(sock);
WSACleanup();
return 0;
}
}
int result = recv(sock, buffer, buffer_size, 0);
shutdown(sock, SD_BOTH);
closesocket(sock);
return result;
}
string generateUniqueFilename(SOCKET _FSoc, int port, string remotePath, bool useLocalFileExtension, string localFile) {
string newFilename;
char cwd[MAX_PATH];
char cmd[7] = "NLST\r\n";
char listFilesBuffer[4096];
char servermessage[2048];
int listFilesBufferReadLength;
strcpy(cwd, "CWD ");
strcat(cwd, remotePath.c_str());
strcat(cwd, "\r\n");
send(_FSoc, cwd, strlen(cwd), 0);
Sleep(1000);
recvResponseAndEcho(_FSoc, (LPSTR)&servermessage, sizeof(servermessage));
Sleep(1000);
send(_FSoc, cmd, strlen(cmd), 0);
listFilesBufferReadLength = readSocketData(port, listFilesBuffer, 4096);
if(listFilesBufferReadLength > 0) {
std::istringstream fileLines(listFilesBuffer);
string fileLine, localFileExtension;
std::vector<std::string> filesList;
while (std::getline(fileLines, fileLine)) {
// Trim whitespaces from each line
fileLine = fileLine.erase(fileLine.find_last_not_of(" \t\n\r") + 1);
if(fileLine != "." && fileLine != "..")
// Save to a vector or strings if it's not current and level up directory
filesList.push_back(fileLine);
}
if(useLocalFileExtension) {
int dotPos = localFile.find_last_of(".");
localFileExtension = dotPos > 0 ? localFile.substr(dotPos) : "";
}
vector<string>::iterator fileFind;
do {
// Generate random filenames of 14 characters length
// untill the filename does not exist on the remote FTP server path
newFilename = generateRandomFilename(14);
if(useLocalFileExtension)
newFilename += localFileExtension;
} while((fileFind = std::find(filesList.begin(), filesList.end(), newFilename)) != filesList.end());
}
return newFilename;
}
void sendLogIn(SOCKET socket) {
char userCmd[128] = "USER ";
char passCmd[128] = "PASS ";
strcat(userCmd, REMOTE_FTP_USERNAME);
strcat(userCmd, "\r\n");
send(socket, userCmd, strlen(userCmd), 0);
recvResponseAndEcho(socket);
strcat(passCmd, REMOTE_FTP_PASSWORD);
strcat(passCmd, "\r\n");
send(socket, passCmd, strlen(passCmd), 0);
recvResponseAndEcho(socket);
}
void sendFileRequest(SOCKET socket, string remoteDirectory, string storFilename) {
char cwdCmd[MAX_PATH] = "CWD ";
char storCmd[MAX_PATH] = "STOR ";
//char stouCmd[] = "STOU\r\n";
// Change current working directory
strcat(cwdCmd, remoteDirectory.c_str());
strcat(cwdCmd, "\r\n");
send(socket, cwdCmd, strlen(cwdCmd), 0);
Sleep(1000);
recvResponseAndEcho(socket);
// Send stor ftp command
strcat(storCmd, storFilename.c_str());
strcat(storCmd, "\r\n");
send(socket, storCmd, strlen(storCmd), 0);
// Send stou ftp command insted of stor
//send(socket, stouCmd, strlen(stouCmd), 0);
}
// Image/binary type
void sendTypeICmd(SOCKET socket) {
char typeCmd[] = "TYPE I\r\n";
send(socket, typeCmd, strlen(typeCmd), 0);
Sleep(1000);
recvResponseAndEcho(socket);
}
// Text type
void sendTypeACmd(SOCKET socket) {
char typeCmd[] = "TYPE A\r\n";
send(socket, typeCmd, strlen(typeCmd), 0);
Sleep(1000);
recvResponseAndEcho(socket);
}
int sendPasvCmd(SOCKET socket) {
int result = -1;
char pasvCmd[] = "PASV\r\n";
char srvResponse[4096];
send(socket, pasvCmd, strlen(pasvCmd), 0);
Sleep(1000);
recvResponseAndEcho(socket, (LPSTR)&srvResponse, sizeof(srvResponse));
// Read the pasv response, something like:
// 227 Entering Passive Mode (x,x,x,x,y,y)
// where x are the ip bytes and y are the port bytes
string pasvMessage = srvResponse;
int modeStart = pasvMessage.find("Mode");
if(modeStart > 0) {
int parenthesisStart = pasvMessage.find("(", modeStart);
if(parenthesisStart > 0) {
string currentPasvByte;
char currentChar;
int currentCharPos = parenthesisStart,
currentPasvByteIndex = 0,
highByte = -1,
lowByte = -1;
do {
currentChar = pasvMessage[++currentCharPos];
if(currentChar == ',' || currentChar == ')') {
if(currentPasvByteIndex == 4)
highByte = stoi(currentPasvByte);
else if(currentPasvByteIndex == 5)
lowByte = stoi(currentPasvByte);
currentPasvByteIndex++;
currentPasvByte = "";
} else
currentPasvByte += currentChar;
} while(currentChar != ')');
// Assemble the port number by joing hi and lo port number bytes
if(highByte != -1 && lowByte != -1)
result = (highByte << 8) | lowByte;
}
}
return result;
}
void sendQuitCmd(SOCKET socket) {
char quitCmd[] = "QUIT\r\n";
send(socket, quitCmd, strlen(quitCmd), 0);
recvResponseAndEcho(socket);
}
BOOL ftpSocket(string localFile, int port) {
BOOL result = false;
SOCKET sock;
SOCKADDR_IN pasvserver;
int connectionerror2;
int trycount2 = 2;
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock == INVALID_SOCKET){
WSACleanup();
return 0;
}
pasvserver.sin_family = AF_INET;
pasvserver.sin_port = htons(port);
pasvserver.sin_addr.s_addr = inet_addr(REMOTE_FTP_HOST_IP); //Once again the drivehq ftp server
connectionerror2 = connect(sock, (LPSOCKADDR)&pasvserver, sizeof(struct sockaddr));
while(connectionerror2 == SOCKET_ERROR){
connectionerror2 = connect(sock, (LPSOCKADDR)&pasvserver, sizeof(struct sockaddr));
trycount2++;
if(trycount2 = 10) {
closesocket(sock);
WSACleanup();
return false;
}
}
HANDLE hFile = CreateFile(localFile.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);;
if(hFile != INVALID_HANDLE_VALUE) {
DWORD read;
char buffer[4096];
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
while(ReadFile(hFile, buffer, 4096, &read, NULL) && read > 0) {
send(sock, buffer, read, 0);
}
CloseHandle(hFile);
result = true;
}
shutdown(sock, SD_BOTH);
closesocket(sock);
return result;
}
int sendFile(string localFile, string remotePath) {
FreeConsole();
WSAData WData;
SOCKET FSoc;
SOCKADDR_IN server;
int connectionerror;
int trycount = 2;
int port;
WSAStartup(MAKEWORD(2,2), &WData);
FSoc = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(FSoc == INVALID_SOCKET){
WSACleanup();
return 0;
}
server.sin_family = AF_INET;
server.sin_port = htons(21);
server.sin_addr.s_addr = inet_addr(REMOTE_FTP_HOST_IP); //this is the drivehq ftp server address.
connectionerror = connect(FSoc, (LPSOCKADDR)&server, sizeof(struct sockaddr));
while(connectionerror == SOCKET_ERROR){
connectionerror = connect(FSoc, (LPSOCKADDR)&server, sizeof(struct sockaddr));
trycount++;
if(trycount = 10){
closesocket(FSoc);
WSACleanup();
return 0;
}
}
recvResponseAndEcho(FSoc);
sendLogIn(FSoc);
Sleep(1000); //give the server and the client sometime to deal with the influx of new messages
//so that data for the ip doesnt get mixed up.
// Send typea command to transfer text data.
// It works better for windows to unix text files transfer, bacause it fixes
// the line endnings CR, LF on most ftp servers
// Use typei command for binary data.
//sendTypeACmd(FSoc);
sendTypeACmd(FSoc);
// Open passing mode for the files list data
port = sendPasvCmd(FSoc);
// Generate a unique filename that does not exists on the FTP remote directory
string filename = generateUniqueFilename(FSoc, port, remotePath, true, localFile);
// Open passing mode for the file transfer data
port = sendPasvCmd(FSoc);
sendFileRequest(FSoc, remotePath, filename);
ftpSocket(localFile, port);
recvResponseAndEcho(FSoc);
sendQuitCmd(FSoc);
shutdown(FSoc, SD_BOTH);
closesocket(FSoc);
WSACleanup();
return 0;
}
person
Christos Lytras
schedule
28.11.2016