美文网首页程序员
WebSocket简介和SocketRocket源码分析

WebSocket简介和SocketRocket源码分析

作者: Mr_Zander | 来源:发表于2018-06-08 21:22 被阅读141次

    之前的文章讲了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>
    1. 不同于其他协议,WebSocket是基于帧的而不是基于流

    2. 字段解释:

      1. FIN: 1 bit 表示是否是当前信息的最后一段
      2. RSV1, RSV2, RSV3: 1 bit 除非双方协商定义了非0值,否则为0
      3. Opcode: 4 bits 定义了数据包的类型,如果不是已定义类型则表示连接出错。定义的数据类型包括: 0x0表示中间数据包;0x1表示text类型数据包;0x2表示binary类型数据包;0x3-7保留;0x8表示连接关闭;0x9表示ping;0xA表示pong;0xB-F保留
      4. MASK:1 bit 表示Payload是否经过掩码处理。MASK如果是1,Masking-Key的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此为是1。
      5. Payload length:7 bit,7+16 bits,or 7+64 bits 表示payload data的长度,如果其值是0-125,则是payload的真实长度;如果是126,则后面的2个字节形成的16bits无符号整型数的值是payload的真实长度;如果是127,则后面8个字节形成的64bits无符号整型数的值是payload的真实长度。
    3. 掩码

      1. 掩码值必须是随机的32-bit值
      2. 掩码值并不会影响Payload data的长度。
      3. 掩码算法:掩码后的第i字节数据 = 掩码前的第i字节的数据 XOR 掩码key的第(i mod 4)字节的数据

    SocketRocket源码解析

    1. 辅助类简单说明

      1. <text>_SRRunLoopThread</text>: 承载和处理所有事件的线程。通过NSRunLoop (SRWebSocket)把这个线程放到了RunLoop里。并且在- (void)main中使用一个while循环使这个线程正常情况下永不停止。
      2. SRIOConsumer和SRIOConsumerPool: 用来处理收到的消息。
    2. 主要方法说明

      1. - (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates 基本初始化方法。初始化了SRSocket类内的几个成员变量。

      2. _SR_commonInit

        1. 判断协议类型
        2. 初始化了_workQueue,这个GCD队列用来处理主要的业务逻辑,包括处理错误、发送内容、关闭连接等。
        3. 初始化_delegateDispatchQueue,这个队列是用来向外发送通知的。我们可以通过- (void)setDelegateOperationQueue:(NSOperationQueue*) queue- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue来自定义这个队列。
      3. _initializeStreams 创建输入/出流

      4. open/openConnection 这两个方法和被他们调用的方法是用来配置并打开流的

      5. didConnect

        1. 构建HTTP Header
        2. 发送HTTP Header
        3. 注册一个接收服务器返回Header信息的监听,并在回调内进行相应处理
      6. - (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream

        1. 这是NSStream的回调方法,输入和输出流的共同回调
        2. NSStreamEventOpenCompleted 连接打开;NSStreamEventHasBytesAvailable 可读取;NSStreamEventHasSpaceAvailable 可写入数据
        3. NSStreamEventOpenCompleted里面的[self _pumpScanner];用来触发第5条中的3,来处理服务器返回的握手Header信息
      7. _pumpWriting

        1. 向输出流写数据[_outputStream write:_outputBuffer.bytes + _outputBufferOffset maxLength:dataLength - _outputBufferOffset]
        2. if (_outputBufferOffset > 4096 && _outputBufferOffset > (_outputBuffer.length >> 1)) { _outputBuffer = [[NSMutableData alloc] initWithBytes:(char *)_outputBuffer.bytes + _outputBufferOffset length:_outputBuffer.length - _outputBufferOffset]; _outputBufferOffset = 0; } 当缓存超过4M时清空
      8. - (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data 把数据组装成符合协议要求的格式。参考“数据帧格式”的图片。

        1. 向frame_buffer中写入fin/opcode字段
        2. 写入mask字段
        3. 根据要发送的数据得到未掩码数据unmasked_payload
        4. 使用int SecRandomCopyBytes(SecRandomRef rnd, size_t count, void *bytes);得到掩码keymask_key
        5. 根据mask_key和unmasked_payload得到掩码后数据并切入frame_buffer
      9. _HTTPHeadersDidFinish 处理服务器返回的握手信息。通过对服务器返回的握手信息的分析来判断连接是否成功打开。

      10. _innerPumpScanner 处理收到的数据,读取未读数据,并且把未读数据通过consumer.handler(self, slice);返回给consumer来解析数据内容。

      11. - (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode 根据opCode对收到的数据进行分类处理

      12. - (void)_readFrameContinue 这个是主要的数据处理方法。整个数据结构的处理的过程就是按照WebSocket协议的规定来的。

    到此,SocketRocket的源码分析就结束了。其中一些地方说的比较啰嗦,有些地方说的可能不够详细。大家在看源码的时候一定要对照着WebSocket的协议内容。如果有不清楚的地方欢迎咨询。有说的不对的地方也欢迎指正。

    祝你早日走上人生巅峰🎉🎉🎉

    原文首发在我的博客

    参考文章

    阮一峰的网络日志
    知乎
    RFC6455 WebSocket Protocol

    相关文章

      网友评论

        本文标题:WebSocket简介和SocketRocket源码分析

        本文链接:https://www.haomeiwen.com/subject/dyhzsftx.html