Working with Streams
@官方文檔翻譯-李冰
@譯文
這一章介紹怎樣對(duì)讀/寫(xiě)流進(jìn)行創(chuàng)建迁酸、打開(kāi)夫啊、和錯(cuò)誤檢查遮晚。描述了怎么從讀流中讀取,怎樣用寫(xiě)流輸出渣蜗,當(dāng)讀取和寫(xiě)入一個(gè)流的時(shí)候怎樣防止阻塞屠尊,還有怎樣去通過(guò)代理服務(wù)器導(dǎo)流。
Working with Read Streams 用讀取流工作
Core Foundation 中的流可以被使用于讀取和寫(xiě)入文件或者用于網(wǎng)絡(luò)套接字耕拷。除了創(chuàng)建流的工程以外讼昆,它們?cè)眍?lèi)似。
創(chuàng)建一個(gè)讀流
首先創(chuàng)建一個(gè)讀取流骚烧。表2-1為一個(gè)文件創(chuàng)建一個(gè)讀取流浸赫。
表 2-1 Creating a read stream from a file 從一個(gè)文件創(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)建一對(duì)基于網(wǎng)絡(luò)服務(wù)的流涧狮。
現(xiàn)在已經(jīng)創(chuàng)建了流炕矮,你可以打開(kāi)流。打開(kāi)流將使流保留它需要的任何系統(tǒng)資源者冤,比如打開(kāi)文件所需要的文件描述符肤视。打開(kāi)讀流的示例見(jiàn)表2-2。
表2-2Creating a Read Stream 創(chuàng)建一個(gè)讀取流
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表示任何打開(kāi)錯(cuò)誤的原因涉枫。如果CFReadStreamOpen返回FALSE邢滑,示例程序調(diào)用CFReadStreamGetError方法,并返回CFStreamError結(jié)構(gòu)體類(lèi)型包含兩個(gè)值:域碼和錯(cuò)誤碼愿汰。域碼表示對(duì)錯(cuò)誤碼的解釋困后。比如,如果域碼是kCFStreamErrorDomainPOSIX衬廷,錯(cuò)誤碼是UNIX errno值摇予。其他的錯(cuò)誤域是kCFStreamErrorDomainMacOSStatus,表示錯(cuò)誤碼是一個(gè)在MacErrors.h聲明的OSStatus值吗跋,以及kCFStreamErrorDomainHTTP表示的聲明的CFStreamErrorHTTP枚舉錯(cuò)誤碼侧戴。
打開(kāi)流可能是一個(gè)很長(zhǎng)的過(guò)程,所以CFReadStreamOpen和CFWriteStreamOpen通過(guò)返回值TRUE表示打開(kāi)流的過(guò)程已經(jīng)開(kāi)始來(lái)避免阻塞跌宛。檢查打開(kāi)狀態(tài)酗宋,可調(diào)用CFReadStreamGetStatus和CFWriteStreamGetStatus方法,通過(guò)返回kCFStreamStatusOpening表示正在打開(kāi)的過(guò)程狀態(tài)疆拘,通過(guò)返回kCFStreamStatusOpen表示打開(kāi)完成蜕猫,或者通過(guò)kCFStreamStatusErrorOccurred表示打開(kāi)完成但是失敗。大多數(shù)情況下哎迄,打開(kāi)是否完成無(wú)關(guān)緊要丹锹,因?yàn)镃FStream方法讀流和寫(xiě)流將阻塞至流打開(kāi)完成。
Reading from a Read Stream 從讀取流中讀取
從讀流中讀取數(shù)據(jù)芬失,調(diào)用CFReadStreamRead方法楣黍,這類(lèi)似于UNIX read()系統(tǒng)調(diào)用。兩者需要buffer和buffer length參數(shù)棱烂。兩者都返回讀取的字節(jié)數(shù)量租漂,在流或者文件結(jié)束時(shí)返回0或者發(fā)生錯(cuò)誤時(shí)返回-1.兩者都會(huì)阻塞直到至少一個(gè)字節(jié)可以被讀取,只要沒(méi)有阻塞兩者都可以持續(xù)讀取。從讀流中讀取示例見(jiàn)表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 拆除一個(gè)讀流
當(dāng)讀取所有數(shù)據(jù)時(shí),需要調(diào)用CFReadStreamClose方法去關(guān)閉流业筏,從而釋放關(guān)聯(lián)的系統(tǒng)資源憔杨。然后參考調(diào)用CFRelease方法釋放流。你可能還想通過(guò)將引用設(shè)置為NULL使引用無(wú)效蒜胖。見(jiàn)示例表2-4消别。
表 2-4Releasing a read stream 釋放一個(gè)讀流
CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;
Working with Write Streams 操作寫(xiě)流
操作寫(xiě)流類(lèi)似于操作讀流。一個(gè)主要的不同點(diǎn)在于CFWriteStreamWrite方法不能保證接受所有傳入的字節(jié)台谢。反而寻狂,CFWriteStreamWrite返回它接受的字節(jié)數(shù)量。你可以注意到示例2-5如果寫(xiě)入字節(jié)數(shù)和要寫(xiě)入的字節(jié)總數(shù)不一樣朋沮,則通過(guò)調(diào)整緩存區(qū)以去適應(yīng)蛇券。
表 2-5Creating, opening, writing to, and releasing a write stream 創(chuàng)建,打開(kāi)樊拓,寫(xiě)入和釋放寫(xiě)流
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 操作流時(shí)防止阻塞
當(dāng)使用流建立連接時(shí)纠亚,總是會(huì)發(fā)生,尤其是基于套接字的流在數(shù)據(jù)傳輸?shù)臅r(shí)候可能會(huì)花費(fèi)很長(zhǎng)的時(shí)間筋夏。如果你用同步的方式實(shí)現(xiàn)流菜枷,整個(gè)程序都將被迫等待數(shù)據(jù)傳輸。因此叁丧,強(qiáng)烈推薦你替換方法來(lái)防止阻塞。
有兩種方式來(lái)防止當(dāng)CFStream對(duì)象讀和寫(xiě)的時(shí)候阻塞:
- 使用 run loop — 注冊(cè)接收流相關(guān)的時(shí)間并把流添加到run loop 表岳瞭。當(dāng)一個(gè)流相事件發(fā)生拥娄,你的回調(diào)函數(shù)被調(diào)用(指定注冊(cè)的回調(diào)) 。
- Polling(輪詢(xún)) — 對(duì)于讀取流瞳筏,在從流讀取前發(fā)現(xiàn)可讀字節(jié)稚瘾。對(duì)于寫(xiě)流,在寫(xiě)入流阻塞前發(fā)現(xiàn)流是否可以被寫(xiě)入 姚炕。
這些處理在以下章節(jié)中描述摊欠。
用Run Loop防止阻塞
優(yōu)先的方式是在run loop中使用流。在你的主線(xiàn)程執(zhí)行一個(gè)run loop柱宦。它等待事件發(fā)生些椒,然后調(diào)用任何一個(gè)和發(fā)生的事件關(guān)聯(lián)的函數(shù)。
在網(wǎng)絡(luò)傳輸事件中掸刊,當(dāng)你注冊(cè)的事件發(fā)生時(shí)回調(diào)函數(shù)被run loop執(zhí)行免糕。
這讓你不需要輪詢(xún)你的套接字流而讓線(xiàn)程變慢的。
了解更多關(guān)于run loops,閱讀 Threading Programming Guide.
這個(gè)例子首先創(chuàng)建套接字讀取流:
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port,
&myReadStream, NULL);
CFHost對(duì)象引用石窑,host牌芋,指定讀取流建立連接的遠(yuǎn)程主機(jī)和port參數(shù)指定主機(jī)使用的端口號(hào)。CFStreamCreatePairWithSocketToCFHost函數(shù)返回新的myReadStream讀取流引用松逊。最后一個(gè)參數(shù)躺屁,NULL,表示調(diào)用者不需要?jiǎng)?chuàng)建寫(xiě)入流经宏。如果你想創(chuàng)建寫(xiě)入流犀暑,最后一個(gè)參數(shù)需要傳入,比如&myWriteStream烛恤。
在打開(kāi)套接字讀取流之前母怜,創(chuàng)建一個(gè)上下文將用于你注冊(cè)接收流相關(guān)事件:
CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc};
第一個(gè)參數(shù)是0指定版本號(hào)。info參數(shù)缚柏,myPtr苹熏,是一個(gè)指向你想傳入到回調(diào)函數(shù)的指針。通常币喧,myPtr是一個(gè)指向你已定義包含流相關(guān)信息的結(jié)構(gòu)體的指針轨域。retain參數(shù)是一個(gè)指向持有info參數(shù)的函數(shù)的指針。所以如果你設(shè)置成你的函數(shù)myRetain杀餐,像上面的代碼中一樣干发,CFStream將調(diào)用myRetain(myPtr)去持有info指針。同樣的史翘,release參數(shù)枉长,myRelease,是一個(gè)指向釋放info參數(shù)的指針琼讽。當(dāng)流從上下文分離必峰,CFStream將調(diào)用myRelease(myPtr)。最后钻蹬,copyDescription是一個(gè)提供流的描述的函數(shù)吼蚁。例如,如果你 如上所示對(duì)流端上下文調(diào)用 CFCopyDesc(myReadStream)问欠,CFStream將調(diào)用myCopyDesc(myPtr)肝匆。
客戶(hù)端上下文還允許你設(shè)置選項(xiàng)的retain,release和copyDescription參數(shù)為NULL顺献。如果你設(shè)置retain和release參數(shù)為NULL旗国, 然后系統(tǒng)會(huì)期望你去保存可用的info指針指向的內(nèi)存知道流本身被銷(xiāo)毀。如果你將copyDescription參數(shù)為NULL注整,然后系統(tǒng)將提供粗仓,如果需要 嫁怀,info指針指向的內(nèi)存是什么的基本的描述。
客戶(hù)端上下文設(shè)置完成后借浊,調(diào)用CFReadStreamSetClient去注冊(cè)接收相關(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)備打開(kāi)這個(gè)流如表2-7.
表 2-7 打開(kāi)非阻塞讀取流
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?jiàn)表 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獲得錯(cuò)誤和自己的錯(cuò)誤函數(shù)(reportError)去處理錯(cuò)誤。
當(dāng)回調(diào)函數(shù)接收到kCFStreamEventEndEncountered事件碼审孽,它調(diào)用自己的函數(shù)reportCompletion去處理結(jié)束數(shù)據(jù)县袱,然后調(diào)用CFReadStreamUnscheduleFromRunLoop函數(shù)去移除指定的run loop中的流。然后CFReadStreamClose函數(shù)運(yùn)行關(guān)閉流并且CFRelease去釋放流引用佑力。
輪詢(xún)一個(gè)網(wǎng)絡(luò)流
一般式散,輪詢(xún)一個(gè)網(wǎng)絡(luò)流是不明智的。然而打颤,在某些罕見(jiàn)的情況下暴拄,它也可以非常有用。輪詢(xún)一個(gè)流编饺,首先你得檢查這個(gè)流已準(zhǔn)備讀取和寫(xiě)入乖篷,然后對(duì)流執(zhí)行讀取和寫(xiě)入操作。
當(dāng)用寫(xiě)入流寫(xiě)入的時(shí)候反肋,你可以調(diào)用CFWriteStreamCanAcceptBytes判斷流是否可以接受數(shù)據(jù)。如果返回TRUE踏施,隨后你可以放心的調(diào)用非阻塞函數(shù)CFWriteStreamWrite立即發(fā)送數(shù)據(jù)石蔗。
同樣的,對(duì)于讀取流畅形,在調(diào)用CFReadStreamRead之前养距,調(diào)用函數(shù)CFReadStreamHasBytesAvailable。
表2-9 是一個(gè)讀取流輪詢(xún)示例日熬。
表 2-9 輪詢(xún)一個(gè)讀取流
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 寫(xiě)入流輪詢(xún)示例
表 2-10
輪詢(xún)一個(gè)寫(xiě)入流
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...
}
}
操作防火墻
這里又兩個(gè)方式去申請(qǐng)為流做防火墻設(shè)置棍厌。對(duì)于大多數(shù)流而言,你可以用SCDynamicStoreCopyProxies函數(shù)檢索代理設(shè)置和設(shè)置kCFStreamHTTPProxy (or kCFStreamFTPProxy) 屬性后將結(jié)果應(yīng)用于流中。SCDynamicStoreCopyProxies函數(shù)是系統(tǒng)配置框架的一部分耘纱,所以在你的工程中你需要包含<SystemConfiguration/SystemConfiguration.h>來(lái)使用這個(gè)方法敬肚。然后當(dāng)你使用完成后只需要釋放代理字典引用。這個(gè)過(guò)程看起來(lái)就像表2-11束析。
表 2-11 通過(guò)代理服務(wù)操作流
CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);
不管怎樣艳馒,如果你需要經(jīng)常使用代理設(shè)置多個(gè)流,將會(huì)變得有點(diǎn)更復(fù)雜员寇。在這種情況下弄慰,檢索用戶(hù)機(jī)器的防火墻設(shè)置需要舞五步:
1.為動(dòng)態(tài)存儲(chǔ)會(huì)話(huà)創(chuàng)建一個(gè)持久句柄,SCDynamicStoreRef蝶锋。
2.將動(dòng)態(tài)存儲(chǔ)會(huì)話(huà)句柄添加到run loop去通知代理變更陆爽。
3.用SCDynamicStoreCopyProxies 去檢索最后一次代理設(shè)置。
4.更新你的代理副本時(shí)通知變更扳缕。
5.當(dāng)你通過(guò)SCDynamicStoreRef時(shí)慌闭,清理它。Clean up the SCDynamicStoreRef when you are through with it.
創(chuàng)建動(dòng)態(tài)存儲(chǔ)會(huì)話(huà)句柄第献,使用SCDynamicStoreCreate函數(shù)并傳入分配器贡必,一個(gè)描述過(guò)程的名字,一個(gè)回調(diào)函數(shù)和一個(gè)動(dòng)態(tài)存儲(chǔ)上下文庸毫,SCDynamicStoreContext仔拟。當(dāng)你初始化應(yīng)用程序時(shí)運(yùn)行。
示例代碼類(lèi)似于表2-12飒赃。
表 2-12 創(chuàng)建一個(gè)動(dòng)態(tài)存儲(chǔ)會(huì)話(huà)句柄
SCDynamicStoreContext context = {0, self, NULL, NULL, NULL};
systemDynamicStore = SCDynamicStoreCreate(NULL,
CFSTR("SampleApp"),
proxyHasChanged,
&context);
創(chuàng)建動(dòng)態(tài)存儲(chǔ)引用過(guò)后利花,你需要添加到run loop。首先载佳,采取動(dòng)態(tài)存儲(chǔ)引用并將其設(shè)置為監(jiān)視代理的任何更改炒事。這是通過(guò)SCDynamicStoreKeyCreateProxies和 SCDynamicStoreSetNotificationKeys 函數(shù)完成的。然后蔫慧,你可以使用SCDynamicStoreCreateRunLoopSource和CFRunLoopAddSource函數(shù)添加動(dòng)態(tài)存儲(chǔ)引用到run loop挠乳。代碼如表2-13。
表 2-13 添加一個(gè)動(dòng)態(tài)存儲(chǔ)引用到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);
一旦動(dòng)態(tài)存儲(chǔ)引用被加入到run loop中姑躲,調(diào)用SCDynamicStoreCopyProxies當(dāng)前的代理設(shè)置來(lái)預(yù)加載代理字典睡扬。 表2-14展示了如何做到這些。
表 2-14 加載代理字典
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
由于將動(dòng)態(tài)存儲(chǔ)引用添加到run loop中黍析,每一次代理變更將運(yùn)行你的回調(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ù)類(lèi)似阐枣。
表 2-15 代理回調(diào)函數(shù)
void proxyHasChanged() {
CFRelease(gProxyDict);
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}
由于所有的代理信息是最新的马靠,應(yīng)用代理奄抽。當(dāng)創(chuàng)建你的讀取或?qū)懭肓髦螅ㄟ^(guò)調(diào)用CFReadStreamSetProperty或CFWriteStreamSetProperty函數(shù)設(shè)置kCFStreamPropertyHTTPProxy代理甩鳄。如果你的流是讀取流調(diào)用readStream逞度,函數(shù)如表2-16所示。
表 2-16 添加代理信息到流中
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);
當(dāng)你完成所有的代理設(shè)置娩贷,要確保字典和動(dòng)態(tài)存儲(chǔ)引用釋放第晰,并且將動(dòng)態(tài)存儲(chǔ)引用從run loop移除。見(jiàn)表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);