之前的文章讲了SocketRocket的用法,这里讲一下什么是WebSocket并且对SocketRocket的源码进行分析。
WebSocket
The WebSocket Protocol is an independent TCP-based protocol.<br>Its only relationship to HTTP is that its handshake is interpreted by HTTP servers as an Upgrade request.
<div align=center>
<img src="http://image.iosprogrammer.hongbility.com/WebSocket-SocketRocket-Source/6651f2f811ec133b0e6d7e6d0e194b4c_hd.jpg">
</div>
WebSocket的特点:
1. 建立在 TCP 协议之上,服务器端的实现比较容易。
2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3. 数据格式比较轻量,性能开销小,通信高效。
4. 可以发送文本,也可以发送二进制数据。
5. 没有同源限制,客户端可以与任意服务器通信。
6. 协议标识符是ws(如果加密,则为wss),服务器网址就是URL。`ws://example.com:80/some/path`
7. 全双工通信。
WebSocket握手类似于HTTP:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
Upgrade和Connection是在通知Apache或者Nginx等代理服务器,正在进行的是websocket协议连接而不是普通的HTTP。
Sec-WebSocket-Key是浏览器随机生成的base64编码值,用来验证服务器是不是真正的websocket。
Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同URL下,不同的服务所需要的协议。
Sec-WebSocket-Version 是告诉服务器所使用的协议版本。
服务器验证通过后会返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
Upgrade和Connection的作用同样是为了表明进行的是websocket连接。
Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key,用来给客户端验证身份。
Sec-WebSocket-Protocol 则是表示最终使用的协议。
数据帧:
<div align=center>
<font color="gray" size="2">数据帧格式</font>
</div>
不同于其他协议,WebSocket是基于帧的而不是基于流
-
字段解释:
- FIN: 1 bit 表示是否是当前信息的最后一段
- RSV1, RSV2, RSV3: 1 bit 除非双方协商定义了非0值,否则为0
- Opcode: 4 bits 定义了数据包的类型,如果不是已定义类型则表示连接出错。定义的数据类型包括: 0x0表示中间数据包;0x1表示text类型数据包;0x2表示binary类型数据包;0x3-7保留;0x8表示连接关闭;0x9表示ping;0xA表示pong;0xB-F保留
- MASK:1 bit 表示Payload是否经过掩码处理。MASK如果是1,Masking-Key的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此为是1。
- Payload length:7 bit,7+16 bits,or 7+64 bits 表示payload data的长度,如果其值是0-125,则是payload的真实长度;如果是126,则后面的2个字节形成的16bits无符号整型数的值是payload的真实长度;如果是127,则后面8个字节形成的64bits无符号整型数的值是payload的真实长度。
-
掩码
- 掩码值必须是随机的32-bit值
- 掩码值并不会影响Payload data的长度。
- 掩码算法:掩码后的第i字节数据 = 掩码前的第i字节的数据 XOR 掩码key的第(i mod 4)字节的数据
SocketRocket源码解析
-
辅助类简单说明
- <text>_SRRunLoopThread</text>: 承载和处理所有事件的线程。通过
NSRunLoop (SRWebSocket)
把这个线程放到了RunLoop里。并且在- (void)main
中使用一个while循环使这个线程正常情况下永不停止。 - SRIOConsumer和SRIOConsumerPool: 用来处理收到的消息。
- <text>_SRRunLoopThread</text>: 承载和处理所有事件的线程。通过
-
主要方法说明
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
基本初始化方法。初始化了SRSocket类内的几个成员变量。-
_SR_commonInit
- 判断协议类型
- 初始化了_workQueue,这个GCD队列用来处理主要的业务逻辑,包括处理错误、发送内容、关闭连接等。
- 初始化_delegateDispatchQueue,这个队列是用来向外发送通知的。我们可以通过
- (void)setDelegateOperationQueue:(NSOperationQueue*) queue
或- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue
来自定义这个队列。
_initializeStreams
创建输入/出流open
/openConnection
这两个方法和被他们调用的方法是用来配置并打开流的-
didConnect
- 构建HTTP Header
- 发送HTTP Header
- 注册一个接收服务器返回Header信息的监听,并在回调内进行相应处理
-
- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream
- 这是NSStream的回调方法,输入和输出流的共同回调
-
NSStreamEventOpenCompleted
连接打开;NSStreamEventHasBytesAvailable
可读取;NSStreamEventHasSpaceAvailable
可写入数据 - 在
NSStreamEventOpenCompleted
里面的[self _pumpScanner];
用来触发第5条中的3,来处理服务器返回的握手Header信息
-
_pumpWriting
- 向输出流写数据
[_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]
-
if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; _outputBufferOffset = 0; }
当缓存超过4M时清空
- 向输出流写数据
-
- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data
把数据组装成符合协议要求的格式。参考“数据帧格式”的图片。- 向frame_buffer中写入fin/opcode字段
- 写入mask字段
- 根据要发送的数据得到未掩码数据
unmasked_payload
- 使用
int SecRandomCopyBytes(SecRandomRef rnd, size_t count, void *bytes);
得到掩码keymask_key
- 根据mask_key和unmasked_payload得到掩码后数据并切入frame_buffer
_HTTPHeadersDidFinish
处理服务器返回的握手信息。通过对服务器返回的握手信息的分析来判断连接是否成功打开。_innerPumpScanner
处理收到的数据,读取未读数据,并且把未读数据通过consumer.handler(self, slice);
返回给consumer来解析数据内容。- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode
根据opCode对收到的数据进行分类处理- (void)_readFrameContinue
这个是主要的数据处理方法。整个数据结构的处理的过程就是按照WebSocket协议的规定来的。
到此,SocketRocket的源码分析就结束了。其中一些地方说的比较啰嗦,有些地方说的可能不够详细。大家在看源码的时候一定要对照着WebSocket的协议内容。如果有不清楚的地方欢迎咨询。有说的不对的地方也欢迎指正。
祝你早日走上人生巅峰🎉🎉🎉
原文首发在我的博客