CFNetwork(二)

Working with Streams

@官方文档翻译-李冰

@译文

这一章介绍怎样对读/写流进行创建、打开、和错误检查。描述了怎么从读流中读取,怎样用写流输出,当读取和写入一个流的时候怎样防止阻塞,还有怎样去通过代理服务器导流。

Working with Read Streams 用读取流工作

Core Foundation 中的流可以被使用于读取和写入文件或者用于网络套接字。除了创建流的工程以外,它们原理类似。

创建一个读流

首先创建一个读取流。表2-1为一个文件创建一个读取流。

表 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中描述)创建一对基于网络服务的流。

现在已经创建了流,你可以打开流。打开流将使流保留它需要的任何系统资源,比如打开文件所需要的文件描述符。打开读流的示例见表2-2。

表2-2Creating 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表示打开流的过程已经开始来避免阻塞。检查打开状态,可调用CFReadStreamGetStatus和CFWriteStreamGetStatus方法,通过返回kCFStreamStatusOpening表示正在打开的过程状态,通过返回kCFStreamStatusOpen表示打开完成,或者通过kCFStreamStatusErrorOccurred表示打开完成但是失败。大多数情况下,打开是否完成无关紧要,因为CFStream方法读流和写流将阻塞至流打开完成。

Reading from a Read Stream 从读取流中读取

从读流中读取数据,调用CFReadStreamRead方法,这类似于UNIX read()系统调用。两者需要buffer和buffer length参数。两者都返回读取的字节数量,在流或者文件结束时返回0或者发生错误时返回-1.两者都会阻塞直到至少一个字节可以被读取,只要没有阻塞两者都可以持续读取。从读流中读取示例见表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 拆除一个读流

当读取所有数据时,需要调用CFReadStreamClose方法去关闭流,从而释放关联的系统资源。然后参考调用CFRelease方法释放流。你可能还想通过将引用设置为NULL使引用无效。见示例表2-4。

表 2-4Releasing a read stream 释放一个读流

CFReadStreamClose(myReadStream);
CFRelease(myReadStream);
myReadStream = NULL;

Working with Write Streams 操作写流

操作写流类似于操作读流。一个主要的不同点在于CFWriteStreamWrite方法不能保证接受所有传入的字节。反而,CFWriteStreamWrite返回它接受的字节数量。你可以注意到示例2-5如果写入字节数和要写入的字节总数不一样,则通过调整缓存区以去适应。

表 2-5Creating, 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对象读和写的时候阻塞:

  • 使用 run loop — 注册接收流相关的时间并把流添加到run loop 表。当一个流相事件发生,你的回调函数被调用(指定注册的回调) 。
  • Polling(轮询) — 对于读取流,在从流读取前发现可读字节。对于写流,在写入流阻塞前发现流是否可以被写入 。

这些处理在以下章节中描述。

用Run Loop防止阻塞

优先的方式是在run loop中使用流。在你的主线程执行一个run loop。它等待事件发生,然后调用任何一个和发生的事件关联的函数。

在网络传输事件中,当你注册的事件发生时回调函数被run loop执行。
这让你不需要轮询你的套接字流而让线程变慢的。

了解更多关于run loops,阅读 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,然后系统将提供,如果需要 ,info指针指向的内存是什么的基本的描述。

客户端上下文设置完成后,调用CFReadStreamSetClient去注册接收相关的事件。CFReadStreamSetClient要求你指定回调函数和你希望接收的事件。下面的表2-6示例指定希望接收kCFStreamEventHasBytesAvailable、kCFStreamEventErrorOccurred和kCFStreamEventEndEncountered事件的回调函数。然后调用函数CFReadStreamScheduleWithRunLoop把流排进run loop 。查看表2-6怎么完成这些:

表 2-6 把流排进run loop

CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable |
        kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext))
{
    CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(),
                                    kCFRunLoopCommonModes);
}

当把流排进到run loop后,你可以准备打开这个流如表2-7.

表 2-7 打开非阻塞读取流

if (!CFReadStreamOpen(myReadStream)) {
    CFStreamError myErr = CFReadStreamGetError(myReadStream);
    if (myErr.error != 0) {
    // An error has occurred.
        if (myErr.domain == kCFStreamErrorDomainPOSIX) {
        // Interpret myErr.error as a UNIX errno.
            strerror(myErr.error);
        } else if (myErr.domain == kCFStreamErrorDomainMacOSStatus) {
            OSStatus macError = (OSStatus)myErr.error;
            }
        // Check other domains.
    } else
        // start the run loop
        CFRunLoopRun();
}

现在,等待你的互调函数被执行。在你的回调函数,检查事件码并采取适当的操作。见表 2-8.

表 2-8 网络事件回调函数

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函数去移除指定的run loop中的流。然后CFReadStreamClose函数运行关闭流并且CFRelease去释放流引用。

轮询一个网络流

一般,轮询一个网络流是不明智的。然而,在某些罕见的情况下,它也可以非常有用。轮询一个流,首先你得检查这个流已准备读取和写入,然后对流执行读取和写入操作。

当用写入流写入的时候,你可以调用CFWriteStreamCanAcceptBytes判断流是否可以接受数据。如果返回TRUE,随后你可以放心的调用非阻塞函数CFWriteStreamWrite立即发送数据。

同样的,对于读取流,在调用CFReadStreamRead之前,调用函数CFReadStreamHasBytesAvailable。

表2-9 是一个读取流轮询示例。

表 2-9 轮询一个读取流

while (!done) {
    if (CFReadStreamHasBytesAvailable(myReadStream)) {
        UInt8 buf[BUFSIZE];
        CFIndex bytesRead = CFReadStreamRead(myReadStream, buf, BUFSIZE);
        if (bytesRead < 0) {
            CFStreamError error = CFReadStreamGetError(myReadStream);
            reportError(error);
        } else if (bytesRead == 0) {
            if (CFReadStreamGetStatus(myReadStream) == kCFStreamStatusAtEnd) {
                done = TRUE;
            }
        } else {
            handleBytes(buf, bytesRead);
        }
    } else {
        // ...do something else while you wait...
    }
}

表 2-10 写入流轮询示例

表 2-10 轮询一个写入流

UInt8 buf[] = “Hello, world”;
UInt32 bufLen = strlen(buf);
 
while (!done) {
    if (CFWriteStreamCanAcceptBytes(myWriteStream)) {
        int bytesWritten = CFWriteStreamWrite(myWriteStream, buf, strlen(buf));
        if (bytesWritten < 0) {
            CFStreamError error = CFWriteStreamGetError(myWriteStream);
            reportError(error);
        } else if (bytesWritten == 0) {
            if (CFWriteStreamGetStatus(myWriteStream) == kCFStreamStatusAtEnd)
            {
                done = TRUE;
            }
        } else if (bytesWritten != strlen(buf)) {
            // Determine how much has been written and adjust the buffer
            bufLen = bufLen - bytesWritten;
            memmove(buf, buf + bytesWritten, bufLen);
 
            // Figure out what went wrong with the write stream
            CFStreamError error = CFWriteStreamGetError(myWriteStream);
            reportError(error);
        }
    } else {
        // ...do something else while you wait...
    }
}

操作防火墙

这里又两个方式去申请为流做防火墙设置。对于大多数流而言,你可以用SCDynamicStoreCopyProxies函数检索代理设置和设置kCFStreamHTTPProxy (or kCFStreamFTPProxy) 属性后将结果应用于流中。SCDynamicStoreCopyProxies函数是系统配置框架的一部分,所以在你的工程中你需要包含来使用这个方法。然后当你使用完成后只需要释放代理字典引用。这个过程看起来就像表2-11。

表 2-11 通过代理服务操作流

CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, proxyDict);

不管怎样,如果你需要经常使用代理设置多个流,将会变得有点更复杂。在这种情况下,检索用户机器的防火墙设置需要舞五步:

1.为动态存储会话创建一个持久句柄,SCDynamicStoreRef。
2.将动态存储会话句柄添加到run loop去通知代理变更。
3.用SCDynamicStoreCopyProxies 去检索最后一次代理设置。
4.更新你的代理副本时通知变更。
5.当你通过SCDynamicStoreRef时,清理它。Clean up the SCDynamicStoreRef when you are through with it.

创建动态存储会话句柄,使用SCDynamicStoreCreate函数并传入分配器,一个描述过程的名字,一个回调函数和一个动态存储上下文,SCDynamicStoreContext。当你初始化应用程序时运行。
示例代码类似于表2-12。

表 2-12 创建一个动态存储会话句柄

SCDynamicStoreContext context = {0, self, NULL, NULL, NULL};
systemDynamicStore = SCDynamicStoreCreate(NULL,
                                          CFSTR("SampleApp"),
                                          proxyHasChanged,
                                          &context);

创建动态存储引用过后,你需要添加到run loop。首先,采取动态存储引用并将其设置为监视代理的任何更改。这是通过SCDynamicStoreKeyCreateProxies和 SCDynamicStoreSetNotificationKeys 函数完成的。然后,你可以使用SCDynamicStoreCreateRunLoopSource和CFRunLoopAddSource函数添加动态存储引用到run loop。代码如表2-13。

表 2-13 添加一个动态存储引用到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);

一旦动态存储引用被加入到run loop中,调用SCDynamicStoreCopyProxies当前的代理设置来预加载代理字典。 表2-14展示了如何做到这些。

表 2-14 加载代理字典

gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);

由于将动态存储引用添加到run loop中,每一次代理变更将运行你的回调函数。释放当前的代理字典并加载到新的代理设置。
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. 示例回调函数将与列表2-15中的回调函数类似。

表 2-15 代理回调函数

void proxyHasChanged() {
    CFRelease(gProxyDict);
    gProxyDict = SCDynamicStoreCopyProxies(systemDynamicStore);
}

由于所有的代理信息是最新的,应用代理。当创建你的读取或写入流之后,通过调用CFReadStreamSetProperty或CFWriteStreamSetProperty函数设置kCFStreamPropertyHTTPProxy代理。如果你的流是读取流调用readStream,函数如表2-16所示。

表 2-16 添加代理信息到流中

CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPProxy, gProxyDict);

当你完成所有的代理设置,要确保字典和动态存储引用释放,并且将动态存储引用从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 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);

你可能感兴趣的:(CFNetwork(二))