В этой статье я расскажу основные принципы построения ftp клиента на языке C++. Для понимания этой статьи вам необходимо иметь начальные представления о том, что такое сокеты. Я расскажу лишь о принципах построения программы.
Прежде всего стоит отметить, что фтп соединение состоит из двух сокетов (один для соединения данных, другой — для управления соединением).
По управляющему соединению передаются команды серверу (обычно это 4-ёх байтовые строки) и считываются ответы, по соединению данных передаются файлы, списки файлов и т.д.
Теперь подключим необходимые библиотеки:
- Если вы используете linux:
#include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <stdio.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <iostream> #include <string.h>
Если вы используете Windows:
#include <winsock2.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <iostream> #include <string.h>
Кроме того следует подключить (если вы пользуетесь Visual Studio, это делается в свойствах проекта — настройках компановщика) необходимые в Windows lib файлы: Ws2_32.lib и Wsock32.lib
Следующим шагом будет установка управляющего соединения. Создадим для этого функцию init_sock(). Для упрощения кода преобразование доменного имени в IP адрес опустим. Соединение будем устанавливать на локальный ftp сервер (127.0.0.1), на стандартный 21 порт.
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) {
perror("oops: client");
return -1;
}
return s;
}
Итак мы установили управляющее соединение с сервером. Теперь необходимо считать ответ сервера. Для этого воспользуемся функцией select, которая проверяет наличие данных в потоке; recv — для получения данных из потока.
Немного об этих функциях:
int readServ(int s) {
int rc;
fd_set fdr;
FD_ZERO(&fdr);
FD_SET(s,&fdr);
timeval timeout;
timeout.tv_sec = 1; ///зададим структуру времени со значением 1 сек
timeout.tv_usec = 0;
do {
char buff[512] ={' '};
recv(s,&buff,512,0); ///получаем данные из потока
cout << buff;
rc = select(s+1,&fdr,NULL,NULL,&timeout); ///ждём данные для чтения в потоке 1 сек. подробнее о select
} while(rc); ///проверяем результат
return 2;
}
Таким образом вы можете посылать команды на сервер и считывать его ответ. Имея две эти функции можно построить такую функцию main:
int main() {
int s;
s = init_sock();
readServ(s);
close(s); ///закрытие соединения
return 0;
}
Теперь приступим к созданию соединения данных. Для этого создадим функцию init_data()
int init_data() {
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) {
perror("oops: client");
return -1;
}
return 0;
}
Теперь поговорим об этой функции. Прежде всего она отправляет запрос на пассивное соединение данных. Что это? Это такой вид соединения, когда мы создаём сокет на указанный сервером адрес и порт. После этого запроса мы получаем ответ сервера — строку, которая содержит адрес и порт — затем выводим её на экран.
Для независимости от языка сервера, разберём строку оставив только выражение в скобках. Для этого будем пользоваться функцией strtok(). Подробнее о ней можно прочитать здесь.
Разобрав строку — считаем из неё переменные адреса и порта в целочисленные переменные при помосчи sscanf().
Теперь вычислим необходимый порт: а*256 + b
Затем всё аналогично управляющему соединению. Оговорюсь лишь о том, что переменная addr, которая используется для хранения адреса — глобальная. Вы можете получить адрес заново использую sprintf() и полученные данные в переменных c,d,e,f.
Для получения возможности загружать файлы на сервере необходимоо залогинится. После инициализации соединения отправим запрос на логин. Делается это примерно так:
int login() {
cout << "Введите имя: "; char name[64]; cin >> name;
char str[512];
sprintf(str,"USER %s\r\n",name);
send(s,str,strlen(str),0);
readServ();
cout << "Введите пароль: "; char pass[64]; cin >> pass;
sprintf(str,"PASS %s\r\n",pass);
send(s,str,strlen(str),0);
readServ();
return 0;
}
В этой функции мы формируем строку с именем, отправляем её, считываем ответ. После чего проделываем тоже самое с паролем пользователя. Я не предохранял его от чужих глаз… это не важно для понимания структуры ftp.
После того как мы удачно залогинились на сервере отправим запрос на закачку файла:
int get(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 *f;
f = 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(f);
cout << "Готово. Ожидание ответа сервера...\n";
return 0;
}
Эта довольно громоздкая функция получает в качестве параметра имя загружаемого файла, передаёт запрос на его закачку, из ответа вычисляет при помощи strtok размер файла и пока не загрузится весь файл считывается по 2048 байт и записывается в него.
..уже собственно неочем рассказывать.. вот мой проект
Все функции делаются аналогично..
Ловить ошибки мне лень.. такчто извольте.. Вопросы жду в коментах..








Статья постепенно дополняется.. так что строго не судите..
добавил архив с моими исходниками..
Сделай такую статью про сервер, если есть время.
sockaddr_in что ето за тип даных? он используеться в 3-й строчке листинга после заглавочных файлов.
Структура sockaddr_in описывает сокет для работы с протоколами IP
sockaddr_in address;
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 порт
Нет так и не получил…. я уже третий день парюсь… в боланде в дебаге вижу что в переменной в буф есть а когда выжову их то ничего не выходит
Я под Linux писал. Там это делать не нужно.
Данные то получили?
я пробовал ставить 64 байт буфер. в nsize у меня поподает 21 байт полученных данных. т.е. данные приходят. сервер написан на пхп под линукс а клиент пишу на с++ под винду. Сейчас буду по вашим исходникам свои подправлять.. В ваших исхдоника мне не понятно по чему вы не делаете инициализацию winsocks типа такого if (WSAStartup(0x202,(WSADATA *)&buff[0])){
printf(«WSAStart error %d
«,WSAGetLastError());
return 1;
}
можно с вами в icq пообщаться?
если не трудно постучи в аську 271-013-909
Подскажите!
делаю так:
char buff[1024];
nsize=recv(my_sock,buff,64,0);
Вижу что сервер отвечает. В переменной nsize появляются принятые байты.
а вот данные из переменной buff считать не могу
printf(«S=%s»,buff);
printf(«S=%s»,&buff);
cout << buff;
ни чего не показывает (((
Где ошибаюсь?
Во первых зачем вам буфер на 1024 байта, если вы читаете 64.
Что попадает в nsize?
проверку на правельность открытия сокета делали?
Windows или Linux ?