Working with Streams
@官方文檔翻譯-李冰
@譯文
這一章介紹怎樣對讀/寫流進(jìn)行創(chuàng)建、打開、和錯誤檢查。描述了怎么從讀流中讀取,怎樣用寫流輸出,當(dāng)讀取和寫入一個流的時候怎樣防止阻塞,還有怎樣去通過代理服務(wù)器導(dǎo)流。
Working with Read Streams 用讀取流工作
Core Foundation 中的流可以被使用于讀取和寫入文件或者用于網(wǎng)絡(luò)套接字。除了創(chuàng)建流的工程以外,它們原理類似。
創(chuàng)建一個讀流
首先創(chuàng)建一個讀取流。表2-1為一個文件創(chuàng)建一個讀取流。
表 2-1 Creating a read stream from a file 從一個文件創(chuàng)建讀取流
CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);
在此表中,kCFAllocatorDefault參數(shù)指定當(dāng)前默認(rèn)系統(tǒng)分配器為流分配內(nèi)存空間,fileURL參數(shù)為指定創(chuàng)建流的文件名。比如file:///Users/joeuser/Downloads/MyApp.sit。
同樣,你可以調(diào)用CFStreamCreatePairWithSocketToCFHost(在下文 Using a Run Loop to Prevent Blocking中描述)或CFStreamCreatePairWithSocketToNetService(在下文 NSNetServices and CFNetServices Programming Guide中描述)創(chuàng)建一對基于網(wǎng)絡(luò)服務(wù)的流。
現(xiàn)在已經(jīng)創(chuàng)建了流,你可以打開流。打開流將使流保留它需要的任何系統(tǒng)資源,比如打開文件所需要的文件描述符。打開讀流的示例見表2-2。
表2-2Creating a Read Stream 創(chuàng)建一個讀取流
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,示例程序調(diào)用CFReadStreamGetError方法,并返回CFStreamError結(jié)構(gòu)體類型包含兩個值:域碼和錯誤碼。域碼表示對錯誤碼的解釋。比如,如果域碼是kCFStreamErrorDomainPOSIX,錯誤碼是UNIX errno值。其他的錯誤域是kCFStreamErrorDomainMacOSStatus,表示錯誤碼是一個在MacErrors.h聲明的OSStatus值,以及kCFStreamErrorDomainHTTP表示的聲明的CFStreamErrorHTTP枚舉錯誤碼。
打開流可能是一個很長的過程,所以CFReadStreamOpen和CFWriteStreamOpen通過返回值TRUE表示打開流的過程已經(jīng)開始來避免阻塞。檢查打開狀態(tài),可調(diào)用CFReadStreamGetStatus和CFWriteStreamGetStatus方法,通過返回kCFStreamStatusOpening表示正在打開的過程狀態(tài),通過返回kCFStreamStatusOpen表示打開完成,或者通過kCFStreamStatusErrorOccurred表示打開完成但是失敗。大多數(shù)情況下,打開是否完成無關(guān)緊要,因為CFStream方法讀流和寫流將阻塞至流打開完成。
Reading from a Read Stream 從讀取流中讀取
從讀流中讀取數(shù)據(jù),調(diào)用CFReadStreamRead方法,這類似于UNIX read()系統(tǒng)調(diào)用。兩者需要buffer和buffer length參數(shù)。兩者都返回讀取的字節(jié)數(shù)量,在流或者文件結(jié)束時返回0或者發(fā)生錯誤時返回-1.兩者都會阻塞直到至少一個字節(jié)可以被讀取,只要沒有阻塞兩者都可以持續(xù)讀取。從讀流中讀取示例見表2-3。
表2-3 Reading from a read stream (blocking) 從讀流中讀取(阻塞)
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 );
Tearing Down a Read Stream 拆除一個讀流
當(dāng)讀取所有數(shù)據(jù)時,需要調(diào)用CFReadStreamClose方法去關(guān)閉流,從而釋放關(guān)聯(lián)的系統(tǒng)資源。然后參考調(diào)用CFRelease方法釋放流。你可能還想通過將引用設(shè)置為NULL使引用無效。見示例表2-4。
表 2-4Releasing a read stream 釋放一個讀流
CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;
Working with Write Streams 操作寫流
操作寫流類似于操作讀流。一個主要的不同點在于CFWriteStreamWrite方法不能保證接受所有傳入的字節(jié)。反而,CFWriteStreamWrite返回它接受的字節(jié)數(shù)量。你可以注意到示例2-5如果寫入字節(jié)數(shù)和要寫入的字節(jié)總數(shù)不一樣,則通過調(diào)整緩存區(qū)以去適應(yīng)。
表 2-5Creating, opening, writing to, and releasing a write stream 創(chuàng)建,打開,寫入和釋放寫流
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;
Preventing Blocking When Working with Streams 操作流時防止阻塞
當(dāng)使用流建立連接時,總是會發(fā)生,尤其是基于套接字的流在數(shù)據(jù)傳輸?shù)臅r候可能會花費很長的時間。如果你用同步的方式實現(xiàn)流,整個程序都將被迫等待數(shù)據(jù)傳輸。因此,強(qiáng)烈推薦你替換方法來防止阻塞。
有兩種方式來防止當(dāng)CFStream對象讀和寫的時候阻塞:
- 使用 run loop — 注冊接收流相關(guān)的時間并把流添加到run loop 表。當(dāng)一個流相事件發(fā)生,你的回調(diào)函數(shù)被調(diào)用(指定注冊的回調(diào)) 。
- Polling(輪詢) — 對于讀取流,在從流讀取前發(fā)現(xiàn)可讀字節(jié)。對于寫流,在寫入流阻塞前發(fā)現(xiàn)流是否可以被寫入 。
這些處理在以下章節(jié)中描述。
用Run Loop防止阻塞
優(yōu)先的方式是在run loop中使用流。在你的主線程執(zhí)行一個run loop。它等待事件發(fā)生,然后調(diào)用任何一個和發(fā)生的事件關(guān)聯(lián)的函數(shù)。
在網(wǎng)絡(luò)傳輸事件中,當(dāng)你注冊的事件發(fā)生時回調(diào)函數(shù)被run loop執(zhí)行。
這讓你不需要輪詢你的套接字流而讓線程變慢的。
了解更多關(guān)于run loops,閱讀 Threading Programming Guide.
這個例子首先創(chuàng)建套接字讀取流:
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port,
&myReadStream, NULL);
CFHost對象引用,host,指定讀取流建立連接的遠(yuǎn)程主機(jī)和port參數(shù)指定主機(jī)使用的端口號。CFStreamCreatePairWithSocketToCFHost函數(shù)返回新的myReadStream讀取流引用。最后一個參數(shù),NULL,表示調(diào)用者不需要創(chuàng)建寫入流。如果你想創(chuàng)建寫入流,最后一個參數(shù)需要傳入,比如&myWriteStream。
在打開套接字讀取流之前,創(chuàng)建一個上下文將用于你注冊接收流相關(guān)事件:
CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc};
第一個參數(shù)是0指定版本號。info參數(shù),myPtr,是一個指向你想傳入到回調(diào)函數(shù)的指針。通常,myPtr是一個指向你已定義包含流相關(guān)信息的結(jié)構(gòu)體的指針。retain參數(shù)是一個指向持有info參數(shù)的函數(shù)的指針。所以如果你設(shè)置成你的函數(shù)myRetain,像上面的代碼中一樣,CFStream將調(diào)用myRetain(myPtr)去持有info指針。同樣的,release參數(shù),myRelease,是一個指向釋放info參數(shù)的指針。當(dāng)流從上下文分離,CFStream將調(diào)用myRelease(myPtr)。最后,copyDescription是一個提供流的描述的函數(shù)。例如,如果你 如上所示對流端上下文調(diào)用 CFCopyDesc(myReadStream),CFStream將調(diào)用myCopyDesc(myPtr)。
客戶端上下文還允許你設(shè)置選項的retain,release和copyDescription參數(shù)為NULL。如果你設(shè)置retain和release參數(shù)為NULL, 然后系統(tǒng)會期望你去保存可用的info指針指向的內(nèi)存知道流本身被銷毀。如果你將copyDescription參數(shù)為NULL,然后系統(tǒng)將提供,如果需要 ,info指針指向的內(nèi)存是什么的基本的描述。
客戶端上下文設(shè)置完成后,調(diào)用CFReadStreamSetClient去注冊接收相關(guān)的事件。CFReadStreamSetClient要求你指定回調(diào)函數(shù)和你希望接收的事件。下面的表2-6示例指定希望接收kCFStreamEventHasBytesAvailable、kCFStreamEventErrorOccurred和kCFStreamEventEndEncountered事件的回調(diào)函數(shù)。然后調(diào)用函數(shù)CFReadStreamScheduleWithRunLoop把流排進(jìn)run loop 。查看表2-6怎么完成這些:
表 2-6 把流排進(jìn)run loop
CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable |
kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext))
{
CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
}
當(dāng)把流排進(jìn)到run loop后,你可以準(zhǔn)備打開這個流如表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();
}
現(xiàn)在,等待你的互調(diào)函數(shù)被執(zhí)行。在你的回調(diào)函數(shù),檢查事件碼并采取適當(dāng)?shù)牟僮鳌R姳?2-8.
表 2-8 網(wǎng)絡(luò)事件回調(diào)函數(shù)
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;
}
}
當(dāng)回調(diào)函數(shù)接收到kCFStreamEventHasBytesAvailable事件碼,它將調(diào)用CFReadStreamRead去讀取數(shù)據(jù)。
當(dāng)回調(diào)函數(shù)接收到kCFStreamEventErrorOccurred事件碼,它調(diào)用CFReadStreamGetError獲得錯誤和自己的錯誤函數(shù)(reportError)去處理錯誤。
當(dāng)回調(diào)函數(shù)接收到kCFStreamEventEndEncountered事件碼,它調(diào)用自己的函數(shù)reportCompletion去處理結(jié)束數(shù)據(jù),然后調(diào)用CFReadStreamUnscheduleFromRunLoop函數(shù)去移除指定的run loop中的流。然后CFReadStreamClose函數(shù)運行關(guān)閉流并且CFRelease去釋放流引用。
輪詢一個網(wǎng)絡(luò)流
一般,輪詢一個網(wǎng)絡(luò)流是不明智的。然而,在某些罕見的情況下,它也可以非常有用。輪詢一個流,首先你得檢查這個流已準(zhǔn)備讀取和寫入,然后對流執(zhí)行讀取和寫入操作。
當(dāng)用寫入流寫入的時候,你可以調(diào)用CFWriteStreamCanAcceptBytes判斷流是否可以接受數(shù)據(jù)。如果返回TRUE,隨后你可以放心的調(diào)用非阻塞函數(shù)CFWriteStreamWrite立即發(fā)送數(shù)據(jù)。
同樣的,對于讀取流,在調(diào)用CFReadStreamRead之前,調(diào)用函數(shù)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...
}
}
操作防火墻
這里又兩個方式去申請為流做防火墻設(shè)置。對于大多數(shù)流而言,你可以用SCDynamicStoreCopyProxies函數(shù)檢索代理設(shè)置和設(shè)置kCFStreamHTTPProxy (or kCFStreamFTPProxy) 屬性后將結(jié)果應(yīng)用于流中。SCDynamicStoreCopyProxies函數(shù)是系統(tǒng)配置框架的一部分,所以在你的工程中你需要包含<SystemConfiguration/SystemConfiguration.h>來使用這個方法。然后當(dāng)你使用完成后只需要釋放代理字典引用。這個過程看起來就像表2-11。
表 2-11 通過代理服務(wù)操作流
CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);
不管怎樣,如果你需要經(jīng)常使用代理設(shè)置多個流,將會變得有點更復(fù)雜。在這種情況下,檢索用戶機(jī)器的防火墻設(shè)置需要舞五步:
1.為動態(tài)存儲會話創(chuàng)建一個持久句柄,SCDynamicStoreRef。
2.將動態(tài)存儲會話句柄添加到run loop去通知代理變更。
3.用SCDynamicStoreCopyProxies 去檢索最后一次代理設(shè)置。
4.更新你的代理副本時通知變更。
5.當(dāng)你通過SCDynamicStoreRef時,清理它。Clean up the SCDynamicStoreRef when you are through with it.
創(chuàng)建動態(tài)存儲會話句柄,使用SCDynamicStoreCreate函數(shù)并傳入分配器,一個描述過程的名字,一個回調(diào)函數(shù)和一個動態(tài)存儲上下文,SCDynamicStoreContext。當(dāng)你初始化應(yīng)用程序時運行。
示例代碼類似于表2-12。
表 2-12 創(chuàng)建一個動態(tài)存儲會話句柄
SCDynamicStoreContext context = {0, self, NULL, NULL, NULL};
systemDynamicStore = SCDynamicStoreCreate(NULL,
CFSTR("SampleApp"),
proxyHasChanged,
&context);
創(chuàng)建動態(tài)存儲引用過后,你需要添加到run loop。首先,采取動態(tài)存儲引用并將其設(shè)置為監(jiān)視代理的任何更改。這是通過SCDynamicStoreKeyCreateProxies和 SCDynamicStoreSetNotificationKeys 函數(shù)完成的。然后,你可以使用SCDynamicStoreCreateRunLoopSource和CFRunLoopAddSource函數(shù)添加動態(tài)存儲引用到run loop。代碼如表2-13。
表 2-13 添加一個動態(tài)存儲引用到run loop
// 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);
一旦動態(tài)存儲引用被加入到run loop中,調(diào)用SCDynamicStoreCopyProxies當(dāng)前的代理設(shè)置來預(yù)加載代理字典。 表2-14展示了如何做到這些。
表 2-14 加載代理字典
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
由于將動態(tài)存儲引用添加到run loop中,每一次代理變更將運行你的回調(diào)函數(shù)。釋放當(dāng)前的代理字典并加載到新的代理設(shè)置。
As a result of adding the dynamic store reference to the run loop, each time the proxies are changed your callback function will be run. Release the current proxy dictionary and reload it with the new proxy settings. 示例回調(diào)函數(shù)將與列表2-15中的回調(diào)函數(shù)類似。
表 2-15 代理回調(diào)函數(shù)
void proxyHasChanged() {
CFRelease(gProxyDict);
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}
由于所有的代理信息是最新的,應(yīng)用代理。當(dāng)創(chuàng)建你的讀取或?qū)懭肓髦螅ㄟ^調(diào)用CFReadStreamSetProperty或CFWriteStreamSetProperty函數(shù)設(shè)置kCFStreamPropertyHTTPProxy代理。如果你的流是讀取流調(diào)用readStream,函數(shù)如表2-16所示。
表 2-16 添加代理信息到流中
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);
當(dāng)你完成所有的代理設(shè)置,要確保字典和動態(tài)存儲引用釋放,并且將動態(tài)存儲引用從run loop移除。見表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);
Working with Streams
This chapter discusses how to create, open, and check for errors on read and write streams. It also describes how to read from a read stream, how to write to a write stream, how to prevent blocking when reading from or writing to a stream, and how to navigate a stream through a proxy server.
Working with Read Streams
Core Foundation streams can be used for reading or writing files or working with network sockets. With the exception of the process of creating those streams, they behave similarly.
Creating a Read Stream
Start by creating a read stream. Listing 2-1 creates a read stream for a file.
Listing 2-1 Creating a read stream from a file
CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);
In this listing, the kCFAllocatorDefault parameter specifies that the current default system allocator be used to allocate memory for the stream and the fileURL parameter specifies the name of the file for which this read stream is being created, such as file:///Users/joeuser/Downloads/MyApp.sit.
Similarly, you can create a pair of streams based on a network service by calling CFStreamCreatePairWithSocketToCFHost (described in Using a Run Loop to Prevent Blocking) or CFStreamCreatePairWithSocketToNetService (described in NSNetServices and CFNetServices Programming Guide).
Now that you have created the stream, you can open it. Opening a stream causes the stream to reserve any system resources that it requires, such as the file descriptor needed to open the file. Listing 2-2 is an example of opening the read stream.
Listing 2-2 Opening a read stream
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.
}
}
The CFReadStreamOpen function returns TRUE to indicate success and FALSE if the open fails for any reason. If CFReadStreamOpen returns FALSE, the example calls the CFReadStreamGetError function, which returns a structure of type CFStreamError consisting of two values: a domain code and an error code. The domain code indicates how the error code should be interpreted. For example, if the domain code is kCFStreamErrorDomainPOSIX, the error code is a UNIX errno value. The other error domains are kCFStreamErrorDomainMacOSStatus, which indicates that the error code is an OSStatus value defined in MacErrors.h, and kCFStreamErrorDomainHTTP, which indicates that the error code is the one of the values defined by the CFStreamErrorHTTP enumeration.
Opening a stream can be a lengthy process, so the CFReadStreamOpen and CFWriteStreamOpen functions avoid blocking by returning TRUE to indicate that the process of opening the stream has begun. To check the status of the open, call the functions CFReadStreamGetStatus and CFWriteStreamGetStatus, which return kCFStreamStatusOpening if the open is still in progress, kCFStreamStatusOpen if the open is complete, or kCFStreamStatusErrorOccurred if the open has completed but failed. In most cases, it doesn’t matter whether the open is complete because the CFStream functions that read and write will block until the stream is open.
Reading from a Read Stream
To read from a read stream, call the function CFReadStreamRead, which is similar to the UNIX read() system call. Both take buffer and buffer length parameters. Both return the number of bytes read, 0 if at the end of stream or file, or -1 if an error occurred. Both block until at least one byte can be read, and both continue reading as long as they can do so without blocking. Listing 2-3 is an example of reading from the read stream.
Listing 2-3 Reading from a read stream (blocking)
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 );
Tearing Down a Read Stream
When all data has been read, you should call the CFReadStreamClose function to close the stream, thereby releasing system resources associated with it. Then release the stream reference by calling the function CFRelease. You may also want to invalidate the reference by setting it to NULL. See Listing 2-4 for an example.
Listing 2-4 Releasing a read stream
CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;
Working with Write Streams
Working with write streams is similar to working with read streams. One major difference is that the function CFWriteStreamWrite does not guarantee to accept all of the bytes that you pass it. Instead, CFWriteStreamWrite returns the number of bytes that it accepted. You'll notice in the sample code shown in Listing 2-5 that if the number of bytes written is not the same as the total number of bytes to be written, the buffer is adjusted to accommodate this.
Listing 2-5 Creating, opening, writing to, and releasing a write stream
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;
Preventing Blocking When Working with Streams
When using streams to communicate, there is always a chance, especially with socket-based streams, that a data transfer could take a long time. If you are implementing your streams synchronously your entire application will be forced to wait on the data transfer. Therefore, it is highly recommended that your code use alternate methods to prevent blocking.
There are two ways to prevent blocking when reading from or writing to a CFStream object:
- Using a run loop — Register to receive stream-related events and schedule the stream on a run loop. When a stream-related event occurs, your callback function (specified by the registration call) is called.
- Polling — For read streams, find out if there are bytes to read before reading from the stream. For write streams, find out whether the stream can be written to without blocking before writing to the stream.
Each of these approaches is described in the following sections.
Using a Run Loop to Prevent Blocking
The preferred way to use streams is with a run loop. A run loop executes on your main program thread. It waits for events to occur, then calls whatever function is associated with a given event.
In the case of network transfers, your callback functions are executed by the run loop when the event you registered for occurs. This allows you to not have to poll your socket stream, which would slow down the thread.
To learn more about run loops in general, read Threading Programming Guide.
This example begins by creating a socket read stream:
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port,
&myReadStream, NULL);
where the CFHost object reference, host, specifies the remote host with which the read stream is to be made and the port parameter specifies the port number that the host uses. The CFStreamCreatePairWithSocketToCFHost function returns the new read stream reference in myReadStream. The last parameter, NULL, indicates that the caller does not want to create a write stream. If you wanted to create a write steam, the last parameter would be, for example, &myWriteStream.
Before opening the socket read stream, create a context that will be used when you register to receive stream-related events:
CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc};
The first parameter is 0 to specify the version number. The info parameter, myPtr, is a pointer to data you want to be passed to your callback function. Usually, myPtr is a pointer to a structure you’ve defined that contains information relating to the stream. The retain parameter is a pointer to a function to retain the info parameter. So if you set it to your function myRetain, as in the code above, CFStream will call myRetain(myPtr) to retain the info pointer. Similarly, the release parameter, myRelease, is a pointer to a function to release the info parameter. When the stream is disassociated from the context, CFStream would call myRelease(myPtr). Finally, copyDescription is a parameter to a function to provide a description of the stream. For example, if you were to call CFCopyDesc(myReadStream) with the stream client context shown above, CFStream would call myCopyDesc(myPtr).
The client context also allows you the option of setting the retain, release, and copyDescription parameters to NULL. If you set the retain and release parameters to NULL, then the system will expect you to keep the memory pointed to by the info pointer alive until the stream itself is destroyed. If you set the copyDescription parameter to NULL, then the system will provide, if requested, a rudimentary description of what is in the memory pointed to by the info pointer.
With the client context set up, call the function CFReadStreamSetClient to register to receive stream-related events. CFReadStreamSetClient requires that you specify the callback function and the events you want to receive. The following example in Listing 2-6 specifies that the callback function wants to receive the kCFStreamEventHasBytesAvailable, kCFStreamEventErrorOccurred, and kCFStreamEventEndEncountered events. Then schedule the stream on a run loop with the CFReadStreamScheduleWithRunLoop function. See Listing 2-6 for an example of how to do this.
Listing 2-6 Scheduling a stream on a run loop
CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable |
kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext))
{
CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
}
With the stream scheduled on the run loop, you are ready to open the stream as shown in Listing 2-7.
Listing 2-7 Opening a nonblocking read stream
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();
}
Now, wait for your callback function to be executed. In your callback function, check the event code and take appropriate action. See Listing 2-8.
Listing 2-8 Network events callback function
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;
}
}
When the callback function receives the kCFStreamEventHasBytesAvailable event code, it calls CFReadStreamRead to read the data.
When the callback function receives the kCFStreamEventErrorOccurred event code, it calls CFReadStreamGetError to get the error and its own error function (reportError) to handle the error.
When the callback function receives the kCFStreamEventEndEncountered event code, it calls its own function (reportCompletion) for handling the end of data and then calls the CFReadStreamUnscheduleFromRunLoop function to remove the stream from the specified run loop. Then the CFReadStreamClose function is run to close the stream and CFRelease to release the stream reference.
Polling a Network Stream
In general, polling a network stream is inadvisable. However, in certain rare circumstances, it can be useful to do so. To poll a stream, you first check to see if the streams are ready for reading or writing, then perform a read or write operation on the stream.
When writing to a write stream, you can determine if the stream is ready to accept data by calling CFWriteStreamCanAcceptBytes. If it returns TRUE, then you can be assured that a subsequent call to the CFWriteStreamWrite function will send data immediately without blocking.
Similarly, for a read stream, before calling CFReadStreamRead, call the function CFReadStreamHasBytesAvailable.
Listing 2-9 is a polling example for a read stream.
Listing 2-9 Polling a read stream
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...
}
}
Listing 2-10 is a polling example for a write stream.
Listing 2-10
Polling a write stream
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...
}
}
Navigating Firewalls
There are two ways to apply firewall settings to a stream. For most streams, you can retrieve the proxy settings using the SCDynamicStoreCopyProxies function and then apply the result to the stream by setting the kCFStreamHTTPProxy (or kCFStreamFTPProxy) property. The SCDynamicStoreCopyProxies function is part of the System Configuration framework, so you need to include <SystemConfiguration/SystemConfiguration.h> in your project to use the function. Then just release the proxy dictionary reference when you are done with it. The process would look like that in Listing 2-11.
Listing 2-11 Navigating a stream through a proxy server
CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);
However, if you need to use the proxy settings often for multiple streams, it becomes a bit more complicated. In this case retrieving the firewall settings of a user's machine requires five steps:
1.Create a single, persistent handle to a dynamic store session, SCDynamicStoreRef.
2.Put the handle to the dynamic store session into the run loop to be notified of proxy changes.
3.Use SCDynamicStoreCopyProxies to retrieve the latest proxy settings.
4.Update your copy of the proxies when told of the changes.
5.Clean up the SCDynamicStoreRef when you are through with it.
To create the handle to the dynamic store session, use the function SCDynamicStoreCreate and pass an allocator, a name to describe your process, a callback function and a dynamic store context, SCDynamicStoreContext. This is run when initializing your application. The code would be similar to that in Listing 2-12.
Listing 2-12 Creating a handle to a dynamic store session
SCDynamicStoreContext context = {0, self, NULL, NULL, NULL};
systemDynamicStore = SCDynamicStoreCreate(NULL,
CFSTR("SampleApp"),
proxyHasChanged,
&context);
After creating the reference to the dynamic store, you need to add it to the run loop. First, take the dynamic store reference and set it up to monitor for any changes to the proxies. This is accomplished with the functions SCDynamicStoreKeyCreateProxies and SCDynamicStoreSetNotificationKeys. Then, you can add the dynamic store reference to the run loop with the functions SCDynamicStoreCreateRunLoopSource and CFRunLoopAddSource. Your code should look like that in Listing 2-13.
Listing 2-13 Adding a dynamic store reference to the run loop
// 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);
Once the dynamic store reference has been added to the run loop, use it to preload the proxy dictionary the current proxy settings by calling SCDynamicStoreCopyProxies. See Listing 2-14 for how to do this.
Listing 2-14 Loading the proxy dictionary
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
As a result of adding the dynamic store reference to the run loop, each time the proxies are changed your callback function will be run. Release the current proxy dictionary and reload it with the new proxy settings. A sample callback function would look like the one in Listing 2-15.
Listing 2-15 Proxy callback function
void proxyHasChanged() {
CFRelease(gProxyDict);
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}
Since all of the proxy information is up-to-date, apply the proxies. After creating your read or write stream, set the kCFStreamPropertyHTTPProxy proxy by calling the functions CFReadStreamSetProperty or CFWriteStreamSetProperty. If your stream was a read stream called readStream, your function call would be like that in Listing 2-16.
Listing 2-16 Adding proxy information to a stream
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);
When you are all done with using the proxy settings, make sure to release the dictionary and dynamic store reference, and to remove the dynamic store reference from the run loop. See Listing 2-17.
Listing 2-17 Cleaning up proxy information
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);