Работа с ftp-серверами

Эта глава объясняет, как использовать некоторые основные функции API CFFTP. В то время как управление передачей файлов реализовано синхронно, управление транзакциями FTP выполняется асинхронно.

Загрузка файла

Используя CFFTP очень подобно использованию CFHTTP, потому что они оба на основе CFStream. Как с любым другим API, использующим CFStream асинхронно, загружая файл CFFTP, требует, чтобы Вы создали поток чтения для файла и функцию обратного вызова для того потока чтения. Когда поток чтения получит данные, функция обратного вызова будет выполнена, и необходимо будет соответственно загрузить байты. Эта процедура должна обычно выполняться с помощью двух функций: один для установки потоков и один для действия как функция обратного вызова.

Установка потоков FTP

Начните путем создания потока чтения с помощью CFReadStreamCreateWithFTPURL функция и передача его строка URL файла, который будет загружен на удаленном сервере. Пример строки URL мог бы быть ftp://ftp.example.com/file.txt. Обратите внимание на то, что строка содержит имя сервера, путь и файл. Затем, создайте поток записи для локального расположения, где будет загружен файл. Это выполняется с помощью CFWriteStreamCreateWithFile функция, передавая путь, где будет загружен файл.

Так как поток записи и поток чтения должны остаться в синхронизации, это - хорошая идея создать структуру, содержащую всю общую информацию, такую как словарь прокси, размер файла, число записанных байтов, число байтов, перенесенных, и буфер. Эта структура могла бы быть похожей на это в Перечислении 5-1.

Перечисление 5-1  потоковая структура

typedef struct MyStreamInfo {
 
    CFWriteStreamRef  writeStream;
    CFReadStreamRef   readStream;
    CFDictionaryRef   proxyDict;
    SInt64            fileSize;
    UInt32            totalBytesWritten;
    UInt32            leftOverByteCount;
    UInt8             buffer[kMyBufferSize];
 
} MyStreamInfo;

Инициализируйте свою структуру с потоком чтения, и запись передают потоком, Вы просто создали. Можно тогда определить info поле Вашего потокового клиентского контекста (CFStreamClientContext) указать на Вашу структуру. Это станет полезным позже.

Откройте свой поток записи с CFWriteStreamOpen функционируйте, таким образом, можно начать писать в локальный файл. Для проверки поток открывается должным образом, вызовите функцию CFWriteStreamGetStatus и проверьте, возвращается ли это также kCFStreamStatusOpen или kCFStreamStatusOpening.

С открытым потоком записи свяжите функцию обратного вызова с потоком чтения. Вызовите функцию CFReadStreamSetClient и передайте поток чтения, сетевые события, которые Ваша функция обратного вызова должна получить, имя функции обратного вызова и CFStreamClientContext объект. Тем, что ранее установил info поле потокового клиентского контекста, Ваша структура будет теперь отправлена в Вашу функцию обратного вызова каждый раз, когда это выполняется.

Некоторые Ftp-серверы могут потребовать имени пользователя, и некоторые могут также потребовать пароля. Если серверу, к которому Вы получаете доступ, нужно имя пользователя для аутентификации, вызовите CFReadStreamSetProperty функционируйте и передайте поток чтения, kCFStreamPropertyFTPUserName для свойства и ссылки на a CFString объект, содержащий имя пользователя. Кроме того, если необходимо установить пароль, установите kCFStreamPropertyFTPPassword свойство.

Некоторые конфигурации сети могут также использовать прокси FTP. Вы получаете информацию прокси по-разному в зависимости от того, работает ли Ваш код в OS X или iOS.

  • В OS X можно получить настройки прокси в словаре путем вызова SCDynamicStoreCopyProxies функция и передача его NULL.

  • В iOS можно получить настройки прокси путем вызова CFNetworkCopyProxiesForURL.

Эти функции возвращают ссылку динамической памяти. Можно использовать это значение для установки kCFStreamPropertyFTPProxy свойство потока чтения. Это устанавливает прокси-сервер, указывает порт и возвращает булево значение, указывающее, осуществляется ли пассивный режим для потока FTP.

В дополнение к упомянутым свойствам существует много других свойств, доступных для потоков FTP. Полный список следует.

  • kCFStreamPropertyFTPUserName — имя пользователя для использования для входа в систему (устанавливаемый и восстановимый; не устанавливайте для анонимных соединений FTP),

  • kCFStreamPropertyFTPPassword — пароль для использования для входа в систему (устанавливаемый и восстановимый; не устанавливайте для анонимных соединений FTP),

  • kCFStreamPropertyFTPUsePassiveMode — использовать ли пассивный режим (устанавливаемый и восстановимый)

  • kCFStreamPropertyFTPResourceSize — ожидаемый размер элемента, загружающегося, при наличии (восстановимый; доступный только для FTP читает потоки),

  • kCFStreamPropertyFTPFetchResourceInfo — запросить ли ту информацию ресурса, такую как размер, требоваться прежде, чем запустить загрузку (устанавливаемый и восстановимый); установка этого свойства может повлиять на производительность

  • kCFStreamPropertyFTPFileTransferOffset — файловое смещение, при котором можно запустить передачу (устанавливаемый и восстановимый)

  • kCFStreamPropertyFTPAttemptPersistentConnection — попытаться ли снова использовать соединения (устанавливаемый и восстановимый)

  • kCFStreamPropertyFTPProxy — Тип CFDictionary, содержащий пары ключ/значение словаря прокси (устанавливаемый и восстановимый)

  • kCFStreamPropertyFTPProxyHost — имя прокси-сервера FTP (устанавливаемый и восстановимый)

  • kCFStreamPropertyFTPProxyPort — номер порта прокси-сервера FTP (устанавливаемый и восстановимый)

После того, как корректные свойства были присвоены потоку чтения, откройте поток с помощью CFReadStreamOpen функция. Предполагая, что это не возвращает ошибку, все потоки были должным образом установлены.

Реализация функции обратного вызова

Ваша функция обратного вызова получит три параметра: поток чтения, тип события и Вашего MyStreamInfo структура. Тип события определяет, какие меры должны быть приняты.

Наиболее распространенное событие kCFStreamEventHasBytesAvailable, когда поток чтения получил байты от сервера, который отправляется. Во-первых, проверьте, сколько байты были считаны путем вызова CFReadStreamRead функция. Удостоверьтесь, что возвращаемое значение не является меньше, чем нуль (ошибка), или равный нулю (загрузка завершилась). Если возвращаемое значение положительно, то можно начать писать данные в потоке чтения к диску через поток записи.

Вызовите CFWriteStreamWrite функционируйте для записи данных в поток записи. Иногда CFWriteStreamWrite может возвратиться, не пишущий все данные от потока чтения. Поэтому установите цикл для выполнения, пока существуют все еще данные, которые будут записаны. Код для этого цикла находится в Перечислении 5-2, где info MyStreamInfo структура от Установки Потоков. Этот метод записи в потоковое использование записи, блокирующее потоки. Можно достигнуть лучшей производительности путем создания потока записи управляемым событиями, но код более сложен.

Перечисление 5-2  Пишущий данные в поток записи от потока чтения

bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize);
 
//...make sure bytesRead > 0 ...
 
bytesWritten = 0;
while (bytesWritten < bytesRead) {
    CFIndex result;
 
    result = CFWriteStreamWrite(info->writeStream, info->buffer + bytesWritten, bytesRead - bytesWritten);
    if (result <= 0) {
        fprintf(stderr, "CFWriteStreamWrite returned %ld\n", result);
        goto exit;
    }
    bytesWritten += result;
}
info->totalBytesWritten += bytesWritten;

Повторите эту всю процедуру, пока существуют доступные байты в потоке чтения.

Другие два события, которые необходимо не упустить, kCFStreamEventErrorOccurred и kCFStreamEventEndEncountered. Если ошибка происходит, получите ошибку с помощью CFReadStreamGetError и затем выход. Если конец файла происходит, то Ваша загрузка завершилась, и можно выйти.

Удостоверьтесь, что удалили все свои потоки после того, как все завершается, и никакой другой процесс не использует потоки. Во-первых, закройте поток записи и установите клиент в NULL. Тогда не запланируйте поток от цикла выполнения и выпустите его. Удалите потоки из цикла выполнения, когда Вы будете сделаны.

Загрузка файла

Загрузка файла подобна загрузке файла. Как с загрузкой файла, Вам нужны поток чтения и поток записи. Однако при загрузке файла, поток чтения будет для локального файла, и поток записи будет для удаленного файла. Следуйте инструкциям в Установке Потоков, но везде, где она относится к потоку чтения, адаптируйте код к потоку записи и стиху визы.

В функции обратного вызова, вместо того, чтобы искать kCFStreamEventHasBytesAvailable событие, теперь ищите событие kCFStreamEventCanAcceptBytes. Во-первых, считайте байты из файла с помощью потока чтения и поместите данные в буфер в MyStreamInfo. Затем работайте CFWriteStreamWrite функционируйте для продвижения байтов от буфера в поток записи. CFWriteStreamWrite возвращает число байтов, записанных в поток. Если число байтов, записанных в поток, является меньше, чем число, считанное из файла, вычислите оставшиеся байты и сохраните их назад в буфер. Во время следующего цикла записи, если существуют оставшиеся байты, пишут им в поток записи вместо того, чтобы загрузить новые данные из потока чтения. Повторите эту целую процедуру, пока поток записи может принять байты (CFWriteStreamCanAcceptBytes). Посмотрите этот цикл в коде в Перечислении 5-3.

Перечисление 5-3  Пишущий данные в поток записи

do {
    // Check for leftover data
    if (info->leftOverByteCount > 0) {
        bytesRead = info->leftOverByteCount;
    } else {
        // Make sure there is no error reading from the file
        bytesRead = CFReadStreamRead(info->readStream, info->buffer,
                                     kMyBufferSize);
        if (bytesRead < 0) {
            fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
            goto exit;
        }
        totalBytesRead += bytesRead;
    }
 
    // Write the data to the write stream
     bytesWritten = CFWriteStreamWrite(info->writeStream, info->buffer, bytesRead);
    if (bytesWritten > 0) {
 
        info->totalBytesWritten += bytesWritten;
 
        // Store leftover data until kCFStreamEventCanAcceptBytes event occurs again
        if (bytesWritten < bytesRead) {
            info->leftOverByteCount = bytesRead - bytesWritten;
            memmove(info->buffer, info->buffer + bytesWritten,
                    info->leftOverByteCount);
        } else {
            info->leftOverByteCount = 0;
        }
    } else {
        if (bytesWritten < 0)
            fprintf(stderr, "CFWriteStreamWrite returned %ld\n", bytesWritten);
        break;
    }
 
} while (CFWriteStreamCanAcceptBytes(info->writeStream));

Также счет kCFStreamEventErrorOccurred и kCFStreamEventEndEncountered события, как Вы делаете при загрузке файла.

Создание удаленного каталога

Для создания каталога на удаленном сервере установите поток записи, как будто Вы собирались быть загрузкой файла. Однако обеспечьте путь к каталогу, не файл, для CFURL объект, передающийся CFWriteStreamCreateWithFTPURL функция. Закончите путь наклонной чертой вправо. Например, надлежащий путь к каталогу был бы ftp://ftp.example.com/newDirectory/, нет ftp://ftp.example.com/newDirectory/newFile.txt. Когда функция обратного вызова выполняется циклом выполнения, она отправляет событие kCFStreamEventEndEncountered, что означает, что каталог был создан (или kCFStreamEventErrorOccurred если что-то пошло не так, как надо).

Только один уровень каталогов может быть создан с каждым вызовом к CFWriteStreamCreateWithFTPURL. Кроме того, каталог создается, только если у Вас есть корректные полномочия на сервере.

Загрузка перечисления каталога

Загрузка перечисления каталога через FTP немного отличается от загрузки или загрузки файла. Это вызвано тем, что входящие данные должны быть проанализированы. Во-первых, установите поток чтения для получения перечисления каталога. Это должно быть сделано, как это было для загрузки файла: создайте поток, зарегистрируйте функцию обратного вызова, запланируйте поток с циклом выполнения (при необходимости, установите имя пользователя, пароль и проксируйте информацию), и наконец откройте поток. В следующем примере Вам не нужны и чтение и в поток записи при получении перечисления каталога, потому что входящие данные идут в экран, а не файл.

В функции обратного вызова наблюдайте за kCFStreamEventHasBytesAvailable событие. До загружающихся данных от потока чтения удостоверьтесь, что нет никаких оставшихся данных в потоке с предыдущего раза, когда функция обратного вызова была выполнена. Загрузите смещение из leftOverByteCount поле Вашего MyStreamInfo структура. Затем считанные данные из потока, принимая во внимание смещение Вы просто вычислили. Размер буфера и число чтения байтов должны быть вычислены также. Это все выполняется в Перечислении 5-4.

Перечисление 5-4  , Загружающее данные для перечисления каталога

// If previous call had unloaded data
int offset = info->leftOverByteCount;
 
// Load data from the read stream, accounting for the offset
bytesRead = CFReadStreamRead(info->readStream, info->buffer + offset,
                             kMyBufferSize - offset);
if (bytesRead < 0) {
    fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead);
    break;
} else if (bytesRead == 0) {
    break;
}
bufSize = bytesRead + offset;
totalBytesRead += bufSize;

После того, как данные были считаны в буфер, устанавливал цикл для парсинга данных. Анализирующиеся данные являются не обязательно всем перечислением каталога; это могло (и вероятно будет) быть блоками перечисления. Создайте цикл для парсинга данных с помощью функции CFFTPCreateParsedResourceListing, который должен быть передан буфер данных, размер буфера и ссылка словаря. Это возвращает число проанализированных байтов. Пока это значение больше, чем нуль, продолжайте циклично выполняться. Словарь это CFFTPCreateParsedResourceListing создает содержит весь каталог, перечисляющий информацию; больше информации о ключах доступно в Установке Потоков.

Это возможно для CFFTPCreateParsedResourceListing возвратить положительное значение, но не создать словарь синтаксического анализа. Например, если конец перечисления содержит информацию, которая не может быть проанализирована, CFFTPCreateParsedResourceListing возвратит положительное значение, чтобы сказать вызывающей стороне, что были использованы данные. Однако CFFTPCreateParsedResourceListing не создаст словарь синтаксического анализа, так как он не мог понять данные.

Если словарь синтаксического анализа создается, повторно вычислите число чтения байтов и размера буфера как показано в Перечислении 5-5.

Перечисление 5-5  , Загружающее перечисление каталога и парсинг его

do
{
    bufRemaining = info->buffer + totalBytesConsumed;
 
    bytesConsumed = CFFTPCreateParsedResourceListing(NULL, bufRemaining,
                                                     bufSize, &parsedDict);
    if (bytesConsumed > 0) {
 
        // Make sure CFFTPCreateParsedResourceListing was able to properly
        // parse the incoming data
        if (parsedDict != NULL) {
            // ...Print out data from parsedDict...
            CFRelease(parsedDict);
        }
 
        totalBytesConsumed += bytesConsumed;
        bufSize -= bytesConsumed;
        info->leftOverByteCount = bufSize;
 
    } else if (bytesConsumed == 0) {
 
        // This is just in case. It should never happen due to the large buffer size
        info->leftOverByteCount = bufSize;
        totalBytesRead -= info->leftOverByteCount;
        memmove(info->buffer, bufRemaining, info->leftOverByteCount);
 
    } else if (bytesConsumed == -1) {
        fprintf(stderr, "CFFTPCreateParsedResourceListing parse failure\n");
        // ...Break loop and cleanup...
    }
 
} while (bytesConsumed > 0);

Когда поток больше не будет иметь доступных байтов, очистите все потоки и удалите их из цикла выполнения.