WebSocket简介和SocketRocket源码分析

之前的文章讲了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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容