• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Пишем свой FTP клиент на С++

Dido

HDD-drive
Пользователь
Регистрация
06.01.2021
Сообщения
35
Реакции
34
Перед тем как погрузимся в пучины сетевых app, сперва надо обозначить пару вещей:

  1. - Должны понимать и знать ООП С++ и работу с функциями (забегая на перед, здесь будет все реализовано в процедурном темпе)
  2. - Знание TCP/IP протоколов и принципа их работы Читать
  3. - Понимание и знание "Сокетов" Читать

Весь код будет разбит по кускам в виде функций и подробным описанием каждой части кода. Собрать и скомпилить уже сможете и сами.
И так, FTP клиент юзает сразу два сокета ( один для конекта данных, другой для управления этим конектом). С помощью управляющей части передаются команды серверу и считываются ответы, а по коннекту мы просто передаем данные, файлы, списки файлов и много чего другого.


Как всегда, для старта мы начнем подключать препроцесорные файлы заголовков. Да сразу обозначимся, мы будем пилить его под Windows.


Header-files
C++:
#include <winsock2.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
Кроме них, нам надо залезть в VS и в свойствах проекта - настройка компоновщика подключить еще : Ws2_32.lib and Wsock32.lib

Следующий уровень, мы устанавливаем управляющее соединение. Чтобы это сделать, мы запилим функцию init_Sock(). Конект будем воплозать по localhost ftp (127.0.0.1) на порт 21 (стандартный).


Функция init_Sock()
C++:
int init_Sock() 
{
    int len;
    sockaddr_in address;
    int result;
    int s;
    s = socket(AF_INET, SOCK_STREAM,0); //создаем сокет
    address.sin_family = AF_INET;   //интернет домен; здесь же описываем все семейство сокета
    address.sin_addr.s_addr = inet_addr("127.0.0.1");   //соединяемся с 127-0-0-1
    address.sin_port = htons(21);    // 21 порт для конекта
    len = sizeof(address); // получаем размер
    result = connect(s, (sockaddr *)&address, len);   //установка соединения
    if (result == -1) 
    {
        сerr("Увы: клиент офф");
        return -1;
    }
    return s; // все гуд, возвращем готовый и чистый сокет
}

Ура, мы установили управляющий конект с серваком. Сейчас надо будет получить и считать ответ сервера. И в этом нам поможет функция select(), которая будет чекать наличие валидных данных в потоке: А вот функция recv() - будет ждать получение данных из потока!

Функция readServerResponse()
C++:
int readServerResponse(int s)  // передаем сокет
{
    int rc;
    fd_set fdr;
    FD_ZERO(&fdr);
    FD_SET(s,&fdr);
    timeval timeout; // запилим структуру времени 
    timeout.tv_sec = 2;   // и зададим зна. 2 сек, к примеру
    timeout.tv_usec = 0;  
    do {
        char buff[512] ={' '}; // размер буффера 512
        recv(s,&buff,512,0);   //получаем данные из потока
        cout << buff;
        rc = select(s+1,&fdr,NULL,NULL,&timeout);  //ждём данные для чтения в потоке 2 сек.
    } while(rc);     //проверяем результат на валид
    return 2;
}

В данный момент наш клиент может посылать команды и считывать ответ. Вот к примеру в main мы можете задать такой алгоритм:

Функция tempMain:
C++:
int main() 
{
   int mySocket;
   mySocket = init_sock(); // сделали сокет
   readServerResponse(mySocket); //  передали его в нашу функцию и получили ответ
   close(mySocket);  //закрыли соединения по правилам этика 
   return 0;
   }
9c079c0ea5dcacbf60e4a93befbc3fc8.jpg


Ну чем я не программист, просите вы после этого.
Но нам мало, мы хотим больше и лучше. Так давайте запилим к созданию данных для соединения. Для этого мы реализуем функцию init_Data_Connect():


Функция init_Data_Connect:
C++:
int init_Data_Connect() 
{
    send(s,"PASV\r\n",strlen("PASV\r\n"),0);
    char buff[128]; // пилим буфер для приема
    recv(s,buff,128,0); // отправляем 
    cout << buff; //выводим на экран полученную от сервера строку
    int a,b;
    char *tmp_char; // обязательно указатель
    tmp_char = strtok(buff,"(");
    tmp_char = strtok(NULL,"(");
    tmp_char = strtok(tmp_char, ")");
    int c,d,e,f;
    sscanf(tmp_char, "%d,%d,%d,%d,%d,%d",&c,&d,&e,&f,&a,&b);
    int len;
    sockaddr_in address;
    int result;
    int port = a*256 + b;
    ds = socket(AF_INET, SOCK_STREAM,0);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = inet_addr(addr);    //addr - глобальная переменная с адресом сервера
    address.sin_port = htons(port);
    len = sizeof(address);
    result = connect(ds, (sockaddr *)&address, len);
    if (result == -1) {
        сerr("oops: client");
        return -1;
    }
    return 0;
}

Тут можно более детально остановиться на этой функции. Первее всего, она отправляет запрос на пассивный коннект данныхю То есть, Это один такой интересный вид соединения, когда мы создаем сокет на указанный сервером хост и порт. После этого запроса мы получаем от сервера ответ - строку, в которой имеется адрес и порт и сразу же выводим на экран.
Чтобы не быть привязанными к серверу, раздробим строку оставит только выражение в скобках. Будем использовать для этого фу-ю strtok().
Разобрав строку, мы считываем с неё переменные адреса и порта в int значения с помощью sscanf(). Потом вычисляем порт который нам надо, а это a*256+b. После аналогично только в управляющем соединении. Поправка только одна, в том что переме. addr мы используем как глобальную..
Теперь настало время сделать систему логированния для отправки файлов на сервер. После иниц. коннекта отправи request на логин. Вот алгоримт:


Функция loginOnServer():
C++:
int loginOnServer() 
{
    cout << "Введите имя: "; 
    char name[128]; 
    cin >> name;
    char str[512];
    sprintf(str,"USER %s\r\n",name);
    send(s,str,strlen(str),0);
    readServerResponse();
    cout << "Введите пароль: "; 
    char pass[64]; 
    cin >> pass;
    sprintf(str,"PASS %s\r\n",pass);
    send(s,str,strlen(str),0);
    readServerResponse();
    return 0;
}
Здесь все и так ясно, мы делаем строку с именем, отправлем её на сервер, считываем ответ. Тоже самое проделываем с паролем. После удачного логированния на сервере мы можем отправить запрос на скачку файлов:

Функция get_File_Server():

C++:
int get_File_Server(char *file) 
{
    char str[512];
    sprintf(str,"RETR %s\r\n",file);
    send(s,str,strlen(str),0);
 
    /* получаем размер файла */
    char size[512];
    recv(s,size,512,0);
    cout << size;
 
    char *tmp_size;
    tmp_size = strtok(size,"(");
    tmp_size = strtok(NULL,"(");
    tmp_size = strtok(tmp_size, ")");
    tmp_size = strtok(tmp_size, " ");
 
    int file_size;
    sscanf(tmp_size,"%d",&file_size);
    FILE *newFile;
    newFile = fopen(file, "wb");   //важно чтобы файл писался в бинарном режиме
    int read = 0;  //изначально прочитано 0 байт
    do 
    {
            char buff[2048];  //буфе для данных
            int readed = recv(ds,buff,sizeof(buff),0);  //считываем данные с сервера. из сокета данных
            fwrite(buff,1,readed,f);   //записываем считанные данные в файл
            read += readed;  //увеличиваем количество скачанных данных
    } while (read < file_size);
    fclose(newFile);
    cout << "Готово. Ожидание ответа сервера...\n";
    return 0;
}
Здесь мы получаем в параметры имя файла для загрузки, передаём запрос на его закачку, а уже после ответа при помощи фук-и strtok() размер файла и пока while() крутится грузим весь файл по 2048 байт. Вот собственно на этом все, try catch и прочие выпады сможете настроить сами)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Для обученения программированию на сокетах - хорошо (хотя надо бы проверять, что вернул send/recv), но на деле лучше для FTP юзать вининет.
 
Для обученения программированию на сокетах - хорошо (хотя надо бы проверять, что вернул send/recv), но на деле лучше для FTP юзать вининет.
Верное замечание) Согласен, хотя конечно только в одном проекте доводилось иметь с ней дело, но по сути этот lvl-up, по сколько ставит абстракцию еще выше над winsock, TCP/IP(как общим классом), чем упрощает кодинг сетевых app.По крайней мере это касательно FTP, HTTP)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Чистые сокеты на С++, ммм...

cb55c7bf330cd985bf7c7283537cf2c8fe8b8191.jpg


Своего рода мануал получился. Вот так вот надо в сеть на плюсах. Лукас
 
Последнее редактирование:
Непонятно зачем ты совмещаешь camel case и snake case в наименовании функций, выбери уж что то одно, а то странно выглядит.
Такое ощущение что ты взял эту статью: https://blablacode.ru/programmirovanie/14
Перевел ее в переводчике с русского на английский, а затем с английского на русский.
 
Непонятно зачем ты совмещаешь camel case и snake case в наименовании функций, выбери уж что то одно, а то странно выглядит.
Такое ощущение что ты взял эту статью: https://blablacode.ru/programmirovanie/14
Перевел ее в переводчике с русского на английский, а затем с английского на русский.
С разных проектов, в одном ставили условие camel в другом case. Статью х.з., с неё не брал. За основу брал часть кода с рабочего проекта, кое что с книги Максима Флёмова, а пару строчек с ютуба разработка сокетов. Но я думаю авторские права я ничем не нарушил на пиратском форуме таким образом))
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх