版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2018.06.09 |
前言
CFNetwork框架访问网络服务并处理网络配置的变化。 建立在网络协议抽象的基础上,可以简化诸如使用BSD套接字,管理HTTP和FTP服务器以及管理Bonjour服务等任务。接下来几篇我们就一起看一下这个框架。感兴趣的可以看上面几篇文章。
1. CFNetwork框架详细解析(一) —— 基本概览
2. CFNetwork框架详细解析(二) —— CFNetwork编程指导之简介(一)
3. CFNetwork框架详细解析(三) —— CFNetwork编程指导之CFNetwork概念(二)
Working with Streams - 处理流
本章讨论如何创建,打开并检查读取和写入流上的错误。 它还介绍了如何从读取流中读取,如何写入写入流,如何防止在读取流或写入流时发生阻塞,以及如何通过代理服务器导航流。
Working with Read Streams - 处理读入流
Core Foundation流可用于读取或写入文件或使用网络套接字。 除了创建这些流的过程之外,它们的行为相似。
1. Creating a Read Stream - 创建读入流
首先创建一个读取流。 Listing 2-1为一个文件创建一个读取流。
Listing 2-1 Creating a read stream from a file
CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL);
在此列表中,kCFAllocatorDefault参数指定使用当前默认系统分配器为流分配内存,fileURL参数指定要为其创建此读取流的文件的名称,例如file:///Users/joeuser/Downloads/MyApp.sit。
同样,您可以通过调用CFStreamCreatePairWithSocketToCFHost(在Using a Run Loop to Prevent Blocking中介绍)或CFStreamCreatePairWithSocketToNetService(在NSNetServices and CFNetServices Programming Guide中介绍)来创建基于网络服务的一对流。
现在你已经创建了流,你可以打开它。 打开流会导致流保留所需的任何系统资源,例如打开文件所需的文件描述符。 Listing 2-2是打开读取流的示例。
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.
}
}
CFReadStreamOpen函数返回TRUE表示成功,如果打开由于某种原因失败,则返回FALSE。如果CFReadStreamOpen返回FALSE,则该示例调用CFReadStreamGetError函数,该函数返回由两个值组成的CFStreamError类型的结构:域代码和错误代码。域代码指示应如何解释错误代码。例如,如果域代码是kCFStreamErrorDomainPOSIX,则错误代码是一个UNIX errno值。其他错误域是kCFStreamErrorDomainMacOSStatus,它指示错误代码是MacErrors.h中定义的OSStatus值,kCFStreamErrorDomainHTTP指示错误代码是由CFStreamErrorHTTP枚举定义的值之一。
打开一个流可能是一个漫长的过程,所以CFReadStreamOpen和CFWriteStreamOpen函数通过返回TRUE来避免阻塞,以指示打开流的过程已经开始。要检查open的状态,调用函数CFReadStreamGetStatus和CFWriteStreamGetStatus,如果打开仍在进行中,则返回kCFStreamStatusOpening;如果打开已完成,则返回kCFStreamStatusOpen;如果打开已完成但失败,则返回kCFStreamStatusErrorOccurred。在大多数情况下,打开是否完成并不重要,因为读取和写入的CFStream函数将阻塞,直到流打开。
2. Reading from a Read Stream - 从读取流中读取
要从读取流中读取数据,请调用函数CFReadStreamRead,该函数与UNIX read()系统调用类似。 都采用缓冲区和缓冲区长度参数。 两者都返回读取的字节数,如果在流或文件结束时返回0,如果发生错误则返回-1。 两者都会阻塞,直到至少有一个字节可以被读取,并且只要不阻塞就可以继续读取。 Listing 2-3是读取流的示例。
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);
3. Tearing Down a Read Stream - 关闭读取流
当所有数据都被读取后,您应该调用CFReadStreamClose函数来关闭流,从而释放与之关联的系统资源。 然后通过调用函数CFRelease来释放流引用。 您可能还想通过将引用设置为NULL来使引用无效。 有关示例,请参阅Listing 2-4。
Listing 2-4 Releasing a read stream
CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;
Working with Write Streams - 处理写入流
使用写入流类似于使用读取流。 一个主要区别是函数CFWriteStreamWrite不保证接受所有传递它的字节。 相反,CFWriteStreamWrite返回它接受的字节数。 在代码Listing 2-5所示的示例代码中,您会注意到,如果写入的字节数与要写入的总字节数不相同,则会调整缓冲区以适应此情况。
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 - 使用流时防止阻塞
在使用流进行通信时,数据传输可能需要很长时间才会出现,尤其是在基于套接字的流中。 如果你正在同步实现你的流,你的整个应用程序将被迫等待数据传输。 因此,强烈建议您的代码使用备用方法来防止阻塞。
在读取或写入CFStream对象时,有两种方法可以防止阻塞:
- 使用运行循环 - 注册以接收与流相关的事件并在运行循环中调度流。 当发生与流相关的事件时,会调用您的回调函数(由注册调用指定)。
- 轮询 - 对于读取流,在从流中读取数据之前查明是否有要读取的字节。 对于写入流,请在写入流之前确定是否可以在不阻塞的情况下写入流。
以下各节将介绍每种方法。
1. Using a Run Loop to Prevent Blocking - 使用运行循环来防止阻塞
使用流的首选方式是使用运行循环。 运行循环在主程序线程上执行。 它等待事件发生,然后调用与给定事件相关的任何函数。
在网络传输的情况下,当您注册的事件发生时,您的回调函数由运行循环执行。 这允许你不必轮询你的套接字流,轮询会减慢线程。
要了解有关运行循环的更多信息,请阅读Threading Programming Guide。
此示例从创建套接字读取流开始:
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是指向释放info参数的函数的指针。当流与上下文分离时,CFStream会调用myRelease(myPtr)。最后,copyDescription是一个函数的参数,用于提供流的描述。例如,如果您要使用上面显示的流客户端上下文来调用CFCopyDesc(myReadStream),则CFStream会调用myCopyDesc(myPtr)。
客户端上下文还允许您选择将retain,release和copyDescription参数设置为NULL。如果将retain和release参数设置为NULL,那么系统会希望您保持info指针指向的内存处于活动状态,直到流本身被销毁。如果您将copyDescription参数设置为NULL,则系统将根据请求提供信息指针指向的内存内容的基本描述。
设置客户端上下文后,调用函数CFReadStreamSetClient注册以接收与流相关的事件。 CFReadStreamSetClient要求您指定回调函数和您想要接收的事件。Listing 2-6中的以下示例指定回调函数想要接收kCFStreamEventHasBytesAvailable,kCFStreamEventErrorOccurred和kCFStreamEventEndEncountered事件。然后使用CFReadStreamScheduleWithRunLoop函数将流安排在运行循环中。有关如何执行此操作的示例,请参见Listing 2-6。
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);
}
通过运行循环中的流调度,您可以打开流,如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();
}
现在,等待你的回调函数被执行。 在您的回调函数中,检查事件代码并采取适当的措施。 参见代码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;
}
}
当回调函数收到kCFStreamEventHasBytesAvailable事件代码时,它会调用CFReadStreamRead读取数据。
当回调函数收到kCFStreamEventErrorOccurred事件代码时,它会调用CFReadStreamGetError来获取错误和它自己的错误函数(reportError)来处理错误。
当回调函数接收到kCFStreamEventEndEncountered事件代码时,它会调用自己的函数(reportCompletion)来处理数据结束,然后调用CFReadStreamUnscheduleFromRunLoop函数从指定的运行循环中删除流。 然后运行CFReadStreamClose函数关闭流和CFRelease以释放流引用。
2. Polling a Network Stream - 轮询网络流
一般来说,轮询网络流是不可取的。 但是,在某些罕见情况下,这样做可能很有用。 要轮询流,首先检查流是否准备好读取或写入,然后对流执行读取或写入操作。
写入写入流时,可以通过调用CFWriteStreamCanAcceptBytes来确定流是否准备好接受数据。 如果它返回TRUE,那么你可以确定随后调用CFWriteStreamWrite函数将立即发送数据而不会阻塞。
同样,对于读取流,在调用CFReadStreamRead之前,调用函数CFReadStreamHasBytesAvailable。
Listing 2-9是读取流的轮询示例。
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是写入流的轮询示例。
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 - 浏览防火墙
将防火墙设置应用于流有两种方法。 对于大多数流,您可以使用SCDynamicStoreCopyProxies函数检索代理设置,然后通过设置kCFStreamHTTPProxy(或kCFStreamFTPProxy)属性将结果应用于流。 SCDynamicStoreCopyProxies函数是System Configuration框架的一部分,因此您需要在项目中包含<SystemConfiguration / SystemConfiguration.h>以使用该函数。 然后,只需在完成后发布代理字典参考。 该过程将如Listing 2-11所示
Listing 2-11 Navigating a stream through a proxy server
CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);
但是,如果您需要将代理设置经常用于多个流,则会变得更加复杂。 在这种情况下,检索用户机器的防火墙设置需要五个步骤:
- 为动态存储会话创建一个持久句柄
SCDynamicStoreRef。 - 将句柄放入动态存储会话中,以便通知代理更改。
- 使用
SCDynamicStoreCopyProxies检索最新的代理设置。 - 当被告知更改时更新您的代理副本。
- 清理
SCDynamicStoreRef时,请仔细阅读。
要创建动态存储会话的句柄,请使用函数SCDynamicStoreCreate并传递一个分配器,一个名称来描述您的进程,一个回调函数和一个动态存储上下文SCDynamicStoreContext。 这在初始化应用程序时运行。 代码与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);
创建对动态存储的引用后,需要将其添加到运行循环中。 首先,获取动态存储引用并设置它以监视代理的任何更改。 这是通过函数SCDynamicStoreKeyCreateProxies和SCDynamicStoreSetNotificationKeys完成的。 然后,您可以使用函数SCDynamicStoreCreateRunLoopSource和CFRunLoopAddSource将动态存储引用添加到运行循环中。 你的代码应该如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);
一旦将动态存储引用添加到运行循环中,可以使用它通过调用SCDynamicStoreCopyProxies将代理字典预加载当前代理设置。 有关如何执行此操作,请参见Listing 2-14。
Listing 2-14 Loading the proxy dictionary
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
由于将动态存储引用添加到运行循环中,每次更改代理时,都会运行回调函数。 释放当前的代理字典并使用新的代理设置重新加载它。 示例回调函数看起来像Listing 2-15中的那个。
Listing 2-15 Proxy callback function
void proxyHasChanged() {
CFRelease(gProxyDict);
gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}
由于所有代理信息都是最新的,因此应用代理。 创建读取或写入流后,通过调用函数CFReadStreamSetProperty或CFWriteStreamSetProperty来设置kCFStreamPropertyHTTPProxy代理。 如果您的流是一个名为readStream的读取流,那么您的函数调用将如Listing 2-16所示
Listing 2-16 Adding proxy information to a stream
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);
当您完成使用代理设置时,请确保释放字典和动态存储引用,并从运行循环中删除动态存储引用。 参见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);
后记
本篇主要介绍了处理流的相关逻辑,感兴趣的给个赞或者关注~~~~














网友评论