简介:
SocketRocket库由Facebook开发的WebSocket 客户端库,用于 iOS 和 macOS 上的 Objective-C 应用。它采用了标准的 RFC6455 协议规范,支持安全的 wss 连接,同时也能对付网络环境下的各种不可靠场景。
WebSocket 是一种网络通信协议,提供了全双工通信通道。它允许服务器主动向客户端推送信息,而传统的 HTTP 请求只允许客户端发送请求给服务器。
SocketRocket 的特性如下:
- 完全符合 RFC6455 协议规范。
- 支持 wss: / https: URL 方案,以进行加密连接。
- 对可能出现的历史操作进行了良好的测试和精心的处理。
- 支持发送 pings 和 pongs(WebSocket 控制消息)。
- 以流方式处理数据,节省内存开支。
使用
NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:url];
self.webSocket = [[SRWebSocket alloc] initWithURLRequest:request];
self.webSocket.delegate = self;
self.webSocket.requestCookies = cookies;
// Open connection
[self.webSocket open];
然后处理回调
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code {}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {}
源码分析
你可以通过readystate获取当前socket的状态
typedef NS_ENUM(NSInteger, SRReadyState) {
SR_CONNECTING = 0,
SR_OPEN = 1,
SR_CLOSING = 2,
SR_CLOSED = 3,
};
@interface SRWebSocket : NSObject <NSStreamDelegate>
@property (nonatomic, readonly) SRReadyState readyState;
@end
注意:SocketRocket在socket通道建立后,不会帮助你自动发送心跳包,这需要你自己把握它的状态
在_SR_commonInit函数中,初始化_readBuffer、_outputBuffer、_currentFrameData、_consumers、_consumerPool、_scheduledRunloops,以及完成输入输出流在port上的数据绑定
- (void)_initializeStreams;
{
assert(_url.port.unsignedIntValue <= UINT32_MAX);
uint32_t port = _url.port.unsignedIntValue;
if (port == 0) {
if (!_secure) {
port = 80;
} else {
port = 443;
}
}
NSString *host = _url.host;
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
_outputStream = CFBridgingRelease(writeStream);
_inputStream = CFBridgingRelease(readStream);
_inputStream.delegate = self;
_outputStream.delegate = self;
}
数据流完成绑定后,去看看stream流的处理
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
{
__weak typeof(self) weakSelf = self;
//这个if下面是ssl握手的鉴权过程,_pinnedCertFound标记了ssl握手的完成
if (_secure && !_pinnedCertFound && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) {
// SR_SSLPinnedCertificates是对外暴露的catory属性,你可以在外部赋值
NSArray *sslCerts = [_urlRequest SR_SSLPinnedCertificates];
if (sslCerts) {
// 如果你不需要保证连接的安全性,没有设置SR_SSLPinnedCertificates,就不会走到这里
// 这里是server传过来的证书
SecTrustRef secTrust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust];
if (secTrust) {
NSInteger numCerts = SecTrustGetCertificateCount(secTrust);
for (NSInteger i = 0; i < numCerts && !_pinnedCertFound; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(secTrust, i);
NSData *certData = CFBridgingRelease(SecCertificateCopyData(cert));
for (id ref in sslCerts) {
SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));
if ([trustedCertData isEqualToData:certData]) {
// 在server提供的证书里找到与客户端SSL证书匹配的,此时连接有效,通信正式开始
_pinnedCertFound = YES;
break;
}
}
}
}
if (!_pinnedCertFound) {
dispatch_async(_workQueue, ^{
// ssl证书失效,连接关闭
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"Invalid server cert" };
[weakSelf _failWithError:[NSError errorWithDomain:@"org.lolrus.SocketRocket" code:23556 userInfo:userInfo]];
});
return;
} else if (aStream == _outputStream) {
dispatch_async(_workQueue, ^{
//
[self didConnect];
});
}
}
}
dispatch_async(_workQueue, ^{
[weakSelf safeHandleEvent:eventCode stream:aStream];
});
}
继续解读connect,还记得SSL握手过程吗?确定证书后要干啥?对滴!对称加密的密钥!SecRandomCopy随机确定secKey,将其作为双方通信的密钥
NSMutableData *keyBytes = [[NSMutableData alloc] initWithLength:16];
SecRandomCopyBytes(kSecRandomDefault, keyBytes.length, keyBytes.mutableBytes);
if ([keyBytes respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
_secKey = [keyBytes base64EncodedStringWithOptions:0];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_secKey = [keyBytes base64Encoding];
#pragma clang diagnostic pop
}
然后就是把一堆东西,包括上门的secKey塞到http header里,这样,客户端拼出了一个NSData
- (void)_writeData:(NSData *)data;
{
[self assertOnWorkQueue];
if (_closeWhenFinishedWriting) {
return;
}
// 先把数据写到outputBuffer里
[_outputBuffer appendData:data];
[self _pumpWriting];
}
- (void)_pumpWriting;
{
// 注意:readBuffer对应inputStream,outputBuffer对应outputStream
[self assertOnWorkQueue];
NSUInteger dataLength = _outputBuffer.length;
if (dataLength - _outputBufferOffset > 0 && _outputStream.hasSpaceAvailable) {
// outputStream仍有富余空间
// _outputBufferOffset是因为buffer里的数据可能太大,要分片发送
NSInteger bytesWritten = [_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset];
if (bytesWritten == -1) {
[self _failWithError:[NSError errorWithDomain:SRWebSocketErrorDomain code:2145 userInfo:[NSDictionary dictionaryWithObject:@"Error writing to stream" forKey:NSLocalizedDescriptionKey]]];
return;
}
_outputBufferOffset += bytesWritten;
if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) {
_outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset];
_outputBufferOffset = 0;
}
}
if (_closeWhenFinishedWriting &&
_outputBuffer.length - _outputBufferOffset == 0 &&
(_inputStream.streamStatus != NSStreamStatusNotOpen &&
_inputStream.streamStatus != NSStreamStatusClosed) &&
!_sentClose) {
// 如果接到关闭frame,且没有待发送的数据了,就关闭全双工通道
_sentClose = YES;
@synchronized(self) {
[_outputStream close];
[_inputStream close];
for (NSArray *runLoop in [_scheduledRunloops copy]) {
[self unscheduleFromRunLoop:[runLoop objectAtIndex:0] forMode:[runLoop objectAtIndex:1]];
}
}
if (!_failed) {
[self _performDelegateBlock:^{
if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
[self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
}
}];
}
[self _scheduleCleanup];
}
}
上面介绍了发送数据,来看看接收数据
下面看就是在往readBuffer里灌数据
case NSStreamEventHasBytesAvailable: {
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;
}
-(void)_pumpScanner;
{
[self assertOnWorkQueue];
if (!_isPumping) {
_isPumping = YES;
} else {
return;
}
while ([self _innerPumpScanner]) {
}
_isPumping = NO;
}
read操作就出现consumer了
整个读出数据分为两步,解析header和解析body
无论是header解析还是body解析,都要先进consumerPool,再做解析
关闭
当读到空包时,textFrame为nil,或者opcode明确被告知是close帧时,调用栈依次是
closeConnection->_pumpWriting->websocketClose回调
需要注意的是,webClose回调是在_pumpWriting中出现的,也就是扫一遍有没有待发送的数据,才会决定是否关闭