Перед тем как погрузимся в пучины сетевых app, сперва надо обозначить пару вещей:
Весь код будет разбит по кускам в виде функций и подробным описанием каждой части кода. Собрать и скомпилить уже сможете и сами.
И так, FTP клиент юзает сразу два сокета ( один для конекта данных, другой для управления этим конектом). С помощью управляющей части передаются команды серверу и считываются ответы, а по коннекту мы просто передаем данные, файлы, списки файлов и много чего другого.
Как всегда, для старта мы начнем подключать препроцесорные файлы заголовков. Да сразу обозначимся, мы будем пилить его под Windows.
Header-files
Кроме них, нам надо залезть в VS и в свойствах проекта - настройка компоновщика подключить еще : Ws2_32.lib and Wsock32.lib
Следующий уровень, мы устанавливаем управляющее соединение. Чтобы это сделать, мы запилим функцию init_Sock(). Конект будем воплозать по localhost ftp (127.0.0.1) на порт 21 (стандартный).
Функция init_Sock()
Ура, мы установили управляющий конект с серваком. Сейчас надо будет получить и считать ответ сервера. И в этом нам поможет функция select(), которая будет чекать наличие валидных данных в потоке: А вот функция recv() - будет ждать получение данных из потока!
Функция readServerResponse()
В данный момент наш клиент может посылать команды и считывать ответ. Вот к примеру в main мы можете задать такой алгоритм:
Функция tempMain:
Ну чем я не программист, просите вы после этого.
Но нам мало, мы хотим больше и лучше. Так давайте запилим к созданию данных для соединения. Для этого мы реализуем функцию init_Data_Connect():
Функция init_Data_Connect:
Тут можно более детально остановиться на этой функции. Первее всего, она отправляет запрос на пассивный коннект данныхю То есть, Это один такой интересный вид соединения, когда мы создаем сокет на указанный сервером хост и порт. После этого запроса мы получаем от сервера ответ - строку, в которой имеется адрес и порт и сразу же выводим на экран.
Чтобы не быть привязанными к серверу, раздробим строку оставит только выражение в скобках. Будем использовать для этого фу-ю strtok().
Разобрав строку, мы считываем с неё переменные адреса и порта в int значения с помощью sscanf(). Потом вычисляем порт который нам надо, а это a*256+b. После аналогично только в управляющем соединении. Поправка только одна, в том что переме. addr мы используем как глобальную..
Теперь настало время сделать систему логированния для отправки файлов на сервер. После иниц. коннекта отправи request на логин. Вот алгоримт:
Функция loginOnServer():
Здесь все и так ясно, мы делаем строку с именем, отправлем её на сервер, считываем ответ. Тоже самое проделываем с паролем. После удачного логированния на сервере мы можем отправить запрос на скачку файлов:
Функция get_File_Server():
- - Должны понимать и знать ООП С++ и работу с функциями (забегая на перед, здесь будет все реализовано в процедурном темпе)
- - Знание TCP/IP протоколов и принципа их работы Читать
- - Понимание и знание "Сокетов" Читать
Весь код будет разбит по кускам в виде функций и подробным описанием каждой части кода. Собрать и скомпилить уже сможете и сами.
И так, FTP клиент юзает сразу два сокета ( один для конекта данных, другой для управления этим конектом). С помощью управляющей части передаются команды серверу и считываются ответы, а по коннекту мы просто передаем данные, файлы, списки файлов и много чего другого.
Как всегда, для старта мы начнем подключать препроцесорные файлы заголовков. Да сразу обозначимся, мы будем пилить его под Windows.
Header-files
C++:
#include <winsock2.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <iostream>
#include <string.h>
Следующий уровень, мы устанавливаем управляющее соединение. Чтобы это сделать, мы запилим функцию 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;
}
Ну чем я не программист, просите вы после этого.
Но нам мало, мы хотим больше и лучше. Так давайте запилим к созданию данных для соединения. Для этого мы реализуем функцию 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 и прочие выпады сможете настроить сами)