Работа с потоками

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

Работа с потоками чтения

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

Создание потока чтения

Запустите путем создания потока чтения. Перечисление 2-1 создает поток чтения для файла.

Перечисление 2-1  , Создающее поток чтения из файла

CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);

В этом перечислении, kCFAllocatorDefault параметр указывает, что текущее системное средство выделения по умолчанию используется для выделения памяти для потока и fileURL параметр указывает имя файла, для которого этот поток чтения создается, такой как file:///Users/joeuser/Downloads/MyApp.sit.

Точно так же можно создать пару потоков на основе сетевой службы путем вызова CFStreamCreatePairWithSocketToCFHost (описанный в Использовании цикла выполнения для предотвращения блокирования) или CFStreamCreatePairWithSocketToNetService (описанный в руководстве по программированию NSNetServices и CFNetServices).

Теперь, когда Вы создали поток, можно открыть его. Открытие потока заставляет поток резервировать любые системные ресурсы, которых это требует, такие как дескриптор файла должен был открыть файл. Перечисление 2-2 является примером открытия потока чтения.

Перечисление 2-2  , Открывающее поток чтения

if (!CFReadStreamOpen(myReadStream)) {
    CFStreamError myErr = CFReadStreamGetError(myReadStream);
    // An error has occurred.
        if (myErr.domain == kCFStreamErrorDomainPOSIX) {
        // Interpret myErr.error as a UNIX errno.
        } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
        // Interpret myErr.error as a MacOS error code.
            OSStatus macError = (OSStatus)myErr.error;
        // Check other error domains.
    }
}

CFReadStreamOpen функциональные возвраты TRUE указать успех и FALSE если открытые сбои по любой причине. Если CFReadStreamOpen возвраты FALSE, пример вызывает CFReadStreamGetError функция, возвращающая структуру типа CFStreamError состоя из двух значений: доменный код и код ошибки. Доменный код указывает, как должен быть интерпретирован код ошибки. Например, если доменный код kCFStreamErrorDomainPOSIX, кодом ошибки является UNIX errno значение. Другие ошибочные домены kCFStreamErrorDomainMacOSStatus, который указывает, что код ошибки OSStatus значение, определенное в MacErrors.h, и kCFStreamErrorDomainHTTP, который указывает, что код ошибки является тем из значений, определенных CFStreamErrorHTTP перечисление.

Открытие потока может быть долгим процессом, таким образом, CFReadStreamOpen и CFWriteStreamOpen функции избегают блокировать путем возврата TRUE указать, что начался процесс открытия потока. Для проверки состояния открытого вызовите функции CFReadStreamGetStatus и CFWriteStreamGetStatus, которые возвращаются kCFStreamStatusOpening если открытое все еще происходит, kCFStreamStatusOpen если открытое завершено, или kCFStreamStatusErrorOccurred если открытое завершилось, но перестало работать. В большинстве случаев не имеет значения, завершено ли открытое, потому что функции CFStream, читающие и запись, блокируют, пока поток не будет открыт.

Чтение от потока чтения

Для чтения из потока чтения вызовите функцию CFReadStreamRead, который подобен UNIX read() системный вызов. Оба берут параметры длины буфера и буфер. Оба возвращают число чтения байтов, 0 если в конце потока или файла, или -1 если произошла ошибка. Оба блока по крайней мере до одного байта могут быть считаны, и оба продолжают читать, пока они могут сделать так без блокирования. Перечисление 2-3 является примером чтения от потока чтения.

Перечисление 2-3  , Читающее из потока чтения (блокирование)

CFIndex numBytesRead;
do {
    UInt8 buf[myReadBufferSize]; // define myReadBufferSize as desired
    numBytesRead = CFReadStreamRead(myReadStream, buf, sizeof(buf));
    if( numBytesRead > 0 ) {
        handleBytes(buf, numBytesRead);
    } else if( numBytesRead < 0 ) {
        CFStreamError error = CFReadStreamGetError(myReadStream);
        reportError(error);
    }
} while( numBytesRead > 0 );

Разъединение потока чтения

Когда все данные были считаны, необходимо вызвать CFReadStreamClose функционируйте для закрытия потока, таким образом выпуская системные ресурсы, связанные с ним. Тогда выпустите потоковую ссылку путем вызывания функции CFRelease. Можно также хотеть лишить законной силы ссылку путем установки его в NULL. См. Перечисление 2-4 для примера.

Перечисление 2-4  , Выпускающее поток чтения

CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;

Работа с потоками записи

Работа с потоками записи подобна работе с потоками чтения. Одно существенное различие то, что функция CFWriteStreamWrite не гарантирует, что принял все байты, что Вы передаете его. Вместо этого CFWriteStreamWrite возвращает число байтов, которые оно приняло. Вы заметите в примере кода, показанном в Перечислении 2-5, что, если число записанных байтов не является тем же как общим количеством байтов, которые будут записаны, буфер корректируется для размещения этого.

  Создание перечисления 2-5, открытие, запись в, и выпуск потока записи

CFWriteStreamRef myWriteStream =
        CFWriteStreamCreateWithFile(kCFAllocatorDefault, fileURL);
if (!CFWriteStreamOpen(myWriteStream)) {
    CFStreamError myErr = CFWriteStreamGetError(myWriteStream);
    // An error has occurred.
    if (myErr.domain == kCFStreamErrorDomainPOSIX) {
    // Interpret myErr.error as a UNIX errno.
    } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
        // Interpret myErr.error as a MacOS error code.
        OSStatus macError = (OSStatus)myErr.error;
        // Check other error domains.
    }
}
UInt8 buf[] = “Hello, world”;
CFIndex bufLen = (CFIndex)strlen(buf);
 
while (!done) {
    CFIndex bytesWritten = CFWriteStreamWrite(myWriteStream, buf, (CFIndex)bufLen);
    if (bytesWritten < 0) {
        CFStreamError error = CFWriteStreamGetError(myWriteStream);
        reportError(error);
    } else if (bytesWritten == 0) {
        if (CFWriteStreamGetStatus(myWriteStream) == kCFStreamStatusAtEnd) {
            done = TRUE;
        }
    } else if (bytesWritten != bufLen) {
        // Determine how much has been written and adjust the buffer
        bufLen = bufLen - bytesWritten;
        memmove(buf, buf + bytesWritten, bufLen);
 
        // Figure out what went wrong with the write stream
        CFStreamError error = CFWriteStreamGetError(myWriteStream);
        reportError(error);
 
    }
}
CFWriteStreamClose(myWriteStream);
CFRelease(myWriteStream);
myWriteStream = NULL;

Предотвращение блокирования при работе с потоками

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

Существует два способа предотвратить блокирование при чтении из или записи в объект CFStream:

Каждый из этих подходов описан в следующих разделах.

Используя цикл выполнения для предотвращения блокирования

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

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

Для узнавания больше о выполненных циклах в целом считайте Руководство по программированию Поточной обработки.

Этот пример начинается путем создания потока чтения сокета:

CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port,
                                   &myReadStream, NULL);

где ссылка на объект CFHost, host, указывает удаленный узел, с которым поток чтения должен быть сделан и port параметр указывает номер порта, который использует узел. CFStreamCreatePairWithSocketToCFHost функционируйте возвращает новую потоковую ссылку чтения в myReadStream. Последний параметр, NULL, указывает, что вызывающая сторона не хочет создавать поток записи. Если бы Вы хотели создать пар записи, то последний параметр был бы, например, &myWriteStream.

Прежде, чем открыть сокет читает поток, создают контекст, который будет использоваться, когда Вы зарегистрируетесь для получения связанных с потоком событий:

CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc};

Первый параметр 0 указать номер версии. info параметр, myPtr, указатель на данные, Вы хотите быть переданными Вашей функции обратного вызова. Обычно, myPtr указатель на структуру, которую Вы определили, который содержит информацию, касающуюся потока. retain параметр является указателем на функцию для сохранения info параметр. Таким образом, если Вы устанавливаете его в свою функцию myRetain, как в коде выше, CFStream вызовет myRetain(myPtr) сохранить info указатель. Точно так же release параметр, myRelease, указатель на функцию для выпуска информационного параметра. Когда поток разъединен с контекстом, CFStream вызвал бы myRelease(myPtr). Наконец, copyDescription параметр к функции для предоставления описания потока. Например, если необходимо было вызвать CFCopyDesc(myReadStream) с потоковым клиентским контекстом, показанным выше, CFStream вызвал бы myCopyDesc(myPtr).

Клиентский контекст также позволяет Вам опцию установки retain, release, и copyDescription параметры к NULL. Если Вы устанавливаете retain и release параметры к NULL, тогда система будет ожидать, что Вы сохраните память указанной info указатель, живой, пока не уничтожается сам поток. Если Вы устанавливаете copyDescription параметр к NULL, тогда система предоставит, если требуется, элементарное описание того, что находится в памяти, на которую указывают info указатель.

С клиентским установленным контекстом вызовите функцию CFReadStreamSetClient зарегистрироваться для получения связанных с потоком событий. CFReadStreamSetClient требует, чтобы Вы указали функцию обратного вызова и события, которые Вы хотите получить. Следующий пример в Перечислении 2-6 указывает, что функция обратного вызова хочет получить kCFStreamEventHasBytesAvailable, kCFStreamEventErrorOccurred, и kCFStreamEventEndEncountered события. Тогда запланируйте поток на цикл выполнения с CFReadStreamScheduleWithRunLoop функция. См. Перечисление 2-6 для примера того, как сделать это.

Перечисление 2-6  Планируя поток на цикл выполнения

CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable |
        kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext))
{
    CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),
                                    kCFRunLoopCommonModes);
}

С потоком, запланированным на цикл выполнения, Вы готовы открыть поток как показано в Перечислении 2-7.

Перечисление 2-7  , Открывающее неблокирование, считало поток

if (!CFReadStreamOpen(myReadStream)) {
    CFStreamError myErr = CFReadStreamGetError(myReadStream);
    if (myErr.error != 0) {
    // An error has occurred.
        if (myErr.domain == kCFStreamErrorDomainPOSIX) {
        // Interpret myErr.error as a UNIX errno.
            strerror(myErr.error);
        } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
            OSStatus macError = (OSStatus)myErr.error;
            }
        // Check other domains.
    } else
        // start the run loop
        CFRunLoopRun();
}
 

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

  Функция обратного вызова событий Listing 2-8 Network

void myCallBack (CFReadStreamRef stream, CFStreamEventType event, void *myPtr) {
    switch(event) {
        case kCFStreamEventHasBytesAvailable:
            // It is safe to call CFReadStreamRead; it won’t block because bytes
            // are available.
            UInt8 buf[BUFSIZE];
            CFIndex bytesRead = CFReadStreamRead(stream, buf, BUFSIZE);
            if (bytesRead > 0) {
                handleBytes(buf, bytesRead);
            }
            // It is safe to ignore a value of bytesRead that is less than or
            // equal to zero because these cases will generate other events.
            break;
        case kCFStreamEventErrorOccurred:
            CFStreamError error = CFReadStreamGetError(stream);
            reportError(error);
            CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(),
                                              kCFRunLoopCommonModes);
            CFReadStreamClose(stream);
            CFRelease(stream);
            break;
        case kCFStreamEventEndEncountered:
            reportCompletion();
            CFReadStreamUnscheduleFromRunLoop(stream, CFRunLoopGetCurrent(),
                                              kCFRunLoopCommonModes);
            CFReadStreamClose(stream);
            CFRelease(stream);
            break;
    }
}

Когда функция обратного вызова получает kCFStreamEventHasBytesAvailable код события, это вызывает CFReadStreamRead считывать данные.

Когда функция обратного вызова получает kCFStreamEventErrorOccurred код события, это вызывает CFReadStreamGetError получить ошибку и ее собственную функцию ошибок (reportError) обрабатывать ошибку.

Когда функция обратного вызова получает kCFStreamEventEndEncountered код события, это вызывает свою собственную функцию (reportCompletion) для обработки конца данных и затем вызывает CFReadStreamUnscheduleFromRunLoop функция для удаления потока из указанного цикла выполнения. Тогда CFReadStreamClose функция выполняется для закрытия потока и CFRelease выпускать потоковую ссылку.

Опрос сетевого потока

В целом опрос сетевого потока нецелесообразен. Однако при определенных редких обстоятельствах, может быть полезно сделать так. Для опроса потока Вы сначала проверяете, чтобы видеть, готовы ли потоки к чтению или записи, то выполняют операцию чтения или операцию записи на потоке.

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

Точно так же для потока чтения, перед вызовом CFReadStreamRead, вызовите функцию CFReadStreamHasBytesAvailable.

Перечисление 2-9 является примером опроса для потока чтения.

Перечисление 2-9  , Опрашивающее поток чтения

while (!done) {
    if (CFReadStreamHasBytesAvailable(myReadStream)) {
        UInt8 buf[BUFSIZE];
        CFIndex bytesRead = CFReadStreamRead(myReadStream, buf, BUFSIZE);
        if (bytesRead < 0) {
            CFStreamError error = CFReadStreamGetError(myReadStream);
            reportError(error);
        } else if (bytesRead == 0) {
            if (CFReadStreamGetStatus(myReadStream) == kCFStreamStatusAtEnd) {
                done = TRUE;
            }
        } else {
            handleBytes(buf, bytesRead);
        }
    } else {
        // ...do something else while you wait...
    }
}

Перечисление 2-10 является примером опроса для потока записи.

Перечисление 2-10  , Опрашивающее поток записи

UInt8 buf[] = “Hello, world”;
UInt32 bufLen = strlen(buf);
 
while (!done) {
    if (CFWriteStreamCanAcceptBytes(myWriteStream)) {
        int bytesWritten = CFWriteStreamWrite(myWriteStream, buf, strlen(buf));
        if (bytesWritten < 0) {
            CFStreamError error = CFWriteStreamGetError(myWriteStream);
            reportError(error);
        } else if (bytesWritten == 0) {
            if (CFWriteStreamGetStatus(myWriteStream) == kCFStreamStatusAtEnd)
            {
                done = TRUE;
            }
        } else if (bytesWritten != strlen(buf)) {
            // Determine how much has been written and adjust the buffer
            bufLen = bufLen - bytesWritten;
            memmove(buf, buf + bytesWritten, bufLen);
 
            // Figure out what went wrong with the write stream
            CFStreamError error = CFWriteStreamGetError(myWriteStream);
            reportError(error);
        }
    } else {
        // ...do something else while you wait...
    }
}

Навигация по брандмауэрам

Существует два способа применить настройки брандмауэра к потоку. Для большинства потоков можно получить настройки прокси с помощью SCDynamicStoreCopyProxies функционируйте и затем примените результат к потоку путем установки kCFStreamHTTPProxy (или kCFStreamFTPProxy) свойство. SCDynamicStoreCopyProxies функция является частью платформы Конфигурации системы, таким образом, необходимо включать <SystemConfiguration/SystemConfiguration.h> в Вашем проекте использовать функцию. Тогда просто выпустите ссылку словаря прокси, когда Вы будете сделаны с нею. Процесс был бы похож на это в Перечислении 2-11.

Перечисление 2-11  , Перемещающееся по потоку через прокси-сервер

CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);

Однако, если необходимо часто использовать настройки прокси для многократных потоков, это становится немного более сложным. В этом случае, получающем настройки брандмауэра машины пользователя, требует пяти шагов:

  1. Создайте единственный, персистентный дескриптор к сеансу динамической памяти, SCDynamicStoreRef.

  2. Поместите дескриптор в сеанс динамической памяти в цикл выполнения, который будет уведомлен относительно изменений прокси.

  3. Использовать SCDynamicStoreCopyProxies получать последние настройки прокси.

  4. Обновите свою копию прокси, когда сказали об изменениях.

  5. Очистите SCDynamicStoreRef когда Вы заканчиваете с ним.

Для создания дескриптора к сеансу динамической памяти используйте функцию SCDynamicStoreCreate и передайте средство выделения, имя для описания процесса, функции обратного вызова и контекста динамической памяти, SCDynamicStoreContext. Это выполняется при инициализации приложения. Код был бы подобен этому в Перечислении 2-12.

Перечисление 2-12  , Создающее дескриптор к сеансу динамической памяти

SCDynamicStoreContext context = {0, self, NULL, NULL, NULL};
systemDynamicStore = SCDynamicStoreCreate(NULL,
                                          CFSTR("SampleApp"),
                                          proxyHasChanged,
                                          &context);

После создания ссылки на динамическую память необходимо добавить его к циклу выполнения. Во-первых, возьмите ссылку динамической памяти и установите ее для контроля для любых изменений в прокси. Это выполняется с функциями SCDynamicStoreKeyCreateProxies и SCDynamicStoreSetNotificationKeys. Затем можно добавить ссылку динамической памяти на цикл выполнения с функциями SCDynamicStoreCreateRunLoopSource и CFRunLoopAddSource. Ваш код должен быть похожим на это в Перечислении 2-13.

Перечисление 2-13  , Добавляющее ссылку динамической памяти на цикл выполнения

// Set up the store to monitor any changes to the proxies
CFStringRef proxiesKey = SCDynamicStoreKeyCreateProxies(NULL);
CFArrayRef keyArray = CFArrayCreate(NULL,
                                    (const void **)(&proxiesKey),
                                    1,
                                    &kCFTypeArrayCallBacks);
SCDynamicStoreSetNotificationKeys(systemDynamicStore, keyArray, NULL);
CFRelease(keyArray);
CFRelease(proxiesKey);
 
// Add the dynamic store to the run loop
CFRunLoopSourceRef storeRLSource =
    SCDynamicStoreCreateRunLoopSource(NULL, systemDynamicStore, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), storeRLSource, kCFRunLoopCommonModes);
CFRelease(storeRLSource);

Как только ссылка динамической памяти была добавлена к циклу выполнения, используйте его, чтобы предварительно загрузить словарь прокси текущие настройки прокси путем вызова SCDynamicStoreCopyProxies. См. Перечисление 2-14 для того, как сделать это.

Перечисление 2-14  , Загружающее словарь прокси

gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);

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

  Функция обратного вызова Прокси перечисления 2-15

void proxyHasChanged() {
    CFRelease(gProxyDict);
    gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}

Так как вся информация прокси актуальна, примените прокси. После создания Вашего чтения или потока записи, набор kCFStreamPropertyHTTPProxy прокси путем вызывания функций CFReadStreamSetProperty или CFWriteStreamSetProperty. Если Ваш поток был вызванным потоком чтения readStream, Ваш вызов функции походил бы на это в Перечислении 2-16.

Перечисление 2-16  , Добавляющее информацию прокси к потоку

CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);

Когда Вы будете все сделаны с использованием настроек прокси, удостоверьтесь, что выпустили словарь и ссылку динамической памяти, и удалили ссылку динамической памяти из цикла выполнения. См. Перечисление 2-17.

Перечисление 2-17  , Очищающее информацию прокси

if (gProxyDict) {
    CFRelease(gProxyDict);
}
 
// Invalidate the dynamic store's run loop source
// to get the store out of the run loop
CFRunLoopSourceRef rls = SCDynamicStoreCreateRunLoopSource(NULL, systemDynamicStore, 0);
CFRunLoopSourceInvalidate(rls);
CFRelease(rls);
CFRelease(systemDynamicStore);