文章由两部分组成,第一部分通过对API的讲解来对SocketRocket
的使用有个简单的了解。如果对SocketRocket
的实现感兴趣的同学可以着重看看第二部分,在源码解析的过程中会穿插着Websocket
协议的讲解,干货满满。
第一部分 - 简单使用
初始化
如果没有特殊需求,以下就能进行初始化并连接websocket
- (void)connectWithUrl:(NSString *)url {
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",url]]];
self.socket = [[SRWebSocket alloc] initWithURLRequest:request];
self.socket.delegate = self;
[self.socket open];
}
如果需要超时时间,需要在执行open
方法前使用以下设置
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",url]]];
request.timeoutInterval = 30;
如果设置了超时时间,SRWebSocket
在open
方法中会起一个延迟任务,时间为timeoutInterval
,当触发延迟任务时,webSocket
还未连接完成则会报超时错误。
发送消息
通过send
发送消息
[self.socket send:@"Hello!"];
- 如果是文本消息,需要传递NSString格式的数据。
- 如果是二进制消息,需要传递NSSData格式的数据。
- 如果传nil则内部会转成文本消息。
- 其他类型的消息,SRWebSocket不会进行处理。
代理回调
接收到服务端消息时的回调
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSLog(@"接收到消息 - %@", message);
}
message
为NSString
类型或者NSData
类型。- 当为文字消息时,默认情况下
SRWebSocket
会将其转化为NSString
。 - 可以通过
webSocketShouldConvertTextFrameToString
代理修改是否自动转化为NSSting。
webSocket
连接成功
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
NSLog(@"webSocket已经打开");
}
触发此回调时表明webSocket已经连接成功,可以发送和接收消息了。
webSocket
连接,发送或接收消息时失败
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
NSLog(@"didFailWithError - %@", error);
}
webSocket
连接,发送或接收消息时失败
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
NSLog(@"关闭webSocket完成 Code:%ld reason:%@",code,reason);
}
收到Pong
的回调
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload {
NSLog(@"didReceivePong");
}
是否需要将内容转化为字符串
- (BOOL)webSocketShouldConvertTextFrameToString:(SRWebSocket *)webSocket {
return YES;
}
- 默认是
YES
。 - 为
YES
时将服务端发来的文本消息自动转化为NSString
。
第二部分 - 源码解析
WebSocket
建立连接的过程
sequenceDiagram
Client->>Service: Input/OutputStream Socket Connection
Client->>Service: HTTP Request(Connection: Upgrade Upgrade: websocket 发起协议升级请求)
Service-->>Client: HTTP Response (101 Switching Protocols 完成协议升级)
Client-)Service: Websocket Data
Client-)Service: Websocket Data
Service-->>Client: Websocket Data
如上面所描述的WebSocket
建立连接有以下几个过程:
- 客户端绑定数据输入/输出流并与服务端进行Socket连接。
- 首先客户端会向服务端发起HTTP请求,
Request Header
中会有两个字段,Connection: Upgrade
表示需要将协议进行升级,Upgrade: websocket
表明要升级为websocket。 - 服务端响应HTTP请求,返回状态码101表明协议升级完成。
- 之后客户端和服务器通过
WebSocket
协议进行通讯。
接下来我们可以通过以下几个过程为抓手去解读SRWebSocket
源码,其中4和6放在一起来讲。
SRWebSocket
的初始化- 数据输入输出流绑定Socket并进行连接
- 发起请求
HTTP
- 接收
HTTP
请求服务端的响应 - 发送
WebSocket Data
- 接收
WebSocket Data
一. SRWebSocket
的初始化
初始化过程如下
graph TD
initWithURLRequest: --> initWithURLRequest:protocols: --> initWithURLRequest:protocols:allowsUntrustedSSLCertificates: --> _SR_commonInit --> _initializeStreams
构造函数
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols;
- (id)initWithURLRequest:(NSURLRequest *)request;
// Some helper constructors.
- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
- (id)initWithURL:(NSURL *)url protocols:(NSArray *)protocols;
- (id)initWithURL:(NSURL *)url;
所有的构造函数最终都走到initWithURLRequest:protocols:allowsUntrustedSSLCertificates:
中,且其内部只是做一些赋值操作,并且调用_SR_commonInit
函数。
_SR_commonInit
函数
_SR_commonInit
函数由以下几个部分组成:
// 获取协议头
NSString *scheme = _url.scheme.lowercaseString;
// 过滤协议头
assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);
// 如果是wss和https则表识需要使用SSL加密
if ([scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]) {
_secure = YES;
}
可以注意到,SRWebSocket并没有过虑掉http和https,在websocket连接时首先会进行一个http请求,然后再将http升级到websocket,所以此时使用ws或者http是没有区别的。
// 初始化准备状态
_readyState = SR_CONNECTING;
_consumerStopped = YES;
// 设置webSocket的版本
_webSocketVersion = 13;
// 创建串行工作队列
_workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
- (void)assertOnWorkQueue {
assert(dispatch_get_specific((__bridge void *)self) == maybe_bridge(_workQueue));
}
使用dispatch_queue_set_specific
将_workQueue
和当前对象进行绑定,然后通过在assertOnWorkQueue
中
去判断当前执行的任务是否包含在当前工作队列中。
初始化成员变量:
_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];
_initializeStreams
- (void)_initializeStreams {
// 判断端口号是否合法
assert(_url.port.unsignedIntValue <= UINT32_MAX);
uint32_t port = _url.port.unsignedIntValue;
if (port == 0) {
// 如果端口号未进行赋值则进行默认设置
if (!_secure) {
// 如果未加密默认端口号为80,否则为443
port = 80;
} else {
port = 443;
}
}
NSString *host = _url.host;
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
// 创建socket连接并且绑定输入输出流
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
// 把非OC的指针指向OC并且转换成ARC来管理生命周期
_outputStream = CFBridgingRelease(writeStream);
_inputStream = CFBridgingRelease(readStream);
// 输入输出流设置代理
_inputStream.delegate = self;
_outputStream.delegate = self;
}
二. 数据输入输出流绑定Socket并进行连接
graph TD
open --> openConnection --> _updateSecureStreamOptions
openConnection --> scheduleInRunLoop:forMode:
open
函数
- (void)open;
{
assert(_url);
// 如果是正在连接的状态则断言报错,表明之前已经调用过 open
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];
}
_updateSecureStreamOptions
// 如果设置加密
if (_secure) {
NSMutableDictionary *SSLOptions = [[NSMutableDictionary alloc] init];
// kCFStreamPropertySocketSecurityLevel 套接字安全级别属性键
// kCFStreamSocketSecurityLevelNegotiatedSSL 指定将可协商的最高级别安全协议设置为套接字流的安全协议。
[_outputStream setProperty:(__bridge id)kCFStreamSocketSecurityLevelNegotiatedSSL forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
// 如果使用了SSLPinned则设置不需要验证证书,SSLPinne需要再APP中内置证书
if ([_urlRequest SR_SSLPinnedCertificates].count) {
[SSLOptions setValue:@NO forKey:(__bridge id)kCFStreamSSLValidatesCertificateChain];
}
[_outputStream setProperty:SSLOptions
forKey:(__bridge id)kCFStreamPropertySSLSettings];
}
openConnection
- (void)openConnection {
// 更新流的配置
[self _updateSecureStreamOptions];
if (!_scheduledRunloops.count) {
[self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
}
// 打开输入输出流
[_outputStream open];
[_inputStream open];
}
当打开流之后,输入输出流会和指定的host建立连接,当连接成功后Stream会回调代理stream:handleEvent:
,并且此时eventCode
的状态为NSStreamEventOpenCompleted
。
三. 发起HTTP请求
在stream:handleEvent:
中当eventCode
的状态为NSStreamEventOpenCompleted
时会执行didConnect
操作。
stateDiagram-v2
OpenCompleted --> didConnect
OpenCompleted --> _pumpWriting
OpenCompleted --> _pumpScanner
didConnect--> _readHTTPHeader
_readHTTPHeader --> _readUntilHeaderCompleteWithCallback
_readUntilHeaderCompleteWithCallback --> _readUntilByteslengthcallback
_readUntilByteslengthcallback --> _addConsumerWithScannercallbackdataLength
_addConsumerWithScannercallbackdataLength --> _pumpScanner
_pumpScanner --> _innerPumpScanner
didConnect--> _writeData
_writeData--> _pumpWriting
didConnect
解析:
1.设置Header
Websocket
连接时首次发送HTTP
请求时Header
规范如下:
GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 34v76lq3RNcgctw==O6xFTi3
其中头部字端意义如下:
Connection
:Upgrade
表示要升级协议Upgrade
:websocket
表示要升级到websocket
Sec-WebSocket-Version
: 13 表示websocket
的版本,如果服务端不支持该版本,需要返回一个Sec-WebSocket-Version
:header
,里面包含服务端支持的版本号。Sec-WebSocket-Key
对称密钥
2.设置Cookie
NSDictionary * cookies = [NSHTTPCookie requestHeaderFieldsWithCookies:[self requestCookies]];
for (NSString * cookieKey in cookies) {
NSString * cookieValue = [cookies objectForKey:cookieKey];
if ([cookieKey length] && [cookieValue length]) {
CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)cookieKey, (__bridge CFStringRef)cookieValue);
}
}
3.发送请求
//将request序列化为 NSData
NSData *message = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request));
//发送 message 消息
[self _writeData:message];
3.读取响应的http的头部
[self _readHTTPHeader];
_writeData
解析
- (void)_writeData:(NSData *)data {
[self assertOnWorkQueue];
// 如果连接已经关闭则直接返回
if (_closeWhenFinishedWriting) {
return;
}
// 输出缓存拼接需要发送的数据
[_outputBuffer appendData:data];
[self _pumpWriting];
}
_outputBuffer
是存储需要发送数据的缓存区,_outputBufferOffset
是用来记录已发送数据的位置。
_pumpWriting
解析
1.发送数据
// 获取发将要发送数据的长度
NSUInteger dataLength = _outputBuffer.length;
// _outputBufferOffset 记录已经发送数据的偏移量 且 输出流可以被写入
if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) {
// write:maxLength:执行后会返回已被写入数据的大小,被写入的部分会发送给服务器
NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset];
// 当写入大小为-1则表明写入数据失败
if (bytesWritten == -1) {
[self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]];
return;
}
// 记录被加入的数据
_outputBufferOffset += bytesWritten;
// 如果被写入的数据大于4096 且 大于数据总长度的一半 则清除已写入的数据
if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) {
// 重置输出缓存区,释放无用内存
_outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset];
_outputBufferOffset = 0;
}
}
2.当同时满足以下四个条件时会关闭:
- 标记为写完成关闭
- 输出的bufeer - 偏移量 = 0
- 输入流正在打开
- 首次关闭发送
if (_closeWhenFinishedWriting &&
(dispatch_data_get_size(_outputBuffer) - _outputBufferOffset) == 0 &&
(_inputStream.streamStatus != NSStreamStatusNotOpen &&
_inputStream.streamStatus != NSStreamStatusClosed) &&
!_sentClose) {
_sentClose = YES;
@synchronized(self) {
// 关闭输入输出流
[_outputStream close];
[_inputStream close];
// 移除RunLoop
for (NSArray *runLoop in [_scheduledRunloops copy]) {
[self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]];
}
}
if (!_failed) {
// 触发关闭websocket的回调
[self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
if (availableMethods.didCloseWithCode) {
[delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
}
}];
}
// 关闭定时器
[self _scheduleCleanup];
}
四. 接收服务端响应
当InputStream
接收到服务端发来的数据时其代理stream:handleEvent:
中的eventCode
为NSStreamEventHasBytesAvailable
。
graph TD
HasBytesAvailable --> _pumpScanner
_pumpScanner --> _innerPumpScanner
_innerPumpScanner --> consumer.consumer
consumer.consumer --> _readUntilByteslengthcallback
_innerPumpScanner --> consumer.handler
consumer.handler --> _HTTPHeadersDidFinish
_HTTPHeadersDidFinish --> _readFrameNew
_readFrameNew --> _readFrameContinue
_readFrameContinue --> _handleFrameHeader.curData
_handleFrameHeader.curData --> _handleFrameWithData.opCode
_handleFrameHeader.curData --> _readFrameContinue
_handleFrameWithData.opCode --> _readFrameContinue
stream:handleEvent:
中eventCode
为NSStreamEventHasBytesAvailable
时代码如下:
const int bufferSize = 2048;
uint8_t buffer[bufferSize];
//如果有可读字节
while (_inputStream.hasBytesAvailable) {
// 读取数据,读取2048
NSInteger bytes_read = [_inputStream read:buffer maxLength:bufferSize];
// bytes_read为真正读取数据的大小
if (bytes_read > 0) {
// 拼接数据
[_readBuffer appendBytes:buffer length:bytes_read];
} else if (bytes_read < 0) {
// bytes_read为-1时代表读取失败
[self _failWithError:_inputStream.streamError];
}
//如果读取的不等于最大的,说明读完了,跳出循环
if (bytes_read != bufferSize) {
break;
}
};
//开始扫描,看消费者什么时候消费数据
[self _pumpScanner];
_pumpScanner
解析
- (void)_pumpScanner {
[self assertOnWorkQueue];
// 如果正在进行扫描则直接返回
if (!_isPumping) {
_isPumping = YES;
} else {
return;
}
// 当无消费者,且读缓存区已经无数据时结束循环
while ([self _innerPumpScanner]) {
}
_isPumping = NO;
}
_innerPumpScanner解析
1.判断当前读缓存区里的数据是否已经处理完成,如果处理完成则直接返回。
BOOL didWork = NO;
// 当处于关闭关闭状态时直接返回NO
if (self.readyState >= SR_CLOSED) {
return didWork;
}
// 如果消费者为空直接返回NO
if (!_consumers.count) {
return didWork;
}
// 获取读缓冲区的大小
size_t readBufferSize = dispatch_data_get_size(_readBuffer);
// 如果缓冲区无数据则直接返回NO
size_t curSize = readBufferSize - _readBufferOffset;
if (!curSize) {
return didWork;
}
2.从读缓存中获取本次需要处理的数据
// 获取第一个消费者
SRIOConsumer *consumer = [_consumers objectAtIndex:0];
size_t bytesNeeded = consumer.bytesNeeded;
size_t foundSize = 0;
// 如果消费者的consumer回调存在则触发consumer回调获取匹配数据的大小
if (consumer.consumer) {
// 获取还未读取的数据并将其转化为NSData
NSData *subdata = (NSData *)dispatch_data_create_subrange(_readBuffer, _readBufferOffset, readBufferSize - _readBufferOffset);
// 获取获取匹配的数据大小
foundSize = consumer.consumer(subdata);
} else {
assert(consumer.bytesNeeded);
// 如果当前剩余的数据大于消费者所需要的字节数,则此次读取消费者所需要的字节数的数据
if (curSize >= bytesNeeded) {
foundSize = bytesNeeded;
} else if (consumer.readToCurrentFrame) {
// 如果当前剩余的数据小于消费者所需要的字节数的数据
foundSize = curSize;
}
}
SRWebSocket
中只有注册header
的消费者时会在consumer.consumer
添加回调函数,其具体代码在下面_readUntilBytes:length:callback:
中。
3.从读缓存中读取本次需要处理的数据
//当消费者读取到当前帧或者找到Size
if (consumer.readToCurrentFrame || foundSize) {
// 获取未处理的切片数据
dispatch_data_t slice = dispatch_data_create_subrange(_readBuffer, _readBufferOffset, foundSize);
// 记录读取到的数据
_readBufferOffset += foundSize;
// 当已处理的数据大于默认Buffer的阈值且大于已读取数据的一半
if (_readBufferOffset > SRDefaultBufferSize() && _readBufferOffset > readBufferSize / 2) {
// 重新创建已读数据,释放已处理的数据
_readBuffer = dispatch_data_create_subrange(_readBuffer, _readBufferOffset, readBufferSize - _readBufferOffset);
_readBufferOffset = 0;
}
// 是否要对数据载荷进行反掩码操作
if (consumer.unmaskBytes) {
// 复制当前切片数据
__block NSMutableData *mutableSlice = [slice mutableCopy];
NSUInteger len = mutableSlice.length;
uint8_t *bytes = mutableSlice.mutableBytes;
// 对当前数据的每一个字节进行反掩码操作
for (NSUInteger i = 0; i < len; i++) {
bytes[i] = bytes[i] ^ _currentReadMaskKey[_currentReadMaskOffset % sizeof(_currentReadMaskKey)];
_currentReadMaskOffset += 1;
}
// 重置切片数据,清除原始数据
slice = dispatch_data_create(bytes, len, nil, ^{
mutableSlice = nil;
});
}
// 是否读取当前帧
if (consumer.readToCurrentFrame) {
// 遍历切片数据对象的各个内存
dispatch_data_apply(slice, ^bool(dispatch_data_t region, size_t offset, const void *buffer, size_t size) {
// 将切片中的数据拼接在当前帧数据上
[_currentFrameData appendBytes:buffer length:size];
return true;
});
//在SRWebSocket中未发现具体使用
_readOpCount += 1;
// 如果当前帧是文本格式数据
if (_currentFrameOpcode == SROpCodeTextFrame) {
// 验证数据是否是UTF8
size_t currentDataSize = _currentFrameData.length;
if (_currentFrameOpcode == SROpCodeTextFrame && currentDataSize > 0) {
size_t scanSize = currentDataSize - _currentStringScanPosition;
NSData *scan_data = [_currentFrameData subdataWithRange:NSMakeRange(_currentStringScanPosition, scanSize)];
int32_t valid_utf8_size = validate_dispatch_data_partial_string(scan_data);
if (valid_utf8_size == -1) {
[self closeWithCode:SRStatusCodeInvalidUTF8 reason:@"Text frames must be valid UTF-8"];
dispatch_async(_workQueue, ^{
[self closeConnection];
});
return didWork;
} else {
_currentStringScanPosition += valid_utf8_size;
}
}
}
// 消费者需要处理的数据减去此次处理的数据
consumer.bytesNeeded -= foundSize;
// 触发回调函数handler
// 当消费者处理完成数据则移除消费者数据并将其放入消费者缓存池
if (consumer.bytesNeeded == 0) {
[_consumers removeObjectAtIndex:0];
consumer.handler(self, nil);
[_consumerPool returnConsumer:consumer];
didWork = YES;
}
} else if (foundSize) {
// 如果没有读取当前帧
// 触发回调函数handler
// 当消费者处理完成数据则移除消费者数据并将其放入消费者缓存池
[_consumers removeObjectAtIndex:0];
consumer.handler(self, (NSData *)slice);
[_consumerPool returnConsumer:consumer];
didWork = YES;
}
}
_readUntilBytes:length:callback:
解析
注册consumer
回调,此回调的作用是扫描接收的data
数据,识别header
并将其大小返回。
因为每个header
都以\r\n
结尾,并且最后一行加上一个额外的空行\r\n
,所以只要匹配了{'\r', '\n', '\r', '\n'}
则就代表是响应头。
static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'};
- (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 {
// bytes = CRLFCRLFBytes length = CRLFCRLFBytes中字节的数量
stream_scanner 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;
// 将data与边界符 CRLFCRLFBytes进行匹配,将完全匹配后找到的数据大小返回
// 遍历data中所有的字节
for (size_t i = 0; i < size; i++ ) {
// 如果data当前字节和CRLFCRLFBytes的当前字节相同则匹配度加1
if (((const unsigned char *)buffer)[i] == ((const unsigned char *)bytes)[match_count]) {
// 匹配度加1
match_count += 1;
// 如果匹配度等于CRLFCRLFBytes的长度则返回
if (match_count == length) {
found_size = i + 1;
break;
}
} else {
// 如果不相同则匹配度清0重新匹配
match_count = 0;
}
}
return found_size;
};
[self _addConsumerWithScanner:consumer callback:dataHandler];
}
_HTTPHeadersDidFinish
通过HTTP Response Header来判定升级为WebSocket协议是否成功,如果成功则通过_readFrameContinue解析数据。
_readFrameContinue
解析
_readFrameContinue
中首会添加一个consumer来处理WebSocket数据中的frame_header。
在解析源码前先简单了解一下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
: 1个比特。
- 等于1表明这是消息的最后一个分片
- 等于0表明这不是消息的最后一个分片
Opcode
: 4个比特。Opcode的值决定了应该如何解析后续的数据。如果操作代码是不认识的,那么接收端应该断开连接。
%x0
:表示一个延续帧。当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。%x1
:表示这是一个文本帧%x2
:表示这是一个二进制帧(%x3-7
:保留的操作代码,用于后续定义的非控制帧。%x8
:表示连接断开。%x9
:表示这是一个ping操作。%xA
:表示这是一个pong操作。%xB-F
:保留的操作代码,用于后续定义的控制帧。
Mask
:标识是否对数据进行掩码操作。从客户端向服务端发送消息需要掩码操作,服务端向客户端发送消息不需要掩码操作,当服务端接收到消息如果没有进行掩码操作,服务端需要断开连接。在进行掩码操作操作时在Masking-key
中会定义一个掩码键,并通过掩码键来对数据进行反掩码。
Payload length
:数据的长度
Masking-key
:掩码键,32位的随机数
[self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) {
// 初始化 frame_header
__block frame_header header = {0};
const uint8_t *headerBuffer = data.bytes;
assert(data.length >= 2);
// 如果服务端使用了RSV则报错
if (headerBuffer[0] & SRRsvMask) {
[self _closeWithProtocolError:@"Server used RSV bits"];
return;
}
// 获取Opcode
uint8_t receivedOpcode = (SROpCodeMask & headerBuffer[0]);
// 判断帧类型,是否是指定的控制帧
BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose);
// 当Opcode为0时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
// 如果不是指定帧,且未采用数据分片,而且_currentFrameCount消息帧大于0,错误关闭
if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) {
[self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"];
return;
}
// 如果采用数据分片 且 _currentFrameCount数量等于0则报错
if (receivedOpcode == 0 && self->_currentFrameCount == 0) {
[self _closeWithProtocolError:@"cannot continue a message"];
return;
}
// 如果采用数据分片,继续使用上一个切片的opcode
header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode;
// 得到fin
header.fin = !!(SRFinMask & headerBuffer[0]);
// 得到Mask
header.masked = !!(SRMaskMask & headerBuffer[1]);
// 得到数据长度
header.payload_length = SRPayloadLenMask & headerBuffer[1];
headerBuffer = NULL;
// 如果是带掩码的,则报错
if (header.masked) {
[self _closeWithProtocolError:@"Client must receive unmasked data"];
}
// 如果需要反则加上MaskKey的长度
size_t extra_bytes_needed = header.masked ? sizeof(_currentReadMaskKey) : 0;
// 累加 payload_data的长度
if (header.payload_length == 126) {
extra_bytes_needed += sizeof(uint16_t);
} else if (header.payload_length == 127) {
extra_bytes_needed += sizeof(uint64_t);
}
// 如果如果‘扩展数据长度’不为空则需要通过计算‘扩展数据长度’的大小来判断Masking-key的位置
if (extra_bytes_needed == 0) {
[self _handleFrameHeader:header curData:self->_currentFrameData];
} else {
// 注册消费者 获取 Masking-key
[self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) {
// ...
size_t mapped_size = data.length;
#pragma unused (mapped_size)
const void *mapped_buffer = data.bytes;
size_t offset = 0;
// 获取‘扩展数据长度’的大小
if (header.payload_length == 126) {
// 如果payload_length为126,则mapped_buffer代表一个16位的无符号整数
assert(mapped_size >= sizeof(uint16_t));
uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer));
header.payload_length = newLen;
offset += sizeof(uint16_t);
} else if (header.payload_length == 127) {
// 如果payload_length为127,则mapped_buffer代表一个64位的无符号整数
assert(mapped_size >= sizeof(uint64_t));
header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer));
offset += sizeof(uint64_t);
} else {
assert(header.payload_length < 126 && header.payload_length >= 0);
}
// 如果带有掩码则需要进行反掩码
if (header.masked) {
assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset);
memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey));
}
[self _handleFrameHeader:header curData:self->_currentFrameData];
} readToCurrentFrame:NO unmaskBytes:NO];
}
} readToCurrentFrame:NO unmaskBytes:NO];
_handleFrameHeader:curData:
处理帧数据的Header
...
// 如果不是控制帧
if (!isControlFrame) {
// 记录当前帧的opcode
_currentFrameOpcode = frame_header.opcode;
// 累加帧数量
_currentFrameCount += 1;
}
// 如果数据长度为0
if (frame_header.payload_length == 0) {
if (isControlFrame) {
// 如果是控制帧则处理帧数据
[self _handleFrameWithData:curData opCode:frame_header.opcode];
} else {
// 如果当前帧是最后一个切片则处理帧数据
if (frame_header.fin) {
[self _handleFrameWithData:_currentFrameData opCode:frame_header.opcode];
} else {
// 如果当前帧不是最后一个切片则继续读取数据
[self _readFrameContinue];
}
}
} else {
// 添加consumer,去读取Payload Data
[self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) {
if (isControlFrame) {
[self _handleFrameWithData:newData 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];
}
_handleFrameWithData:opCode:
只是对数据的处理,逻辑比较简单,在这里就不多做描述了
五. 发送WebSocket Data
graph TD
send: --> sendData: --> sendDataNoCopy:--> _sendFrameWithOpcode:data:
send: --> sendString: --> _sendFrameWithOpcode:data: --> _writeData: --> _pumpWriting
_sendFrameWithOpcode
发送帧数据
// 预留32个字节的空间填充WebSocket协议中需要的字段
static const size_t SRFrameHeaderOverhead = 32;
size_t payloadLength = data.length;
// 创建帧数据
NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead];
// 当内存不足时会创建失败
if (!frameData) {
// 创建失败后触发失败回调
[self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"];
return;
}
// 获取数据指针
uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes;
// 设置 fin 和 opcode
frameBuffer[0] = SRFinMask | opCode;
// 对数据载荷进行掩码操作
frameBuffer[1] |= SRMaskMask;
size_t frameBufferSize = 2;
// 数据载荷的长度小于126时直接赋值
if (payloadLength < 126) {
frameBuffer[1] |= payloadLength;
} else {
uint64_t declaredPayloadLength = 0;
size_t declaredPayloadLengthSize = 0;
if (payloadLength <= UINT16_MAX) {
frameBuffer[1] |= 126;
// 后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。
declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength);
declaredPayloadLengthSize = sizeof(uint16_t);
} else {
frameBuffer[1] |= 127;
// 后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。
declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength);
declaredPayloadLengthSize = sizeof(uint64_t);
}
// 将declaredPayloadLength拷贝在frameBuffer的后两位的内存中
memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize);
frameBufferSize += declaredPayloadLengthSize;
}
const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes;
// 创建maskKey(32位的随机数)
uint8_t *maskKey = frameBuffer + frameBufferSize;
size_t randomBytesSize = sizeof(uint32_t);
int result = SecRandomCopyBytes(kSecRandomDefault, randomBytesSize, maskKey);
if (result != 0) {
}
frameBufferSize += randomBytesSize;
// 将数据进行掩码操作
uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize;
memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength);
SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey);
frameBufferSize += payloadLength;
assert(frameBufferSize <= frameData.length);
frameData.length = frameBufferSize;
[self _writeData:frameData];
_sendFrameWithOpcode
发送帧数据
// 预留32个字节的空间填充WebSocket协议中需要的字段
static const size_t SRFrameHeaderOverhead = 32;
size_t payloadLength = data.length;
// 创建帧数据
NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead];
// 当内存不足时会创建失败
if (!frameData) {
// 创建失败后触发失败回调
[self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"];
return;
}
// 获取数据指针
uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes;
// 设置 fin 和 opcode
frameBuffer[0] = SRFinMask | opCode;
// 对数据载荷进行掩码操作
frameBuffer[1] |= SRMaskMask;
size_t frameBufferSize = 2;
// 数据载荷的长度小于126时直接赋值
if (payloadLength < 126) {
frameBuffer[1] |= payloadLength;
} else {
uint64_t declaredPayloadLength = 0;
size_t declaredPayloadLengthSize = 0;
if (payloadLength <= UINT16_MAX) {
frameBuffer[1] |= 126;
// 后续2个字节代表一个16位的无符号整数,该无符号整数的值为数据的长度。
declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength);
declaredPayloadLengthSize = sizeof(uint16_t);
} else {
frameBuffer[1] |= 127;
// 后续8个字节代表一个64位的无符号整数(最高位为0),该无符号整数的值为数据的长度。
declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength);
declaredPayloadLengthSize = sizeof(uint64_t);
}
// 将declaredPayloadLength拷贝在frameBuffer的后两位的内存中
memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize);
frameBufferSize += declaredPayloadLengthSize;
}
const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes;
// 创建maskKey(32位的随机数)
uint8_t *maskKey = frameBuffer + frameBufferSize;
size_t randomBytesSize = sizeof(uint32_t);
int result = SecRandomCopyBytes(kSecRandomDefault, randomBytesSize, maskKey);
if (result != 0) {
}
frameBufferSize += randomBytesSize;
// 将数据进行掩码操作
uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize;
memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength);
SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey);
frameBufferSize += payloadLength;
assert(frameBufferSize <= frameData.length);
frameData.length = frameBufferSize;
[self _writeData:frameData];
Other
常驻线程
为了防止影响主线程的性能,SRWebSocket
将Input/OutputStream
注册到子线程的RunLoop
中。
+ (NSRunLoop *)SR_networkRunLoop {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 开辟一个子线程
networkThread = [[_SRRunLoopThread alloc] init];
networkThread.name = @"com.squareup.SocketRocket.NetworkThread";
[networkThread start];
// 获取个子线程的Runloop
networkRunLoop = networkThread.runLoop;
});
return networkRunLoop;
}
- (id)init {
self = [super init];
if (self) {
// 创建一个group
_waitGroup = dispatch_group_create();
// 等待的group里 + 1
dispatch_group_enter(_waitGroup);
}
return self;
}
// 线程的执行
- (void)main {
@autoreleasepool {
// 获取当前线程的runloop
_runLoop = [NSRunLoop currentRunLoop];
// 开始执行,group - 1
dispatch_group_leave(_waitGroup)
// 创建一个空的runloop SourceContext,防止runloop退出
CFRunLoopSourceContext sourceCtx = {
.version = 0,
.info = NULL,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
.equal = NULL,
.hash = NULL,
.schedule = NULL,
.cancel = NULL,
.perform = NULL
};
// 转化为source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx);
// 添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
// 一直让runloop运行在 NSDefaultRunLoopMode模式下
while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
assert(NO);
}
}
- (NSRunLoop *)runLoop;
{
// 阻塞,直到main中获取到当前RunLoop
dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
return _runLoop;
}