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:
Create a single, persistent handle to a dynamic store session,
SCDynamicStoreRef
.Put the handle to the dynamic store session into the run loop to be notified of proxy changes.
Use
SCDynamicStoreCopyProxies
to retrieve the latest proxy settings.Update your copy of the proxies when told of the changes.
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); |
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-06-11