流NSStream

作者: Arthur澪 | 来源:发表于2018-11-28 10:17 被阅读0次

    在通信路径中串行传输的连续的比特位序列(二进制数据串)。即,二进制数据串在端与端之间的传输。
    从编码的角度来看,流是单向的,因此流可以分输入流输出流
    在 Cocoa 中提供了 NSStreamNSInputStreamNSOutputStream三个类,来实现数据通过流的方式在文件、内存、网络之间的传输。

    NSStream 不支持套接字连接,要实现远程客户端套接字交互,可以使CFStream 的相关接口。
    NSStream及其子类进行的是比较底层的开发,对于某些特殊的需求如果有顶层的Cocoa API更加适合的话(比如NSURLNSFileHandle),那么就用顶层的API进行编程

    NSStream

    NSStream是一个抽象类,定义了一些基本的方法和属性。它是NSInputStreamNSOutputStream类的父类。

    • 使用
      常用方法如下:
    - (void)open;
    - (void)close;
    - (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;
    - (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSRunLoopMode)mode;
    

    用完就关
    当流创建并开启后,会占用一些系统资源,所以流使用结束后,应主动关闭。关闭后的流不能再次开启,但其相关属性仍可以访问。如果流被添加到运行循环RunLoop中,关闭时应将其移出RunLoop

    NSStream是建立在Core FoundationCFStream层之上的。这层紧密的关系意味着NSStream的具体子类NSInputStreamNSOutputStreamCore Foundation中的CFReadStreamCFWriteStream是一一对应的。
    尽管CocoaCore 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 <NSStreamDelegate> 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 ()<NSStreamDelegate>{
        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 ()<NSStreamDelegate>
     
    @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

    相关文章

      网友评论

        本文标题:流NSStream

        本文链接:https://www.haomeiwen.com/subject/zbggqqtx.html