本文討論如何創(chuàng)建、打開和檢查讀寫流錯誤。它還描述了如何從讀取流讀取信息,如何從寫入信息到寫入流,如何在讀取或?qū)懭肓鲿r防止阻塞以及如何通過代理服務(wù)器導(dǎo)航到流。
使用讀取流
核心基礎(chǔ)流可用于讀取或?qū)懭胛募蚴褂镁W(wǎng)絡(luò)套接字。除了創(chuàng)建這些流過程中的異常,其他行為類似。
創(chuàng)建一個讀取流
首先創(chuàng)建一個讀取流。列表2-1為一個文件創(chuàng)建讀取流。
列表2-1 為一個文件創(chuàng)建讀取流
<pre><code>CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);
</pre></code>
在這個列表中,kCFAllocatorDefault 參數(shù)指定當(dāng)前默認(rèn)系統(tǒng)分配器來為流分配內(nèi)存,fileURL 參數(shù)指定讀取流創(chuàng)建到的文件名稱,例如 file:///Users/joeuser/Downloads/MyApp.sit.
類似的,你可以通過調(diào)用CFStreamCreatePairWithSocketToCFHost(在使用運(yùn)行循環(huán)阻止阻塞(Using a Run Loop to Prevent Blocking)中有描述)或者CFStreamCreatePairWithSocketToNetService (在NSNetServices and CFNetServices 編程指南(NSNetServices and CFNetServices Programming Guide)中有描述)來創(chuàng)建一對基于網(wǎng)絡(luò)服務(wù)的流。
現(xiàn)在,你已經(jīng)創(chuàng)建流,你可以打開它。打開流將導(dǎo)致流保留需要的任何系統(tǒng)資源,例如打開文件所需的文件描述符。列表2-1是一個打開讀取流的例子。
列表2-2 打開讀取流
<pre><code>
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.
}
}
</pre></code>
CFReadStreamOpen 函數(shù)返回TRUE 表示成功,FALSE 表示由于某種原因打開失敗。如果CFReadStreamOpen 返回FALSE,例子調(diào)用CFReadStreamGetError 函數(shù),將返回CFStreamError 類型結(jié)構(gòu),包含兩個值:一個域代碼和一個錯誤代碼。域代碼表明錯誤代碼應(yīng)該如何解釋。例如,如果域代碼是kCFStreamErrorDomainPOSIX,錯誤代碼是UNIX errno值。其他錯誤域是kCFStreamErrorDomainMacOSStatus,表明錯誤代碼是一個定義在MacErrors.h中的OSStatus 值,kCFStreamErrorDomainHTTP表明錯誤代碼是CFStreamErrorHTTP 枚舉中定義的值。
打開一個流是一個漫長的過程,所以CFReadStreamOpen 函數(shù)和CFWriteStreamOpen 函數(shù)返回TRUE 表明打開流的過程已經(jīng)開始,來避免阻塞。為了檢查打開的裝填,可以調(diào)用CFReadStreamGetStatus 和CFWriteStreamGetStatus函數(shù),如果仍處于打開過程則返回kCFStreamStatusOpening ,如果已經(jīng)打開則返回kCFStreamStatusOpen ,如果已經(jīng)打開但失敗了則返回kCFStreamStatusErrorOccurred 。在大多數(shù)情況下,打開是否完成無關(guān)緊要,因?yàn)镃FStream函數(shù)的讀寫將會阻塞直到打開流。
從讀取流中讀取信息
從讀取流中讀取信息,調(diào)用函數(shù)CFReadStreamRead,這個函數(shù)類似于UNIX read() 系統(tǒng)調(diào)用。兩者都采用緩存區(qū)和緩存區(qū)長度作為參數(shù)。兩者都返回讀取的字節(jié)數(shù),在流或文件末尾則返回0,錯誤發(fā)生則返回-1.兩者都阻塞直到可以讀取一個字節(jié)并繼續(xù)讀取。列表2-3是從讀取流中讀取信息的例子。
列表2-3 從讀取流(blocking)中讀取信息
<pre><code>
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 );
</pre></code>
釋放讀取流
當(dāng)所有數(shù)據(jù)都被讀取,你可以調(diào)用CFReadStreamClose 函數(shù)關(guān)閉流,從而釋放有關(guān)系統(tǒng)資源。然后通過調(diào)用CFRelease函數(shù)釋放流引用。你可以設(shè)置引用為NULL使其無效。如列表2-4的例子。
列表2-4 釋放讀取流
<pre><code>
CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;
</pre></code>
使用寫入流
使用寫入流類似于使用讀取流。一個主要的區(qū)別是CFWriteStreamWrite 函數(shù)并不能保證接收你傳遞給它的所有字節(jié)。相反,CFWriteStreamWrite 返回它接收的字節(jié)數(shù)。你會看到列表2-5中的實(shí)例代碼,如果寫入的字節(jié)數(shù)與需要寫入的總字節(jié)數(shù)不一致,緩存區(qū)會調(diào)整并適應(yīng)這一點(diǎn)。
列表2-5 創(chuàng)建、打開、寫入并釋放寫入流
<pre><code>
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;
</pre></code>
使用流時防止阻塞
當(dāng)使用流來通信時,特別是基于套接字的流,數(shù)據(jù)傳輸可能需要很長時間。如果你同步執(zhí)行你的流,你的整個應(yīng)用將被迫等待數(shù)據(jù)傳輸。因此,強(qiáng)烈建議你的代碼使用替代方法來防止阻塞。
當(dāng)讀取或?qū)懭胍粋€CFStream對象時,有兩種方法可以防止阻塞:
使用一個運(yùn)行循環(huán)——注冊賬戶接收stream-related 事件并安排流到一個運(yùn)行循環(huán)上。當(dāng)stream-related 事件發(fā)生時,調(diào)用你的回調(diào)函數(shù)(注冊調(diào)用時指定)。
輪詢——對于讀取流,在讀取流之前找出是否有需要讀取的字節(jié)。對于寫入流,在寫入流之前找出流是否可以無阻塞的寫入。
將在以下章節(jié)中討論這些方法。
使用運(yùn)行循環(huán)防止阻塞
使用流的首選方法是運(yùn)行循環(huán)。運(yùn)行循環(huán)在你的主線程上執(zhí)行。等待事件發(fā)生,然后調(diào)用與給定事件相關(guān)的函數(shù)。
在網(wǎng)絡(luò)傳輸?shù)那闆r下,當(dāng)你的注冊事件發(fā)生時,你的回調(diào)函數(shù)被運(yùn)行循環(huán)執(zhí)行。這樣你可以不必輪詢你的套接字流,可以減緩線程。
關(guān)于運(yùn)行循環(huán)的更多信息,參閱線程編程指南(Threading Programming Guide)。
這個例子首先創(chuàng)建一個套接字讀取流:
<pre><code>
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port,
&myReadStream, NULL);
</pre></code>
CFHost對象引用、host指定遠(yuǎn)程主機(jī)為讀取流的主機(jī),port 參數(shù)指定主機(jī)使用的端口號。CFStreamCreatePairWithSocketToCFHost 函數(shù)返回新的讀取流引用myReadStream。最后一個參數(shù)NULL表明調(diào)用者不希望創(chuàng)建寫入流。如果你想創(chuàng)建一個寫入流,最后一個參數(shù)為&myWriteStream。
在打開套接字讀取流之前,創(chuàng)建一個內(nèi)容,這樣當(dāng)你注冊接收 stream-related事件時可以使用。
<pre><code>CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc};
</pre></code>
第一個參數(shù)是0指定版本號。info 參數(shù)myPtr是你想要傳遞給回調(diào)函數(shù)的數(shù)據(jù)指針。通常,myPtr是一個指向結(jié)構(gòu)體的指針,在結(jié)構(gòu)體中你定義了包含有關(guān)流的信息。參數(shù)retain 是一個指向函數(shù)的指針,可以保留參數(shù)info 。所以在函數(shù)中設(shè)置為myRetain,在上面的代碼中,CFStream 將調(diào)用myRetain(myPtr)來保留info 指針。同樣,release 參數(shù)myRelease是一個指向函數(shù)的指針,釋放參數(shù)info 。當(dāng)流與內(nèi)容分離,CFStream將調(diào)用 myRelease(myPtr)。最后,copyDescription 是函數(shù)的一個參數(shù),該函數(shù)提供流的描述。例如,如果你是調(diào)用上文所示的CFCopyDesc(myReadStream) ,CFStream將調(diào)用myCopyDesc(myPtr)。
客戶端環(huán)境允許你設(shè)置retain, release和copyDescription 參數(shù)為NULL。如果你設(shè)置retain 和參數(shù)release 為NULL,系統(tǒng)將認(rèn)為info 指針指向的內(nèi)存一直存在直到流本身被銷毀。如果你將copyDescription 參數(shù)設(shè)置為NULL,如果有要求,系統(tǒng)將提供info 指針?biāo)赶騼?nèi)存的基本信息。
設(shè)置好客戶端環(huán)境后,調(diào)用函數(shù)CFReadStreamSetClient 登記接收有關(guān)流事件。CFReadStreamSetClient 要求你指定回調(diào)函數(shù)和你想接收的事件。列表2-6中的例子指定回調(diào)函數(shù)和接收的kCFStreamEventHasBytesAvailable,kCFStreamEventErrorOccurred和kCFStreamEventEndEncountered 事件。然后調(diào)用CFReadStreamScheduleWithRunLoop 函數(shù)安排流到一個運(yùn)行循環(huán)上。例子見列表2-6.
列表2-6 安排流到運(yùn)行循環(huán)上
<pre><code>
CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable |
kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext))
{
CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
}
</pre></code>
流安排到運(yùn)行循環(huán)上后,你可以準(zhǔn)備打開流,如列表2-7所示。
列表2-7 打開非阻塞讀取流
<pre><code>
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();
}
</pre></code>
現(xiàn)在,等待你的回調(diào)函數(shù)執(zhí)行。在你的回調(diào)函數(shù)中,檢查事件代碼并采取適當(dāng)?shù)男袆印R娏斜?-8.
列表2-8 網(wǎng)絡(luò)事件回調(diào)函數(shù)
<pre><code>
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;
}
}
</pre></code>
當(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)來處理最終的數(shù)據(jù)然后調(diào)用CFReadStreamUnscheduleFromRunLoop 函數(shù)從指定運(yùn)行循環(huán)上移除流。然后運(yùn)行CFReadStreamClose 函數(shù)來關(guān)閉流,CFRelease 來釋放流引用。
輪詢網(wǎng)絡(luò)流
一般來說,輪詢網(wǎng)絡(luò)流是不明智的。然而,在某些罕見的情況下,它非常有用。為了輪詢流,你首先檢查流是否可以讀取或?qū)懭耄缓笤诹魃蠄?zhí)行一個讀取或?qū)懭氲牟僮鳌?/p>
當(dāng)寫入到一個寫入流中,你通過調(diào)用CFWriteStreamCanAcceptBytes可以決定流是否接受數(shù)據(jù)。如果返回TRUE,那么你可以確定隨后調(diào)用的CFWriteStreamWrite 函數(shù)將立即發(fā)送數(shù)據(jù)而沒有阻塞。
同樣,對于讀取流,在調(diào)用CFReadStreamRead之前,調(diào)用CFReadStreamHasBytesAvailable函數(shù)。
列表2-9 是一個輪詢讀取流的例子
列表2-9 輪詢讀取流
<pre><code>
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...
}
}
</pre></code>
列表2-10 輪詢寫入流的例子
列表2-10 輪詢寫入流
<pre><code>
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...
}
}
</pre></code>
導(dǎo)航防火墻
有兩種方式設(shè)置流的防火墻。對于大多數(shù)流,你可以使用SCDynamicStoreCopyProxies 函數(shù)來檢索代理設(shè)置,然后設(shè)置kCFStreamHTTPProxy (或kCFStreamFTPProxy)屬性將結(jié)果應(yīng)用于流。SCDynamicStoreCopyProxies 函數(shù)是系統(tǒng)配置框架的一部分,因此你需要在你的項(xiàng)目中使用該函數(shù)時需導(dǎo)入``。然后當(dāng)你完成后,釋放代理字典引用。整個過程如列表2-11所示。
列表2-11 通過代理服務(wù)器導(dǎo)航一個流
<pre><code>
CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);
</pre></code>
然而,如果你經(jīng)常需要使用代理設(shè)置多個流,這將變得更加復(fù)雜。在這種情況下,檢索用戶機(jī)器防火墻設(shè)置需要五個步驟:
為動態(tài)存儲會話SCDynamicStoreRef,創(chuàng)建一個持久的句柄。
將句柄添加到運(yùn)行循環(huán)中的動態(tài)存儲會話上,這樣可以收到代理更改的通知。
使用SCDynamicStoreCopyProxies 檢索最新的代理設(shè)置。
當(dāng)被告知變更,更新你的代理。
當(dāng)通過后,清理SCDynamicStoreRef 。
為動態(tài)存儲會話創(chuàng)建句柄,使用SCDynamicStoreCreate 函數(shù)并傳遞一個分配器,一個名字來描述你的過程,一個回調(diào)函數(shù)和一個動態(tài)存儲環(huán)境SCDynamicStoreContext。當(dāng)初始化應(yīng)用時運(yùn)行。代碼如列表2-12所示.
列表2-12 為動態(tài)存儲會話創(chuàng)建一個句柄
<pre><code>
SCDynamicStoreContext context = {0, self, NULL, NULL, NULL};
systemDynamicStore = SCDynamicStoreCreate(NULL,
CFSTR("SampleApp"),
proxyHasChanged,
&context);
</pre></code>
創(chuàng)建動態(tài)存儲引用后,你需要將其添加到運(yùn)行循環(huán)上。首先,采用動態(tài)存儲引用對代理的任何更改設(shè)置監(jiān)控。使用SCDynamicStoreKeyCreateProxies 和SCDynamicStoreSetNotificationKeys函數(shù)可完成該功能。然后,你可以調(diào)用SCDynamicStoreCreateRunLoopSource 和CFRunLoopAddSource函數(shù)添加動態(tài)存儲引用到運(yùn)行循環(huán)上。代碼如列表2-13所示。
列表2-13 添加一個動態(tài)存儲引用到運(yùn)行循環(huán)
<pre><code>
// 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);
</pre></code>
一旦動態(tài)存儲引用添加到運(yùn)行循環(huán)上,調(diào)用SCDynamicStoreCopyProxies函數(shù),用它來預(yù)加載代理字典當(dāng)前代理設(shè)置。如列表2-14所示
列表2-14 加載代理字典
<pre><code>
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
</pre></code>
由于動態(tài)存儲引用添加到運(yùn)行循環(huán)上,每次代理改變,你的回調(diào)函數(shù)會運(yùn)行。釋放當(dāng)前代理字典并使用新的代理設(shè)置重新加載?;卣{(diào)函數(shù)示例代碼如列表2-15所示。
列表2-15 代理回調(diào)函數(shù)
<pre><code>
void proxyHasChanged() {
CFRelease(gProxyDict);
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}
</pre></code>
因?yàn)樗写硇畔⑹亲钚碌?。?chuàng)建讀取或?qū)懭肓骱?,通過調(diào)用CFReadStreamSetProperty 和CFWriteStreamSetProperty函數(shù),設(shè)置kCFStreamPropertyHTTPProxy 代理。如果流是叫做的readStream讀取流,函數(shù)調(diào)用如列表2-16所示。
列表2-16 添加代理信息到流
<pre><code>CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);
</pre></code>
當(dāng)代理設(shè)置完成,確保釋放字典和動態(tài)存儲引用并從運(yùn)行循環(huán)上刪除動態(tài)存儲引用。見列表2-17.
列表2-17 清理代理信息
<pre><code>
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);
</pre></code>