iOS 移动开发网络 part6:SRWebSocket
本文在笔者阅读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
.wss
与https
都建立在TLS
的基础上.SRWebSocket
本身就有完成自签名证书认证的代码,我们需要做的只是把证书设置到NSMutableURLRequest
的SR_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
简单讨论起见,我们假定数据都呈单帧,即帧内FIN都为1.多帧数据我们后面再讨论.
- opcode
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
特殊说明:前面已经说过
Mask
设置为1,Masking-key
就为16位.但WebSocket
规定客户端给服务端发的必须是masked data
,而服务端给客户端发必须是unmasked data
.这是为什么?请戳(文中大体想表述的是客户端发masked data
给服务端是为了防止服务器被攻击)
也就是我解读的SRWebSocket
做为客户端读取数据时不用考虑masked data
这种情况的.写数据的时候要考虑masked data
的情况,我们后面会说.
- Payload len
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;
}
![](https://img.haomeiwen.com/i1691771/8dfa775966b20fba.png)
SRWebSocket
读的流程可以大体总结为上图,由于方法
与block
的嵌套关系不能在图上很好的表达,所以真的只是大体呈现
.
对比
CocoaAsyncSocket的read
和SRWebSocket的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)
.