Работа с потоками
В этой главе рассматриваются, как создать, открыть, и проверить на ошибки на потоках записи и чтении. Это также описывает, как читать из потока чтения, как записать в поток записи, как предотвратить блокирование при чтении из или записи в поток, и как переместиться по потоку через прокси-сервер.
Работа с потоками чтения
Базовые потоки Основы могут использоваться для чтения или записи файлов или работы с сетевыми сокетами. За исключением процесса создания тех потоков, они ведут себя так же.
Создание потока чтения
Запустите путем создания потока чтения. Перечисление 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); |
Однако, если необходимо часто использовать настройки прокси для многократных потоков, это становится немного более сложным. В этом случае, получающем настройки брандмауэра машины пользователя, требует пяти шагов:
Создайте единственный, персистентный дескриптор к сеансу динамической памяти,
SCDynamicStoreRef
.Поместите дескриптор в сеанс динамической памяти в цикл выполнения, который будет уведомлен относительно изменений прокси.
Использовать
SCDynamicStoreCopyProxies
получать последние настройки прокси.Обновите свою копию прокси, когда сказали об изменениях.
Очистите
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); |