В этой статье я расскажу основные принципы построения 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 ?