Пишем FTP клиент на Delphi с помощью WinSock API. Часть 1
Наконец то у меня дошли руки чтобы написать эту статью, вернее одну из ее частей( будет 2 или 3 части). Думаю данная тема будет интересна многим людям, т.к. информации о написании фтп клиента с использованием WinSock API практически нет(ну а если есть у кого какие ссылки по этому вопросу скиньте в личку), и когда начинающие кодеры задают вопрос на эту тему их практически всегда посылают читать протокол фтп(RFC 959) ну или советуют пользоваться WinInet или индейцами.
Свою статью я не на целиваю на новичков в программировании, вы должны знать хотябы основы WinSock API и иметь навыки программирования в Delphi.
Для начала я вам советую почитать о FTP протоколе, вот несколько сайтов где вы можете получить некоторую информацию:
http://book.itep.ru/4/45/ftp_454.htm
http://athena.vvsu.ru/docs/tcpip/rfc/rfc959.txt
http://sources.ru/protocols/ftp_learning.shtml
http://www.soslan.ru/tcp/tcp27.html
В 1 Части мы реализуем следующие функции нашего фтп клиента:
1. Создадим соединение с фтп сервером;
2. Напишем процедуру отправки команд FTP серверу;
3. Научимся получать ответы от него.
Ну чтож приступим:
Создайте новый роект, для удобства мы будем использовать VCL(он нам нужен будет для интерфейса), чтобы мы могли непосредственно заниматься написанием кода фтп клиента, а не заниматься еще и кодингом формы и остальной байды. В последствии вы сможете без труда перенести все функции в любое свое приложение.
Для начала после uses зададим константу:
Код:
const
WM_MYSOCKMESS = WM_USER+1;
и объявим следующие переменные:
Код:
var
Form1: TForm1;
WSADt:TWSADATA;
ClSocket:TSocket;//Сокет для соединения клиента с сервером
ClAddr:sockaddr_in;//Структура sockaddr_in, которая содержит днные для соединения с фтп сервером(семейство протоколов, адрес, порт)
TempSock:TSocket;//Сокет передачи данных
Для того чтобы нам работать с библиотекой WinSock нам ее нужно для начала инициализировать, для этого щелкаем по форме и пишем следующий код в событии OnCreate:
Код:
if WSAStartup(MAKEWORD(1,1), WSADt)<>0 then //инициализируем WinSock
begin
ShowMessage('Ошибка иеициализации WinSock'); //Выводим сообщение если неудалось инициализировать WinSock и
Application.Terminate; //Убиваем нашу прогу
end;
Первое, что нам понадобиться это написать процедуру, которая будет отправлять команды ftp серверу, она будет востребована практически во всех функциях и процедурах отвечающих за прием и получение данных, а также выполнение каких либо действий на ftp сервере:
Код:
procedure TForm1.SendCommandFtpSrv(s: TSocket; command:string); // s- сокет для отправки данных, command - команды фтп
var //сервера(о них более подробно вы можете прочитать в описании протокола фтп RFC 959)
buff: array [0..1024] of Char;
begin
command:=command+#13#10; //Добавим к строке FTP команды символы конца строки и перевода каретки
CopyMemory(@buff, pchar(command), length(command)); //Копируем в buff данные
if send(s, buff, length(command),0)=SOCKET_ERROR then //попытаемся отправить данные
begin
Exit; //и при неудаче выйдем
end;
end;
Теперь мы перейдем к написанию процедуры с помощью, которой мы будем подключаться к ftp серверу, тут все просто до безобразия:
Код:
procedure TForm1.ConnectFTP(ftpsrv, port, usern, //ftpsrv - адрес сервера, port -порт,usern- логин,
userp: string); //userps- пароль
begin
ClSocket:=SOCKET(AF_INET, SOCK_STREAM, IPPROTO_IP); // создаем сокет
if ClSocket=INVALID_SOCKET then // При возникновении ошибки
begin
Exit; // Выйдем из процедуры
end;
ClAddr.sin_family:=AF_INET; //Указываем протокол
ClAddr.sin_addr.S_addr:=inet_addr(pchar(ftpsrv));//Указываем адрес удаленного сервера
ClAddr.sin_port:=htons(StrToInt(port));//Указываем порт
WSAAsyncSelect(ClSocket, handle, WM_MYSOCKMESS, FD_READ); // Переводим сокет в асинхронный режим. Устанавливаем наблюдение за прибытием данных(FD_READ)
Connect(ClSocket, ClAddr, sizeof(ClAddr)); //устанавливаем соединение
Sleep(100); // установим задержку
SendCommandFtpSrv(ClSocket, 'USER '+usern); //Отправим логин
SendCommandFtpSrv(ClSocket, 'PASS '+userp); //отправим пароль
SendCommandFtpSrv(ClSocket, 'FEAT');
end;
Для того, что бы нам быть в курсе как происходит "общение" с ftp сервером, т.е. получать ответы и выводить их на экран, это нам нужно прежде всего для того чтобы в случае неправильной команды или неправильного выполненного какого либо действия, мы могли видеть, что именно ненравится серверу и устранить проблему. Для этого напишем процедуру получения данных от сервера, разместим на форме RichEdit он нам понадобится для отображения ответов и перейдем к выполнению поставленной задачи:
Код:
procedure TForm1.RecvS(sock: TSocket);
var
bsrv:array[0..5000] of char;// переменные для хранения данных
strcl:string; //-----//-----
begin
Fillchar(bsrv, sizeof(bsrv), 0);//Очищаем буфер
if recv(sock, bsrv, sizeof(bsrv), 0)=SOCKET_ERROR then //Делаем попытку получить данные
begin
Exit; // если попытка неудачна, то выходим из процедуры
end;
strcl:=bsrv; //Пришедщие в bsrv скопируем в strcl
if pos('221', strcl)>0 then //Проверяем ответы пришедшие с сервера
begin
ShutDown(sock, SD_BOTH); //Сообщим о прекращении соединения
CloseSocket(sock); //Закрываем сокет
end;
{тут мы просто пишем код, который отображает полученные данные в RichEdit :) }
while pos(#13, strcl)>0 do
begin
RichEdit1.Lines.Add(copy(strcl, 1, pos(#13, strcl)));
Delete(strcl, 1, pos(#13, strcl)+1);
end;
end;
Может показаться, что все уже готово, но если мы соберем в кучу весь код мы не увидим в RichEdit ничего,что свидетельствовало о том, что мы подключились к серверу, хотя мы к нему и подключились, но почему же тогда мы не видим приходящих ответов от сервера, для этих целей нам нужно написать процедуру перехватывающую сообщения WM_MYSOCKMESS, они появляются тогда, когда на определенном сокете возникают какие либо события:
Код:
procedure TForm1.MSGSocket(var Mess: TMessage);
begin
case Mess.LParam of
FD_ACCEPT:
begin
TempSock:=accept(Mess.WParam, nil, nil); //Для получения данных, мы переводим socket в асинхронный режим и устанавливаем
WSAAsyncSelect(TempSock, handle, WM_MYSOCKMESS, FD_READ+FD_CLOSE);//наблюдение за событиями и FD_CLOSE(когда мы отрубаемся от сервака)
end;
FD_READ: RecvS(Mess.WParam); //Данные мы получим с помощью описанной выше процедуры RecvS
FD_CLOSE: CloseSocket(Mess.WParam); //Закроем сокет при отключении клиента
end;
end;
Переходим к завершающему этапу кодинга, разместим на форме 2 кнопкb и в событии OnClick:
в 1 кнопке пишем:
Код:
ConnectFTP('ххх.ххх.ххх.ххх','21','login','pass');
Во 2 кнопке:
Код:
SendCommandFtpSrv( ClSocket, 'QUIT'); //Эта команда говорит о завершении соединения клиента с сервером
Для того, чтобы не возникли лишние вопросы, расскажу как все это собрать. После слова private
перечислите названия всех используемых процедур(думаю все знают как это делать, ну а не знаете это уже ваши проблемы), которые мы написали, но даже после этого вы скажете, что код нефига не пашет, поэтому отвечу сразу на этот вопрос процедура MSGSocket после слова private будет иметь следующий вид:
Код:
procedure MSGSocket(var Mess: TMessage); message WM_MYSOCKMESS;
На сим 1 часть статьи о написании Ftp клиента я завершаю.
P.S. О чем я напишу во 2 и 3 частях:
Ну тут будет пожалуй самое интересное, такое как загрузка файлов на фтп сервер, скачивание файлов, переход по папкам, удаление файлов, кароче постепенно я постараюсь выложить достаточно приличный фтп клиент.
-------------------------
De-V: СТАТЬЯ В КОНКУРСЕ