CFNetwork也是基于core foundation的两个API,CFSocket和CFStream
CFSocket是基于BSD socket的抽象,使用CFSocketCreate、CFSocketCreateWithNative
CFSocketCreateRunLoopSource、CFRunLoopAddSource方法创建CFSocket
CFStream提供两种抽象的Stream类型,一种是CFReadStream,一种是CFWriteStream.
使用CFStream读写流数据跟读取UNIX文件类似,首先指定stream的类型(内存、文件、socket等)
然后打开流,进行读写操作。避免主线程阻塞,使用异步方法来读取流。
CFStream支持SSL加密,通过设置kCFStreamPropertySSLSettings属性来设置。
CFNetwork有几种API组成,每种指定着一种网络协议。每种协议的API定义都是类似的
CFFTP API:
1:从ftp服务器中下载文件
2:上传文件到ftp服务器
3:在ftp服务器上创建文件
4:Download a directory listing from an FTP server
CFHTTP API:
CFHTTPAuthentication API支持四种认证方案:
Basic
Digest
NT LAN Manager (NTLM)
Simple and Protected GSS-API Negotiation Mechanism (SPNEGO)
CFHost API处理步骤:
1:创建一个CFHost对象
2:解析CFHost对象
3:获取地址、host名称等信息
4:销毁CFHost对象
CFNetServices API(使用Bonjour服务需要CFNetServices API)
CFNetDiagnostics API(当网络有问题的时候,可以通过这个API来快速定位问题)
//流的读操作
//创建一个读的流: CFReadStreamRef myReadStream = CFReadStreamCreateWithFile(kCFAllocatorDefault, fileURL); //打开一个读的流 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. } } //从流中读取数据 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 ); //关闭流 CFReadStreamClose(myReadStream); CFRelease(myReadStream); myReadStream = NULL;
//流的写操作
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;
在数据转化的时候,如果是同步的,那么就有可能会阻塞主线程,有两种方式可以避免:
1:使用run loop(查看Threading Programming Guide):注册run loop的通知方法,然后在对应的事件上发起通知。
2:使用Polling(轮训)
//创建一个socket,读流 CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, port, &myReadStream, NULL); //创建一个容器,主要为了回调方法用 CFStreamClientContext myContext = {0, myPtr, myRetain, myRelease, myCopyDesc}; //创建run loop CFOptionFlags registeredEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered; if (CFReadStreamSetClient(myReadStream, registeredEvents, myCallBack, &myContext)) { CFReadStreamScheduleWithRunLoop(myReadStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); } //创建一个非阻塞的读流 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(); } //网络请求回调方法 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; } }
//一般情况下,不建议使用轮训来处理读写流的操作
//轮训一个读流 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... } } //轮训一个写的流 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... } }
http请求
//创建一个http request请求 CFStringRef bodyString = CFSTR(""); // Usually used for POST data CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, bodyString, kCFStringEncodingUTF8, 0); CFStringRef headerFieldName = CFSTR("X-My-Favorite-Field"); CFStringRef headerFieldValue = CFSTR("Dreams"); CFStringRef url = CFSTR("http://www.apple.com"); CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); CFStringRef requestMethod = CFSTR("GET"); CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL,CFDataRef bodyDataExt = CFStringCreateExternalRepresentation(kCFAllocatorDefault, bodyData, kCFStringEncodingUTF8, 0); CFHTTPMessageSetBody(myRequest, bodyDataExt); CFHTTPMessageSetHeaderFieldValue(myRequest, headerFieldName, headerFieldValue); CFDataRef mySerializedRequest = CFHTTPMessageCopySerializedMessage(myRequest); //release请求 CFRelease(myRequest); CFRelease(myURL); CFRelease(url); CFRelease(mySerializedRequest); myRequest = NULL; mySerializedRequest = NULL; Communicating with HTTP Servers Creating a CFHTTP Request 2012-
http相应
//反序列化信息 CFHTTPMessageRef myMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE); if (!CFHTTPMessageAppendBytes(myMessage, &data, numBytes)) { //Handle parsing error } //反序列化成功 if (CFHTTPMessageIsHeaderComplete(myMessage)) { // Perform processing. } //反序列化之后可以调用CFHTTPMessageCopyBody等方法获取对应的数据
//通过读的流,序列化一个http请求 CFStringRef url = CFSTR("http://www.apple.com"); CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); CFStringRef requestMethod = CFSTR("GET"); CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myUrl, kCFHTTPVersion1_1); CFHTTPMessageSetBody(myRequest, bodyData); CFHTTPMessageSetHeaderFieldValue(myRequest, headerField, value); CFReadStreamRef myReadStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest); CFReadStreamOpen(myReadStream); //通过CFReadStreamCopyProperty,监听响应 CFHTTPMessageRef myResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(myReadStream, kCFStreamPropertyHTTPResponseHeader); //返回响应code UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse); //重定向流 CFReadStreamClose(myReadStream); CFReadStreamRef myReadStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest); if (CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) { // something went wrong, exit } CFReadStreamOpen(myReadStream); //关闭流就相当于关闭了这个请求
Authenticating HTTP Servers
如果一个http服务器返回401或者407,那么表示服务端需要认证,请求中需要带上证书,在API中,证书放在CFHTTPAuthentication对象中。
//创建一个authentication对象 if (!authentication) { CFHTTPMessageRef responseHeader = (CFHTTPMessageRef) CFReadStreamCopyProperty( readStream, kCFStreamPropertyHTTPResponseHeader ); // Get the authentication information from the response. authentication = CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader); CFRelease(responseHeader); } //判断这个authentication是否验证 CFStreamError err; if (!authentication) { // the newly created authentication object is bad, must return return; } else if (!CFHTTPAuthenticationIsValid(authentication, &err)) {// destroy authentication and credentials if (credentials) { CFRelease(credentials); credentials = NULL; } CFRelease(authentication); authentication = NULL; // check for bad credentials (to be treated separately) if (err.domain == kCFStreamErrorDomainHTTP && (err.error == kCFStreamErrorHTTPAuthenticationBadUserName || err.error == kCFStreamErrorHTTPAuthenticationBadPassword)) { retryAuthorizationFailure(&authentication); return; } else { errorOccurredLoadingImage(err); } } //如果找到证书,那么使用它 else { cancelLoad(); if (credentials) { resumeWithCredentials(); } // are a user name & password needed? else if (CFHTTPAuthenticationRequiresUserNameAndPassword(authentication)) { CFStringRef realm = NULL; CFURLRef url = CFHTTPMessageCopyRequestURL(request); // check if you need an account domain so you can display it if necessary if (!CFHTTPAuthenticationRequiresAccountDomain(authentication)) { realm = CFHTTPAuthenticationCopyRealm(authentication); } // ...prompt user for user name (user), password (pass) // and if necessary domain (domain) to give to the server... // Guarantee values if (!user) user = CFSTR(""); if (!pass) pass = CFSTR(""); CFDictionarySetValue(credentials, kCFHTTPAuthenticationUsername, user); CFDictionarySetValue(credentials, kCFHTTPAuthenticationPassword, pass); // Is an account domain needed? (used currently for NTLM only) if (CFHTTPAuthenticationRequiresAccountDomain(authentication)) { if (!domain) domain = CFSTR(""); CFDictionarySetValue(credentials,kCFHTTPAuthenticationAccountDomain, domain); } if (realm) CFRelease(realm); CFRelease(url); } else { resumeWithCredentials(); } } //发起拥有证书的请求 void resumeWithCredentials() { // Apply whatever credentials we've built up to the old request if (!CFHTTPMessageApplyCredentialDictionary(request, authentication, credentials, NULL)) { errorOccurredLoadingImage(); } else { // Now that we've updated our request, retry the load loadRequest(); } }
把证书放在内存中
//创建一个array来放不同的证书 CFMutableArrayRef authArray; //创建一个证书的映射 CFMutableDictionaryRef credentialsDict; //修改映射 CFDictionaryRemoveValue(credentialsDict, authentication); //寻找一个合适的证书 CFHTTPAuthenticationRef findAuthenticationForRequest { int i, c = CFArrayGetCount(authArray); for (i = 0; i < c; i ++) { CFHTTPAuthenticationRef auth = (CFHTTPAuthenticationRef) CFArrayGetValueAtIndex(authArray, i); if (CFHTTPAuthenticationAppliesToRequest(auth, request)) { return auth; } } return NULL; } //获取证书 credentials = CFDictionaryGetValue(credentialsDict, authentication);
持久化证书存储
CFMutableDictionaryRef findCredentialsForAuthentication( CFHTTPAuthenticationRef auth); void saveCredentialsForRequest(void); //如果证书不在缓存中,那么从钥匙串中寻找 didFind = SecKeychainFindInternetPassword(NULL, strlen(host), host, realm ? strlen(realm) : 0, realm, 0, NULL, path ? strlen(path) : 0, path, port, protocolType, authenticationType, 0, NULL, &itemRef); //从钥匙串中加载服务器证书 if (didFind == noErr) { SecKeychainAttribute attr; SecKeychainAttributeList attrList; UInt32 length; void *outData; // To set the account name attribute attr.tag = kSecAccountItemAttr; attr.length = 0; attr.data = NULL; attrList.count = 1; attrList.attr = &attr; if (SecKeychainItemCopyContent(itemRef, NULL, &attrList, &length, &outData) == noErr) { // attr.data is the account (username) and outdata is the password CFStringRef username = CFStringCreateWithBytes(kCFAllocatorDefault, attr.data, attr.length, kCFStringEncodingUTF8, false); CFStringRef password = CFStringCreateWithBytes(kCFAllocatorDefault, outData, length, kCFStringEncodingUTF8, false); SecKeychainItemFreeContent(&attrList, outData); // create credentials dictionary and fill it with the user name & password credentials = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFDictionarySetValue(credentials, kCFHTTPAuthenticationUsername, username); CFDictionarySetValue(credentials, kCFHTTPAuthenticationPassword, password); CFRelease(username); CFRelease(password); } CFRelease(itemRef); } //修改钥匙串的实体 // Set the attribute to the account name attr.tag = kSecAccountItemAttr; attr.length = strlen(username); attr.data = (void*)username; // Modify the keychain entry SecKeychainItemModifyContent(itemRef, &attrList, strlen(password), (void *)password); //保存钥匙串实体 SecKeychainAddInternetPassword(NULL,strlen(host), host, realm ? strlen(realm) : 0, realm, strlen(username), username, path ? strlen(path) : 0, path, port, protocolType, authenticationType, strlen(password), password, &itemRef);
FTP Servers
//流的结构 typedef struct MyStreamInfo { CFWriteStreamRef writeStream; CFReadStreamRef readStream; CFDictionaryRef proxyDict; SInt64 fileSize; UInt32 totalBytesWritten; UInt32 leftOverByteCount; UInt8 buffer[kMyBufferSize]; } MyStreamInfo; //从读流中写数据 bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize); //...make sure bytesRead > 0 ... bytesWritten = 0; while (bytesWritten < bytesRead) { CFIndex result; result = CFWriteStreamWrite(info->writeStream, info->buffer + bytesWritten, bytesRead - bytesWritten); if (result <= 0) { fprintf(stderr, "CFWriteStreamWrite returned %ld\n", result); goto exit; } bytesWritten += result; } info->totalBytesWritten += bytesWritten; //上传文件 do { // Check for leftover data if (info->leftOverByteCount > 0) { bytesRead = info->leftOverByteCount; } else { // Make sure there is no error reading from the file bytesRead = CFReadStreamRead(info->readStream, info->buffer, kMyBufferSize); if (bytesRead < 0) { fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead); goto exit; } totalBytesRead += bytesRead; } // Write the data to the write stream bytesWritten = CFWriteStreamWrite(info->writeStream, info->buffer, bytesRead); if (bytesWritten > 0) { info->totalBytesWritten += bytesWritten; // Store leftover data until kCFStreamEventCanAcceptBytes event occurs again if (bytesWritten < bytesRead) { info->leftOverByteCount = bytesRead - bytesWritten; memmove(info->buffer, info->buffer + bytesWritten, info->leftOverByteCount); } else { info->leftOverByteCount = 0; } } else { if (bytesWritten < 0) fprintf(stderr, "CFWriteStreamWrite returned %ld\n", bytesWritten); break; } } while (CFWriteStreamCanAcceptBytes(info->writeStream));
创建一个远程目录
下载目录清单
//从目录清单下载数据 // If previous call had unloaded data int offset = info->leftOverByteCount; // Load data from the read stream, accounting for the offset bytesRead = CFReadStreamRead(info->readStream, info->buffer + offset, kMyBufferSize - offset); if (bytesRead < 0) { fprintf(stderr, "CFReadStreamRead returned %ld\n", bytesRead); break; } else if (bytesRead == 0) { break; } bufSize = bytesRead + offset; totalBytesRead += bufSize; //下载整个目录并且解析 do { bufRemaining = info->buffer + totalBytesConsumed; bytesConsumed = CFFTPCreateParsedResourceListing(NULL, bufRemaining, bufSize, &parsedDict); if (bytesConsumed > 0) { // Make sure CFFTPCreateParsedResourceListing was able to properly // parse the incoming data if (parsedDict != NULL) { // ...Print out data from parsedDict... CFRelease(parsedDict); } totalBytesConsumed += bytesConsumed; bufSize -= bytesConsumed; info->leftOverByteCount = bufSize; } else if (bytesConsumed == 0) { // This is just in case. It should never happen due to the large buffer size info->leftOverByteCount = bufSize; totalBytesRead -= info->leftOverByteCount; memmove(info->buffer, bufRemaining, info->leftOverByteCount); } else if (bytesConsumed == -1) { fprintf(stderr, "CFFTPCreateParsedResourceListing parse failure\n"); // ...Break loop and cleanup... } } while (bytesConsumed > 0);
使用网络诊断
case kCFStreamEventErrorOccurred: CFNetDiagnosticRef diagRef = CFNetDiagnosticCreateWithStreams(NULL, stream, NULL); (void)CFNetDiagnosticDiagnoseProblemInteractively(diagRef); CFStreamError error = CFReadStreamGetError(stream); reportError(error); CFReadStreamClose(stream); CFRelease(stream); break;