一,NSStream简介
- Cocoa层:NSURL,Bonjour,Game Kit,WebKit
- Core Foundation层:基于 C 的
CFNetwork 和 CFNetServices - OS层:基于 C 的 BSD socket
二,NSStream 类接口简介
NSStream
- (void)open;
- (void)close;
- (id
- (void)setDelegate:(id
- (void)scheduleInRunLoop:(NSRunLoop
- (void)removeFromRunLoop:(NSRunLoop
- (NSStreamStatus)streamStatus;
- (NSError
NSStream 的一些接口与 CFNetwork 类似,如打开,关闭,获取状态和错误信息,以及和 runloop 结合等在这里就不再重复了。前面提到 NSStream 是通过 NSStreamDelegate 来实现 CFNetwork 中的回调函数,这个可选的协议只有一个接口:
- (void)stream:(NSStream
NSStreamEvent 是一个流事件枚举:
typedef
};
这些事件枚举的含义也和 CFNetwork 中的
NSInputStream
- (NSInteger)read:(uint8_t
从流中读取数据到 buffer 中,buffer 的长度不应少于 len,该接口返回实际读取的数据长度(该长度最大为 len)。
- (BOOL)getBuffer:(uint8_t
获取当前流中的数据以及大小,注意 buffer 只在下一个流操作之前有效。
- (BOOL)hasBytesAvailable;
检查流中是否还有数据。
NSOutputStream
- (NSInteger)write:(const
将 buffer 中的数据写入流中,返回实际写入的字节数。
- (BOOL)hasSpaceAvailable;
检查流中是否还有可供写入的空间。
从这些接口可以看出,NSStream 真的就是 CFNetwork 上的一层简单的 Objective-C 封装。但 iOS 中的 NSStream 不支持 NShost,这是一个缺陷,苹果也意识到这问题了(http://developer.apple.com/library/ios/#qa/qa1652/_index.html),我们可以通过 NSStream 的扩展函数来实现该功能:
@implementation NSStream(StreamsToHost) + (void)getStreamsToHostNamed:(NSString *)hostName port:(NSInteger)port inputStream:(out NSInputStream **)inputStreamPtr outputStream:(out NSOutputStream **)outputStreamPtr { CFReadStreamRef readStream; CFWriteStreamRef writeStream; assert(hostName != nil); assert( (port > 0) && (port < 65536) ); assert( (inputStreamPtr != NULL) || (outputStreamPtr != NULL) ); readStream = NULL; writeStream = NULL; CFStreamCreatePairWithSocketToHost( NULL, (__bridge CFStringRef) hostName, port, ((inputStreamPtr != NULL) ? &readStream : NULL), ((outputStreamPtr != NULL) ? &writeStream : NULL) ); if (inputStreamPtr != NULL) { *inputStreamPtr = CFBridgingRelease(readStream); } if (outputStreamPtr != NULL) { *outputStreamPtr = CFBridgingRelease(writeStream); } } @end
三,客户端示例代码
与前面的示例类似,在这里我只演示客户端示例。同样,我们也在一个后台线程中启动网络操作:
NSURL * url = [NSURL URLWithString:[NSString stringWithFormat:@"%@:%@", serverHost, serverPort]]; NSThread * backgroundThread = [[NSThread alloc] initWithTarget:self selector:@selector(loadDataFromServerWithURL:) object:url]; [backgroundThread start];
然后在
- (void)loadDataFromServerWithURL:(NSURL *)url { NSInputStream * readStream; [NSStream getStreamsToHostNamed:[url host] port:[[url port] integerValue] inputStream:&readStream outputStream:NULL]; [readStream setDelegate:self]; [readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [readStream open]; [[NSRunLoop currentRunLoop] run]; }
因为我们将 KSNSStreamViewController
#pragma mark NSStreamDelegate - (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode { NSLog(@" >> NSStreamDelegate in Thread %@", [NSThread currentThread]); switch (eventCode) { case NSStreamEventHasBytesAvailable: { if (_receivedData == nil) { _receivedData = [[NSMutableData alloc] init]; } uint8_t buf[kBufferSize]; int numBytesRead = [(NSInputStream *)stream read:buf maxLength:kBufferSize]; if (numBytesRead > 0) { [self didReceiveData:[NSData dataWithBytes:buf length:numBytesRead]]; } else if (numBytesRead == 0) { NSLog(@" >> End of stream reached"); } else { NSLog(@" >> Read error occurred"); } break; } case NSStreamEventErrorOccurr ed: { NSError * error = [stream streamError]; NSString * errorInfo = [NSString stringWithFormat:@"Failed while reading stream; error '%@' (code %d)", error.localizedDescription, error.code]; [self cleanUpStream:stream]; [self networkFailedWithErrorMe ssage:errorInfo]; } case NSStreamEventEndEncounte red: { [self cleanUpStream:stream]; [self didFinishReceivingData]; break; } default: break; } }
当数据读取完毕或者读取失败时,调用 cleanUpStream 方法来关闭流:
- (void)cleanUpStream:(NSStream *)stream
{
[stream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[stream close];
stream = nil;
}
四,结语
通过上面的示例演示,我们可以看到 NSStream 只是用 Objective-C