移动开发网络ios

iOS 移动开发网络 part6:SRWebSocket

2018-01-13  本文已影响468人  破弓

本文在笔者阅读SRWebSocket源码的基础上,还借鉴了SRWebSocket源码浅析.

1.创建

webSocket = [[SRWebSocket alloc]initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://%@:%d", Khost, Kport]]];
webSocket.delegate = self;

NSOperationQueue *queue = [[NSOperationQueue alloc]init];
queue.maxConcurrentOperationCount = 1;//zc read:并发数为1==>串行队列
[webSocket setDelegateOperationQueue:queue];
- (id)initWithURL:(NSURL *)url;
.
.
.
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
{
    self = [super init];
    if (self) {
        assert(request.URL);
        _url = request.URL;
        _urlRequest = request;
        _allowsUntrustedSSLCertificates = allowsUntrustedSSLCertificates;
        
        _requestedProtocols = [protocols copy];
        
        [self _SR_commonInit];
    }
    
    return self;
}
- (void)_SR_commonInit;
{
    NSString *scheme = _url.scheme.lowercaseString;
    assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);
    
    if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) {
        _secure = YES;
    }
    
    _readyState = SR_CONNECTING;
    _consumerStopped = YES;
    _webSocketVersion = 13;
    
    _workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
    
    // Going to set a specific on the queue so we can validate we're on the work queue
    dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
    
    _delegateDispatchQueue = dispatch_get_main_queue();
    sr_dispatch_retain(_delegateDispatchQueue);
    
    _readBuffer = [[NSMutableData alloc] init];//读数据缓存
    _outputBuffer = [[NSMutableData alloc] init];//写数据缓存
    
    _currentFrameData = [[NSMutableData alloc] init];//单帧数据缓存

    _consumers = [[NSMutableArray alloc] init];//读取数据消费者数组
    _consumerPool = [[SRIOConsumerPool alloc] init];//读取数据消费者复用池
    
    _scheduledRunloops = [[NSMutableSet alloc] init];
    
    [self _initializeStreams];//初始化读写的Stream
    
    // default handlers
}

SRWebSocket的初始化动作主要做了:

读写Stream的构建;
读写缓存准备;
消费者数组的建立;//消费者的具体作用我们后面会说
消费者复用池的建立;

关于消费者的复用:新鲜的消费者会被加在消费者数组内等待被应用,而一旦新鲜的消费者被应用就不新鲜了,就会被加入消费者复用池.在需要新的消费者的时候会检测消费者复用池内是否有数据?
有,则将消费者复用池不新鲜的消费者重置成新鲜的消费者,加入消费者数组;
没有,直接新建消费者,加入消费者数组;
<本段内容不理解不会影响大局,请放心向下阅读>

当然地址开头必须是:ws,wss,http,https.wsshttps都建立在TLS的基础上.SRWebSocket本身就有完成自签名证书认证的代码,我们需要做的只是把证书设置到NSMutableURLRequestSR_SSLPinnedCertificates属性上,然后用NSMutableURLRequest建立SRWebSocket.

//SRWebSocket内部为我们写好的分类:

@implementation  NSMutableURLRequest (SRCertificateAdditions)

- (NSArray *)SR_SSLPinnedCertificates;
{
    return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self];
}

- (void)setSR_SSLPinnedCertificates:(NSArray *)SR_SSLPinnedCertificates;
{
    [NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self];
}

@end

2.连接

[webSocket open];

- (void)open;
{
    assert(_url);
    NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once");

    _selfRetain = self;

    if (_urlRequest.timeoutInterval > 0)
    {
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, _urlRequest.timeoutInterval * NSEC_PER_SEC);
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            if (self.readyState == SR_CONNECTING)
                [self _failWithError:[NSError errorWithDomain:@"com.squareup.SocketRocket" code:504 userInfo:@{NSLocalizedDescriptionKey: @"Timeout Connecting to Server"}]];
        });
    }

    [self openConnection];
}

- (void)openConnection;
{
    [self _updateSecureStreamOptions];
    
    if (!_scheduledRunloops.count) {
        [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
    }
    
    
    [_outputStream open];
    [_inputStream open];
}

如果你看过前几篇CocoaAsyncSocket的代码解析,或者对Stream建立网络数据读写关系有一定了解,就一定对一条专属线程,一个runLoop,两个Stream的套路不陌生.

套路:
新建一条专属线程,保证线程不会销毁;
线程不会销毁,伴随着对应runLoop也一直存在;
两个Stream加入runLoop,就构建起了两端持续的读写关系;

两个Stream打开后,正常会调用代理方法:

//- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode内部主要完成了TLS自签名证书校验的功能
//代理方法应有的全部功能代码都在- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream
//所以- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream会是我们讨论的重点
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
    .
    . //省略的代码是SRWebSocket进行TLS自签名证书校验的代码
    .
    dispatch_async(_workQueue, ^{
        [weakSelf safeHandleEvent:eventCode stream:aStream];
    });
}
- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream
{
        switch (eventCode) {
            case NSStreamEventOpenCompleted: {
                
                NSLog(@"zc read:NSStreamEventOpenCompleted %@",aStream);
                
                SRFastLog(@"NSStreamEventOpenCompleted %@", aStream);
                if (self.readyState >= SR_CLOSING) {
                    return;
                }
                assert(_readBuffer);
                
                // didConnect fires after certificate verification if we're using pinned certificates.
                BOOL usingPinnedCerts = [[_urlRequest SR_SSLPinnedCertificates] count] > 0;
                if ((!_secure || !usingPinnedCerts) && self.readyState == SR_CONNECTING && aStream == _inputStream) {
                    [self didConnect];
                }
                [self _pumpWriting];
                [self _pumpScanner];
                break;
            }
   .
   .
   .
}

eventCode==NSStreamEventOpenCompleted==>- (void)didConnect被调用,- (void)didConnect方法内部会发出HTTP报文.

eventCode==NSStreamEventOpenCompleted代表socket连接建立完成;
WebSocket的开启会用HTTP报文做'前导',HTTP报文内会有字段:[Upgrade:websocket].
HTTP报文发给服务端,服务端会有数据回传,我们会'读'服务端回传的数据来判定WebSocket的开启是否成功.
当然具体怎么'读'下面会说.

3.读

这是SRWebSocket代码中最复杂的部分,因为代码内会出现:方法调用blcok触发交叉混合的进行,所以逻辑会有些复杂.

读取套路:

加消费者
- (void)_addConsumerWithScanner:(stream_scanner)consumer callback:(data_callback)callback dataLength:(size_t)dataLength;//读边界
- (void)_addConsumerWithDataLength:(size_t)dataLength callback:(data_callback)callback readToCurrentFrame:(BOOL)readToCurrentFrame unmaskBytes:(BOOL)unmaskBytes;//读定长

用消费者读数据
-(void)_pumpScanner;
{
    while ([self _innerPumpScanner]) {
        
    }
}

3.1读HTTP响应报文

- (void)didConnect;
{
    .
    .
    .
    [self _writeData:message];//发出HTTP的报文
    [self _readHTTPHeader];//添加了读取回复的消费者
}

didConnect内除了发出HTTP报文,还添加了读取回复的消费者.

- (void)_readHTTPHeader;
{
    if (_receivedHTTPHeaders == NULL) {
        _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
    }
    
    [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) {
        CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
        
        if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
            SRFastLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
            [self _HTTPHeadersDidFinish];
        } else {
            [self _readHTTPHeader];
        }
    }];
}
- (void)_readUntilHeaderCompleteWithCallback:(data_callback)dataHandler;
{
    [self _readUntilBytes:CRLFCRLFBytes length:sizeof(CRLFCRLFBytes) callback:dataHandler];
}

- (void)_readUntilBytes:(const void *)bytes length:(size_t)length callback:(data_callback)dataHandler;
{
    // TODO optimize so this can continue from where we last searched
    stream_scanner edge_consumer = ^size_t(NSData *data) {
        __block size_t found_size = 0;
        __block size_t match_count = 0;
        
        size_t size = data.length;
        const unsigned char *buffer = data.bytes;
        for (size_t i = 0; i < size; i++ ) {
            if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) {
                match_count += 1;
                if (match_count == length) {
                    found_size = i + 1;
                    break;
                }
            } else {
                match_count = 0;
            }
        }
        return found_size;
    };
    [self _addConsumerWithScanner:edge_consumer callback:dataHandler];
}
- (void)_addConsumerWithScanner:(stream_scanner)edge_consumer callback:(data_callback)callback;
{
    [self assertOnWorkQueue];
    //zc focus:addConsumer1
    [self _addConsumerWithScanner:edge_consumer callback:callback dataLength:0];
}
- (void)_addConsumerWithScanner:(stream_scanner)edge_consumer callback:(data_callback)callback dataLength:(size_t)dataLength;
{    
    [self assertOnWorkQueue];
    [_consumers addObject:[_consumerPool consumerWithScanner:edge_consumer handler:callback bytesNeeded:dataLength readToCurrentFrame:NO unmaskBytes:NO]];
    [self _pumpScanner];
}

可以看出读取回复信息的方法被加入了block,block被赋给了消费者(SRIOConsumer),消费者再加入了NSMutableArray *_consumers.

@interface SRIOConsumer : NSObject 

@property (nonatomic, copy, readonly) stream_scanner edge_consumer;//边界数据提取的block
@property (nonatomic, copy, readonly) data_callback handler;//读数据操作的block
@property (nonatomic, assign) size_t bytesNeeded;
@property (nonatomic, assign, readonly) BOOL readToCurrentFrame;
@property (nonatomic, assign, readonly) BOOL unmaskBytes;
@property (nonatomic, assign) NSInteger tag;

@end

HTTP响应报文是有边界的,上面代码出现的stream_scanner edge_consumer就是边界数据提取的block.

一旦有回复数据,则会走- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream的以下代码模块.

- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream
{
   .
   .
   .
            case NSStreamEventHasBytesAvailable: {
                NSLog(@"zc read:NSStreamEventHasBytesAvailable %@",aStream);
                SRFastLog(@"NSStreamEventHasBytesAvailable %@", aStream);
                const int bufferSize = 2048;
                uint8_t buffer[bufferSize];
                
                while (_inputStream.hasBytesAvailable) {
                    NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize];
                    
                    if (bytes_read > 0) {
                        [_readBuffer appendBytes:buffer length:bytes_read];
                    } else if (bytes_read < 0) {
                        [self _failWithError:_inputStream.streamError];
                    }
                    
                    if (bytes_read != bufferSize) {
                        break;
                    }
                };
                
                [self _pumpScanner];
                break;
            }
   .
   .
   .
}

_inputStream的数据来了先写入_readBuffer,在进行后续的读操作——-(void)_pumpScanner.

_pumpScanner内部会循环的调用_innerPumpScanner,_innerPumpScanner消费者读取block被触发的地方,所以_innerPumpScanner是读取的核心.

在读取HTTP响应报文的时候:

先取出排在第一的消费者,然后用consumer.edge_consumer获取HTTP响应报文的长度;
再从_readBuffer内读出相应长度的数据;
再由consumer.handler解析数据;

HTTP响应报文消费者data_callback handler内的核心方法就是_HTTPHeadersDidFinish.

- (void)_HTTPHeadersDidFinish;
{
    NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders);
    
    if (responseCode >= 400) {
        SRFastLog(@"Request failed with response code %d", responseCode);
        [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2132 userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"received bad response code from server %ld", (long)responseCode], SRHTTPResponseErrorKey:@(responseCode)}]];
        return;
    }
    
    if(![self _checkHandshake:_receivedHTTPHeaders]) {
        [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Invalid Sec-WebSocket-Accept response"] forKey:NSLocalizedDescriptionKey]]];
        return;
    }
    
    NSString *negotiatedProtocol = CFBridgingRelease(CFHTTPMessageCopyHeaderFieldValue(_receivedHTTPHeaders, CFSTR("Sec-WebSocket-Protocol")));
    if (negotiatedProtocol) {
        // Make sure we requested the protocol
        if ([_requestedProtocols indexOfObject:negotiatedProtocol] == NSNotFound) {
            [self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2133 userInfo:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Server specified Sec-WebSocket-Protocol that wasn't requested"] forKey:NSLocalizedDescriptionKey]]];
            return;
        }
        
        _protocol = negotiatedProtocol;
    }
    
    self.readyState = SR_OPEN;
    
    if (!_didFail) {
        [self _readFrameNew];
    }

    [self _performDelegateBlock:^{
        if ([self.delegate respondsToSelector:@selector(webSocketDidOpen:)]) {
            [self.delegate webSocketDidOpen:self];
        };
    }];
}

_HTTPHeadersDidFinish方法内部逻辑很简单:

1.
检验HTTP响应报文,看报文是否报错,报文报错则代码生成报错终止连接;

2.
检验报文内的'Sec-WebSocket-Accept'对应的值是否
与'static NSString *const SRWebSocketAppendToSecKeyString = @"258EAFA5-E914-47DA-95CA-C5AB0DC85B11";'(这个字符串是WebSocket协议规定的)相等,
不相等则代码生成报错终止连接;

3.
以上没报错则改变连接状态(self.readyState = SR_OPEN);
继续添加读取消费者读数据==>[self _readFrameNew];
通知代理:WebSocket真的开启了;

注意[继续添加读取消费者读数据],这次来的数据里可能不止是HTTP响应报文,还可能跟着WebSocket的数据,当然有可能没跟WebSocket的数据.

后续跟着WebSocket的数据:添加读取消费者也会立刻调用_pumpScanner,继续读WebSocket的数据;
后续没跟着WebSocket的数据:添加读取消费者也会立刻调用_pumpScanner, _innerPumpScanner方法内部会判断有没有数据可以读,没有会终止调用,消费者处于等待状态;

注意:_readFrameNew会调用_readFrameContinue添加消费者,这个调用是异步的.添加消费者后会立马调用_pumpScanner进行读取操作.如果HTTP响应报文后面跟着WebSocket的数据,那么这两段数据应该做分别读取,这也是为什么添加消费者得异步原因.这样就保证了读取HTTP响应报文_pumpScanner方法调用完毕,读取WebSocket的数据_pumpScanner方法再开始.

- (void)_readFrameNew;
{
    dispatch_async(_workQueue, ^{
        [_currentFrameData setLength:0];
        
        _currentFrameOpcode = 0;
        _currentFrameCount = 0;
        _readOpCount = 0;
        _currentStringScanPosition = 0;
        [self _readFrameContinue];
    });
}

3.2读WebSocket交互数据

首先我们先来看看WebSocket帧的格式.

0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+
FIN 1bit 表示信息的最后一帧
RSV 1-3 1bit each 以后备用的 默认都为0
Opcode 4bit 帧类型,稍后细说
Mask 1bit 掩码,是否加密数据,默认必须置为1
Payload len 7bit 数据的长度 (2^7 -1 最大到127)
Extended payload length [Payload len==126,Extended payload length为16bit] [Payload len==127,Extended payload length为64bit]
Masking-key [Mask为1,Masking-key为16bit] [Mask为0,Masking-key则无]
Payload data 数据 

就着这个格式,我们再来说一说,WebSocket交互数据是如何读取的.

3.2.1 先读头两个字节

前面已经说过- (void)_readFrameNew异步调用- (void)_readFrameContinue.- (void)_readFrameContinue方法就做了添加读头两个字节消费者的工作,代码如下:

- (void)_readFrameContinue;
{
    [self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) {
    .
    .
    .
    } readToCurrentFrame:NO unmaskBytes:NO];
}

先读头两个字节,刚好囊括(FIN,opcode,MASK,Payload len).

简单讨论起见,我们假定数据都呈单帧,即帧内FIN都为1.多帧数据我们后面再讨论.

typedef enum  {
    SROpCodeTextFrame = 0x1,
    SROpCodeBinaryFrame = 0x2,
    // 3-7 reserved.
    SROpCodeConnectionClose = 0x8,
    SROpCodePing = 0x9,
    SROpCodePong = 0xA,
    // B-F reserved.
} SROpCode;

SROpCodeConnectionClose,SROpCodePing,SROpCodePong都是控制帧,是不带数据的,所以读取开头两个字节的工作结束,整个读取工作也就接近尾声了.
SROpCodeTextFrame,SROpCodeBinaryFrame都是数据帧.

特殊说明:前面已经说过Mask设置为1,Masking-key就为16位.但WebSocket规定客户端给服务端发的必须是masked data,而服务端给客户端发必须是unmasked data.这是为什么?请戳(文中大体想表述的是客户端发masked data给服务端是为了防止服务器被攻击)
也就是我解读的SRWebSocket做为客户端读取数据时不用考虑masked data这种情况的.写数据的时候要考虑masked data的情况,我们后面会说.

Payload len有7位,最多可以表示127.

规定如下:

[Payload len==126,Extended payload length为16bit]
[Payload len==127,Extended payload length为64bit]

也就是说:
Payload len小于126时,Payload len所代表的数值也就是数据长度.
Payload len大于等于126时,我们就需要读Payload len后面的Extended payload length,Extended payload length所代表的数值也就是数据长度.

总结如下:

[Payload len]小于126,我用[Payload len]来读后续的Payload数据;
[Payload len]大于等于126,我用[Extended payload length]来读后续的Payload数据;

读取头两个字节结束后会运行下面的代码:

if (extra_bytes_needed == 0) {
    [self _handleFrameHeader:header curData:self->_currentFrameData];
} else {
    [self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) {
        .
        .//改变读取数据偏移,移动到Payload数据起始的位置
        .
        [self _handleFrameHeader:header curData:self->_currentFrameData];
    } readToCurrentFrame:NO unmaskBytes:NO];
}

extra_bytes_needed就是Extended payload length,以上的header就是如下结构体.

typedef struct {
    BOOL fin;
//  BOOL rsv1;
//  BOOL rsv2;
//  BOOL rsv3;
    uint8_t opcode;
    BOOL masked;
    uint64_t payload_length;
} frame_header;

Extended payload length为0,直接进入后续读取操作;
Extended payload length不为0,添加消费者,改变读取数据的偏移,移动到Payload数据起始的位置;

3.2.2 再读Payload数据

- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData;是读Payload数据的起始方法.

进入- (void)_handleFrameHeader:(frame_header)frame_header curData:(NSData *)curData;又分成读控制帧和读数据帧两种情况:

frame_header.payload_length==0,则判断是不是控制帧,是控制帧,直接调用- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode;方法,方法内部再根据是什么类型的控制帧来调用下面的方法(下面的方法会直接通知代理):

- (void)handleCloseWithData:(NSData *)data;
- (void)handlePing:(NSData *)pingData;
- (void)handlePong:(NSData *)pingData;

frame_header.payload_length>0,则要添加消费者读Payload数据:

[self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *data) {
.
.
.
} readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked];

消费者的读取block触发后,读取到Payload数据,然后再调用- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode;方法,方法内部根据数据帧是SROpCodeTextFrame还是SROpCodeBinaryFrame进行一些小处理再调用下面的方法(下面的方法会直接通知代理):

- (void)_handleMessage:(id)message;

- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode;内除了通知代理获得了数据,同时还会调用_readFrameNew加消费者为下一次数据的到来做准备!

3.2.3 多帧数据的说明

前面关于读取数据的代码解析都是假定数据都是单帧的,下面我们说说数据呈现多帧的情况.

if (frame_header.payload_length == 0) {
.
.
.
} else {
    [self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *data) {
        if (isControlFrame) {
            [self _handleFrameWithData:data opCode:frame_header.opcode];
        } else {
            if (frame_header.fin) {
                [self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode];
            } else {
                [self _readFrameContinue];//多帧入口
            }
        }
    } readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked];
}

以上判断就是读frame_header.payload_length>0,则添加消费者读Payload数据.我们再看消费者的block内的代码:frame_header.fin不为真则会调用[self _readFrameContinue].

对比:

1.读取新数据:清空读取的标记信息,再异步调用_readFrameContinue(前面已经说过)
- (void)_readFrameNew;
{
    dispatch_async(_workQueue, ^{
        [_currentFrameData setLength:0];
        
        _currentFrameOpcode = 0;
        _currentFrameCount = 0;
        _readOpCount = 0;
        _currentStringScanPosition = 0;
        [self _readFrameContinue];
    });
}

2.数据呈多帧,读下一帧:不清空读取的标记信息,同步调用_readFrameContinue
[self _readFrameContinue];

多帧数据,读下一帧:不会清除当前读取的一些标记信息==>按照老的标记信息来读下一帧数据,这样就可以粘连多帧数据.
而开启新的数据读取:会清除当前读取的一些标记信息==>从0开始读新数据.

我们先前已经解释过为什么开启新的数据读取,要异步调用_readFrameContinue.==>方便单次调用-(void)_pumpScanner结束,再开启下一次对-(void)_pumpScanner的调用.
而继续读下一帧,同步调用_readFrameContinue,立刻加入消费者,立刻用掉消费者==>正是为了-(void)_pumpScanner可以不间断的循环的调用内部方法- (BOOL)_innerPumpScanner,把多帧数据粘连起来.

-(void)_pumpScanner;
{
    [self assertOnWorkQueue];
    
    if (!_isPumping) {
        _isPumping = YES;
    } else {
        NSLog(@"zc read:_pumpScanner funcing so return");
        return;
    }
    
    NSLog(@"zc read:---------------loop in");
    
    while ([self _innerPumpScanner]) {
        
    }
    
    NSLog(@"zc read:---------------loop out");
    
    _isPumping = NO;
}
SRWebSocket read.png

SRWebSocket读的流程可以大体总结为上图,由于方法block的嵌套关系不能在图上很好的表达,所以真的只是大体呈现.

对比 CocoaAsyncSocket的readSRWebSocket的read
CocoaAsyncSocket是建packet,对packet进行一堆操作,衍生再建packet,再对packet进行一堆操作.(如:建边界包读边界,边界包会读出后续数据的长度,再建立定长包,读取定长数据)
SRWebSocket是建消费者,对消费者进行一堆操作,衍生再建消费者,再对消费者进行一堆操作.
看上去两套代码逻辑是十分相近的,但不同在于:
CocoaAsyncSocket的衍生关系靠的是清楚的建packet代码和方法调用;
SRWebSocket的衍生关系靠的是block嵌套方法,方法再嵌套block,所以SRWebSocket的读取数据的代码看上去显得更复杂!!!

4.写

- (void)send:(id)data;
- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;

- (void)sendPing:(NSData *)data;
- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;

写包括:写数据帧和写控制(Ping,Pong)帧,控制帧的数据可以为空.当然最后都调用- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;.

- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;内部逻辑是:
0.构建要发送的[NSMutableData *frame];

1.设置FIN和OpCode;

2.设置Mask位为1;

3.设置源数据的长度;
<126,则将数据的第2字节赋值成[源数据的长度];
>=126却<=[2^16-1],则将第2字节赋值成126,将随后的4个字节赋值成[源数据的长度];
>[2^16-1],则将第二字节赋值成127,将随后的8个字节赋值成[源数据的长度];

4.设置Masking-key;

5.将源数据用Masking-key加密,填入[NSMutableData *frame];

要发送的NSMutableData *frame构建完成以后调用[self _writeData:frame];
要发送的数据会被拼接入_outputBuffer,然后再调用[self _pumpWriting]来向外写.

剩下的工作就交给了:
1.NSOutputStream- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)len;方法;
2.Stream的代理方法- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;及时通知有空闲空间可写(eventCode==NSStreamEventHasSpaceAvailable).

上一篇下一篇

猜你喜欢

热点阅读