流
在通信路径中串行传输的连续的比特位序列(二进制数据串)。即,二进制数据串在端与端之间的传输。
从编码的角度来看,流是单向的,因此流可以分输入流
或输出流
。
在 Cocoa 中提供了 NSStream
、NSInputStream
、NSOutputStream
三个类,来实现数据通过流的方式在文件、内存、网络之间的传输。
NSStream
不支持套接字连接,要实现远程客户端套接字交互,可以使CFStream
的相关接口。
NSStream
及其子类进行的是比较底层的开发,对于某些特殊的需求如果有顶层的Cocoa API更加适合的话(比如NSURL
,NSFileHandle
),那么就用顶层的API进行编程
NSStream
NSStream
是一个抽象类,定义了一些基本的方法和属性。它是NSInputStream
和 NSOutputStream
类的父类。
- 使用
常用方法如下:
- (void)open;
- (void)close;
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;
用完就关
当流创建并开启后,会占用一些系统资源,所以流使用结束后,应主动关闭。关闭后的流不能再次开启,但其相关属性仍可以访问。如果流被添加到运行循环RunLoop
中,关闭时应将其移出RunLoop
。
NSStream
是建立在Core Foundation
的CFStream
层之上的。这层紧密的关系意味着NSStream
的具体子类NSInputStream
和NSOutputStream
与Core Foundation
中的CFReadStream
和CFWriteStream
是一一对应的。
尽管Cocoa
和Core Foundation
的stream APIs有很大的相似性,但是它们的实现却不尽相同:
NSStream
类使用delegate
模式来实现异步操作(比如将其布置在run loop
之上),而Core Foundation
使用客户端的回调。
Core Foundation
的stream类型设置的是client(在Core Foundation
中叫做context
),NSStream
中设置的delegate
,这是两个不同的概念,不应该把设置delegate
和设置context
搞混淆。
- 流的状态
NSStreamStatus
@property (readonly) NSStreamStatus streamStatus;
typedef NS_ENUM(NSUInteger, NSStreamStatus) {
NSStreamStatusNotOpen = 0,
NSStreamStatusOpening
NSStreamStatusOpen
NSStreamStatusReading
NSStreamStatusWriting
NSStreamStatusAtEnd
NSStreamStatusClosed
NSStreamStatusError
};
- 流的属性
大部分属性是用于处理网络安全和配置。
以下方法可以设置/获取流相关的属性配置
#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)
- (nullable id)propertyForKey:(NSStreamPropertyKey)key;
- (BOOL)setProperty:(nullable id)property forKey:(NSStreamPropertyKey)key;
#else
- (nullable id)propertyForKey:(NSString *)key;
- (BOOL)setProperty:(nullable id)property forKey:(NSString *)key;
#endif
属性的键
NSStreamPropertyKey
:
NSStreamSocketSecurityLevelKey
套接字安全选项
NSStreamSOCKSProxyConfigurationKey
套接字代理的配置信息
NSStreamDataWrittenToMemoryStreamKey
输出流写入内存中的数据*
NSStreamFileCurrentOffsetKey
基于文件的流的数据偏移量*
NSStreamNetworkServiceType
指定流的用途
- 流的代理
NSStreamDelegate
@property (nullable, assign) id
delegate;
代理可以不指定。如果不指定,那么流本身即为代理对象,应实现代理方法:
NSStreamDelegate
协议中的唯一的方法:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
switch (eventCode)
{
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
break;
case NSStreamEventErrorOccurred:
break;
case NSStreamEventEndEncountered:
break;
default:
break;
}
}
用来处理与流相关的事件。
流一旦打开后,将会持续发送
stream:handleEvent:
消息给代理对象,直到流结束为止。这个消息接收一个NSStreamEvent
常量作为参数,以标识事件的类型。
- 流的事件
NSStreamEvent
可能的事件如下:
NSStreamEventNone 无事件发生
NSStreamEventOpenCompleted 成功打开流
NSStreamEventHasBytesAvailable 流中有数据可读。代理向该stream发送`read:maxLength:`消息,从流中读取数据
NSStreamEventHasSpaceAvailable 可以向流中写入数据。代理向该stream发送`write:maxlength:`消息,向流中写入数据。
NSStreamEventErrorOccurred 发生错误
NSStreamEventEndEncountered 达到流末尾
NSInputStream
NSInputStream
是输入流,流中的数据来自本地文件或网络资源。应用从输入流中读取到数据后,便可进行必要的处理。
创建
创建NSInputStream
实例对象时,应当指定数据源,可以是文件、内存、NSData
对象、或网络资源。读取数据
属性hasBytesAvailable
表示当前输入流中,是否有可以读取的数据。如果为YES
那么可以调用流的read
方法进行读取:
// buffer 是提供的缓存地址
// len 是可以读取的数据的最大字节数
//返回:实际读取的数据字节数
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;
switch (eventCode) {
case NSStreamEventHasBytesAvailable:{
if (!data)
data = [NSMutableData data];
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)aStream read:buf maxLength:1024]; // 读取数据
if (len)
[data appendBytes:(const void *)buf length:len];
}
break;
}
关闭,清理流对象。
当NSInputStream
读取到流的结尾时,会发送一个NSStreamEventEndEncountered
事件给代理,代理对象应该销毁流对象,此过程应该与创建时相对应。
{
switch (eventCode) {
case NSStreamEventEndEncountered:
{
[aStream close];
[aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
aStream = nil;
}
break;
}
}
NSOutputStream
NSOutputStream
是输出流,将处理后的数据写入输出流中,而后数据传输到目标。这个目标可以是本地文件,或网上的某个地址。
- 创建
创建NSOutputStream
实例对象时,应当指定数据输出的目标,可以是内存、本地文件、NSData
对象、或网络地址。
如果输出流的目标为内存,则可使用
NSStreamDataWrittenToMemoryStreamKey
获取最终输出的数据。
- 写入数据
属性hasSpaceAvailable
值如果为YES
,则表示可以向流中write
写入数据:
// buffer 从指定的缓存中向流中写入数据
// len 可以写入的数据最大字节数
//返回:实际向流中写入的数据字节数
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len;
{
switch (eventCode) {
case NSStreamEventHasSpaceAvailable:
{
uint8_t *readBytes = (uint8_t *)[data mutableBytes];
readBytes += byteIndex;
int data_len = [data length];
unsigned int len = (data_len - byteIndex >= 1024) ? 1024 : (data_len - byteIndex);
uint8_t buf[len];
(void)memcpy(buf, readBytes, len);
len = [aStream write:(const uint_8 *)buf maxLength:len];
byteIndex += len;
break;
}
}
}
- 流的轮循处理(阻塞主线程)
在流的处理过程中,除了将流放入run loop
来处理流事件外,还可以对流进行轮循处理。
将流处理数据的过程放到一个while
循环中,并在循环中不断地去询问流是否有可用的数据供读取(hasBytesAvailable==YES
),或可用的空间供写入(hasSpaceAvailable==YES
)。当处理到流的结尾时,我们跳出循环结束流的操作。
- (void)createNewFile
{
NSOutputStream *oStream = [[NSOutputStream alloc] initToMemory];
[oStream open];
uint8_t *readBytes = (uint8_t *)[data mutableBytes];
uint8_t buf[1024];
int len = 1024;
while (1) {
if (len == 0) break;
if ([oStream hasSpaceAvailable])
{
(void)strncpy(buf, readBytes, len);
readBytes += len;
if ([oStream write:(const uint8_t *)buf maxLength:len] == -1)
{
[self handleError:[oStream streamError]];
break;
}
[bytesWritten setIntValue:[bytesWritten intValue]+len];
len = (([data length] - [bytesWritten intValue] >= 1024) ? 1024 : [data length] - [bytesWritten intValue]);
}
}
NSData *newData = [oStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
if (!newData) {
NSLog(@"No data written to memory!");
} else {
[self processData:newData];
}
[oStream close];
[oStream release];
oStream = nil;
}
这种处理方法的问题在于它会阻塞当前线程
,直到流处理结束为止。
- 错误处理
当流出现错误时,会停止对流数据的处理。一个流对象在出现错误时,不能再用于读或写操作。
告知错误的发生几种方式:
1、如果流被放到run loop
中,对象会发送一个NSStreamEventErrorOccurred
事件到代理对象的代理方法stream:handleEvent:
中
任何时候,可以调用streamStatus
属性来查看是否发生错误(返回NSStreamStatusError
)
2、如果在通过调用write:maxLength:
写入数据到NSOutputStream
对象时返回-1,则发生一个写错误。
一旦确定产生错误时,我们可以调用流对象的streamError
属性来查看错误的详细信息。
开发步骤
1、网络连接设置
设置网络连接,绑定到主机和端口
设置输入流和输出流的代理,监听数据流的状态
将输入输出流添加至运行循环
打开输入流和输出流
@interface ViewController (){
NSInputStream *_inputStream;
NSOutputStream *_outputSteam;
}
。。。
// 初始化连接
- (IBAction)connectToServer:(id)sender {
NSString *host = @"127.0.0.1"; // 服务器主机号
int port = 12345; // 端口号
// 定义输入输出流
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
// 分配输入输出流的 内存空间
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
// 把C语言的输入输出流 转成OC对象
_inputStream = (__bridge NSInputStream *)readStream;
_outputSteam = (__bridge NSOutputStream *)(writeStream);
// 设置代理,监听数据接收的状态
_outputSteam.delegate = self;
_inputStream.delegate = self;
// 把输入输入流添加到主运行循环(RunLoop)
// 主运行循环是监听网络状态
[_outputSteam scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 打开输入输出流
[_inputStream open];
[_outputSteam open];
}
2、发送消息给服务器
3、有可读取字节时,读取服务器返回的内容
4、到达流末尾时,关闭流,同时并从主运行循环中删除
//实现代理方法 (回调是在主线程)
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(@"成功连接建立,形成输入输出流的传输通道");
break;
case NSStreamEventHasBytesAvailable:
NSLog(@"有数据可读");
[self readData];
break;
case NSStreamEventHasSpaceAvailable:
NSLog(@"可以发送数据");
[self writeData];
break;
case NSStreamEventErrorOccurred:
NSLog(@"有错误发生,连接失败");
break;
case NSStreamEventEndEncountered:
NSLog(@"正常的断开连接");
//把输入输入流关闭,而还要从主运行循环移除
[_inputStream close];
[_outputSteam close];
[_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_outputSteam removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
break;
default:
break;
}
}
# 读取服务器 返回的数据
-(void)readData{
//定义缓冲区 ,只能存储1024字节
uint8_t buf[1024];
// 读取数据,len为从服务器读取到的实际字节数
NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)];
// 把缓冲区里的实现字节数转成字符串
NSString *receiverStr = [[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding];
NSLog(@"%@",receiverStr);
}
-(void)writeData{
int indexLen = 1024;
uint8_t buffer[1024];
int written = (int)[_outputSteam write: buffer maxLength: indexLen];
}
示例
@interface ViewController ()
@property (nonatomic,strong) NSString *filePath;
@property (nonatomic,assign) int location;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createTestFile];
}
// 创建一个测试文件。
- (void)createTestFile{
_filePath = NSHomeDirectory();
_filePath = [_filePath stringByAppendingPathComponent:@"Documents/test_data.txt"];
NSError *error;
NSString *msg = @"测试数据,需要的测试数据,测试数据显示。";
bool isSuccess = [msg writeToFile:_filePath atomically:true encoding:NSUTF8StringEncoding error:&error];
if (isSuccess) {
NSLog(@"数据写入成功了");
}else{
NSLog(@"error is %@",error.description);
}
// 追加数据
NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:_filePath];
[handle seekToEndOfFile];
NSString *newMsg = @".....我将添加到末尾你处";
NSData *data = [newMsg dataUsingEncoding:NSUTF8StringEncoding];
[handle writeData:data];
[handle closeFile];
}
// NSOutPutStream 处理 写
- (IBAction)outPutStramAction:(id)sender {
NSString *path = @"/Users/yubo/Desktop/stream_ios.txt";
NSOutputStream *writeStream = [[NSOutputStream alloc]initToFileAtPath:path append:true];
// 手动创建文件, 如果是系统创建的话, 格式编码不一样。
bool flag = [@"Ios----->" writeToFile:path atomically:true encoding:NSUTF8StringEncoding error:nil];
if (flag) {
NSLog(@"创建成功");
}
writeStream.delegate = self;
[writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[writeStream open];
}
// NSInPutStream 处理 读
- (IBAction)inPutStreamAction:(id)sender {
NSInputStream *readStream = [[NSInputStream alloc]initWithFileAtPath:_filePath];
[readStream setDelegate:self];
// 这个runLoop就相当于死循环,一直会对这个流进行操作。
[readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[readStream open];
}
#pragma mark NSStreamDelegate代理
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
switch (eventCode) {
case NSStreamEventHasSpaceAvailable:{ // 写
NSString *content = [NSString stringWithContentsOfFile:_filePath encoding:NSUTF8StringEncoding error:nil];
NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding];
NSOutputStream *writeStream = (NSOutputStream *)aStream;
[writeStream write:data.bytes maxLength:data.length];
[aStream close];
// 用buf的还没成功
// [writeStream write:<#(nonnull const uint8_t *)#> maxLength:<#(NSUInteger)#>]; 乱码形式
break;
}
case NSStreamEventHasBytesAvailable:{ // 读
uint8_t buf[1024];
NSInputStream *reads = (NSInputStream *)aStream;
NSInteger blength = [reads read:buf maxLength:sizeof(buf)];
if (blength != 0) {
NSData *data = [NSData dataWithBytes:(void *)buf length:blength];
NSString *msg = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"文件内容如下:----->%@",msg);
}else{
[aStream close];
}
break;
}
case NSStreamEventErrorOccurred:{// 错误处理
NSLog(@"错误处理");
break;
}
case NSStreamEventEndEncountered: {
[aStream close];
break;
}
case NSStreamEventNone:{// 无事件处理
NSLog(@"无事件处理");
break;
}
case NSStreamEventOpenCompleted:{// 打开完成
NSLog(@"打开文件");
break;
}
default:
break;
}
}
@end
其他资料
https://www.cnblogs.com/wangxiaorui/p/5029372.html