Delphi 3. Библиотека программиста

       

Асинхронная пересылка файлов


Познакомившись с протоколом FTP в блокирующем (синхронном) режиме, кратко рассмотрим работу CsShopper в асинхронном режиме. Поскольку процесс регистрации на FTP-сервере подробно описан выше, наше основное внимание будет сосредоточено на пересылке, и особенно— на асинхронном приеме файла с FTP-сервера.

Перед тем как подключаться к FTP-серверу в асинхронном режиме, следует установить переключатель Asynchronous в групповом поле FTP Mode вкладки Options. Этот переключатель управляет режимом всего соединения; после того как SHOPPER32 подключится к FTP-серверу, групповое поле FTP Mode блокируется до окончания сеанса.

Процесс выбора принимаемого файла в асинхронном режиме происходит так же, как и в блокирующем режиме; другими словами, перед вызовом Retrieve мы присваиваем имя файла свойству Get. Отличия начинаются внутри Retrieve. Определив тип файла, мы присваиваем флагу состояния FFtpCmd значение FTP_TYPEI и тем самым приказываем серверу переслать файл как непрерывный поток байтов. Команда TYPE передается через процедуру SendFtpCmd.

Когда Winsock получает событие сокета FD_READ, которое происходит в результате ответа FTP-сервера на команду TYPE, он посылает процедуре FtpEvent сообщение с описанием события. В FtpEvent сообщение анализируется на предмет поиска событий FD_READ, FD_WRITE и FD_CLOSE. Для распознавания события сокета используется оператор case.

При получении события FD_READ процедура InfoEvent отправляет все содержимое буфера FRcvBuffer для вывода в приложении SHOPPER32. В буфере FRcv Buffer, содержащем код ответа от сервера, ищется символ 4 или 5, свидетель ствующий об ошибке FTP. Если поиск окажется успешным, FFtpCmd присваивается значение FTP_FAIL, которое сигнализирует приложению о возникнове нии ошибки.

В противном случае процедура ProcessRecvData обрабатывает FRcvBuffer и флаг состояния FFtpCmd с использованием оператора case. Так как FFtpCmd имеет значение FTP_TYPEI, ProcessRecvData вызывает процедуру ProcessTypeI, в которой выполняется подробный анализ содержимого FRcvBuffer. Следующий фрагмент кода показывает, как это делается:



procedure TCsShopper.ProcessTypeI; begin case GetReplyCode(FRcvBuffer) of 200 : begin if Pos('200-',String(FRcvBuffer)) = 0 then // Сервер ждет, пока мы создадим // соединение данных и пошлем команду USER begin ProcessPort; end; { остаток кода пропущен } end; // case FillChar(FRcvBuffer, SizeOf(FRcvBuffer),#0); end;

Если код ответа равен 200, вызывается процедура ProcessPort, из которой в свою очередь вызывается InitDataConn, выполняющая четыре задачи:

создание сокета для соединения данных;
вызов WSAAsyncSelect для создания логического номера окна, позволяю щего FtpDataEvent перехватывать события сокета, связанные с соедине нием данных;
вызов функции Winsock API bind для связывания нового сокета данных;
вызов listen для перевода сокета данных в состояние «прослушивания» (listening).

Если в результате вызова InitDataConn будет создан допустимый сокет данных, ProcessPort создает для соединения данных уникальный номер порта, который затем передается процедурой SendFtpCmd. Наконец, флагу состояния FFtpCmd присваивается значение FTP_RETR, которое сигнализирует CsShopper о том, что следующее событие сокета FD_READ должно анализироваться в контекс те приема файла.

Когда на управляющем соединении происходит следующее событие FD_READ (при условии отсутствия ошибок сокета или отрицательных кодов ответа), вызывается процедура ProcessRecvData, которая в свою очередь инициирует ProcessGet.

В ProcessGet при получении кода ответа 200 (признак успеха) создается локальный файл, имя которого совпадает с именем файла на сервере. В дальнейшем код ответа 150 сигнализирует FTP-клиенту о том, что сервер приступил к пересылке информации через соединение данных.

Сразу же после того, как FTP-сервер свяжется с клиентом через соединение данных, Winsock уведомляет об этом процедуру FtpDataEvent с помощью события FD_ACCEPT. В ветви FD_ACCEPT оператора case вызывается функция WSAAsyncSelect, которая инициализирует сокет данных для приема только следующих событий: FD_READ, FD_WRITE и FD_CLOSE. Следующий фрагмент процедуры FtpDataEvent показывает, как это делается:

FD_ACCEPT : begin FStartTime := GetTickCount; FIntTime := FStartTime; if FListenSocket <> INVALID_SOCKET then begin nLen := SizeOf(TSockAddr); FDataSocket := accept(FListenSocket, @FRemoteHost, @nLen); if FDataSocket = SOCKET_ERROR then begin InfoEvent(Concat('Error : ',WSAErrorMsg)); FFtpCmd := FTP_FAIL; Exit; end; nStat := WSAAsyncSelect(FDataSocket, FDataWnd, DATA_EVENT, FD_READ or FD_WRITE or FD_CLOSE); if nStat = SOCKET_ERROR then begin InfoEvent(Concat('Error : ',WSAErrorMsg)); FFtpCmd := FTP_FAIL; Exit; end; { остаток кода пропущен } end; end;

При приеме первого и последнего пакета данных через соединение данных Winsock уведомляет FtpDataEvent с помощью события FD_READ, что приводит к вызову RecvData для получения и сохранения поступающих данных в локальном файле. После завершения пересылки FTP-сервер закрывает соединение данных со своей стороны, заставляя Winsock послать сообщение FD_CLOSE. На этом пересылку файла логично было бы завершить, но иногда в сокете данных FTP-клиента все еще остаются непрочитанные данные. Чтобы избежать потерь информации, мы присваиваем флагу FTransferDone значение TRUE. Все сказанное демонстрируется следующим фрагментом кода из процедуры FtpDataEvent:

FD_CLOSE : begin FTransferDone := TRUE; case FFTPCmd of FTP_RETR, FTP_LIST, FTP_VIEW : RecvData; FTP_STOR : SendData; end; end;

Флаг FTransferDone сообщает о необходимости продолжить чтение оставшихся данных сокета в цикле while, как показано в следующем фрагменте кода процедуры RecvData:

FTP_RETR : begin { часть кода пропущена } if FTransferDone then // Работа с //FTP-сервером закончена, // однако необходимо прочитать // и сохранить данные, оставшиеся // в сокете данных begin Done := FALSE; while not Done do begin BlockWrite(FRetrFile, FDataBuffer, Response); { часть кода пропущена } Response := recv(FDataSocket, FDataBuffer, SizeOf(FDataBuffer), 0); if Response = SOCKET_ERROR then begin Done := TRUE; WSAAsyncSelect(FDataSocket, // Прекратить посылку FDataWnd, 0, 0); // уведомлений CloseSocket(FDataSocket); System.CloseFile(FRetrFile); ChangeBusy(FALSE); ChangeDataDone(TRUE); InfoEvent(Concat('ERROR : ',WSAErrorMsg)); end; if Response = 0 then // Данных не осталось begin { часть кода пропущена } Done := TRUE; WSAAsyncSelect(FDataSocket, FDataWnd, 0, 0); CloseSocket(FDataSocket); System.CloseFile(FRetrFile); ChangeBusy(FALSE); ChangeDataDone(TRUE); GetList; end; end; end else if Response > 0 then // FTP-сервер продолжает // посылать данные, // их необходимо обработать begin BlockWrite(FRetrFile, FDataBuffer, Response); { часть кода пропущена } end; end;

Передача файла FTP-серверу в асинхронном режиме выполняется по тому же принципу, что и прием.



Содержание раздела