美文网首页iOS 移动端开发程序员iOS开发
iOS WebSocket(STOMP协议)使用对接

iOS WebSocket(STOMP协议)使用对接

作者: Miaoz0070 | 来源:发表于2018-03-27 18:16 被阅读1387次

    本片我们说下WebSocket,之前项目中有几个轮询的情况,使用基于http协议的接口,每隔几秒调用一下,感觉有点浪费资源。Http默认是短连接,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接客户端主动请求,请求过后关闭,这样每隔几秒钟都要去进行三次握手,请求打开-关闭操作很浪费资源。其实也可以使用Http的长连接来实现但是也有弊端总会造成双方资源的浪费,还有也牵扯H5浏览器,所以经过调研,觉得使用WebSocket来处理轮询的接口。
    首先大家要知道什么是协议呢?我们只有了解了网络协议才能对我们接下来的内容理解有帮助,才不会迷茫,那么我们通过一张图来说明下


    你不懂我,我不懂你

    这就是两个人不在同一频道,各自说各自的,也不懂的对方的,协议可以说是规则。

    网络协议:计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流。它的三要素是:语法、语义、时序。

    首先我们先来了解几个概念:

    Http协议:HTTP属于应用层协议,HTTP的长连接和短连接本质上是TCP长连接和短连接。
    TCP协议:TCP协议属于传输层,TCP协议是可靠的、面向连接的。主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP连接的建立依靠“三次握手”,而释放则需要“四次握手”。
    IP协议:IP协议属于网络层,主要解决网络路由和寻址问题。
    WebSocket协议:也是基于TCP的长连接,目的就是解决网络传输中的双向通信的问题,就是取代Http在双向通信场景下的使用,而且它的实现方式有些也是基于HTTP的(WS的默认端口是80和443),WebSocket协议有两部分组成:握手和数据传输。Websocket是一个持久化的协议,相对于HTTP这种是非持久的协议。Http协议是被动性的,也就是只能客户端发起。
    Http协议双向通讯解决方案:

    1.轮询(polling),轮询就会造成对网络和通信双方的资源的浪费,且非实时。
    2.长轮询,客户端发送一个超时时间很长的Request,服务器hold住这个连接,在有新数据到达时返回Response,相比#1,占用的网络带宽少了,其他类似。
    3.长连接,这里讲的其实是HTTP的长连接,本质上还是Request/Response消息对,仍然会造成资源的浪费、实时性不强等问题。
    *长连接短连接操作过程
    短连接的操作步骤是:
    建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接
    长连接的操作步骤是:
    建立连接——数据传输...(保持连接)...数据传输——关闭连接
    上面我们对一些协议做了了解。

    以上的使用Http方案会有弊端,这里我们使用WebSocket协议来替代轮询的
    *还有一点需要说明的地方就是WebSocket与Socket是没什么关系的,WebSocket协议和Http协议是在应用层,二Socket是对TCP/IP 协议的封装,也就是只是接口(类似于得底层的封装),让你在使用的时候更方便操作。

    到这里我们对网络七层概念、及各个协议在哪个层和他们之间的协同工作原理有了了解。这样才会让我们去使用它们的时候会更高效。

    *我们借用知乎上大佬的例子来理解下WebSocket执行工作原理:

    ----首先,被动性,当服务器完成协议升级后(HTTP->Websocket),服务端就可以主动推送信息给客户端啦。所以上面的情景可以做如下修改。
    客户端:啦啦啦,我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)
    服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)
    客户端:麻烦你有信息的时候推送给我噢。。
    服务端:ok,有的时候会告诉你的。
    服务端:balabalabalabala
    服务端:balabalabalabala
    服务端:哈哈哈哈哈啊哈哈哈哈
    服务端:笑死我了哈哈哈哈哈哈哈
    就变成了这样只需要经过一次HTTP请求,就可以做到源源不断的信息传送了。(在程序设计中,这种设计叫做回调,即:你有信息了再来通知我,而不是我傻乎乎的每次跑来问你)这样的协议解决了同步有延迟,而且还非常消耗资源的这种情况。

    WebSocket协议有很多种
    这边我们使用的是STOMP协议,STOMP定义了客户端和服务器之间以Frame进行同行,Frame的格式为:

    COMMAND
    header1:value1
    header2:value2
    
    Body^@
    
    COMMAND分为CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT这几种。COMMAND之后下一行紧跟着的是头部的键值对,之后加入一条空行,空行之后为body,即传递的消息实体。
    本文的核心内容:通俗点stomp协议就是(本人亲自Debug获取,绝对通俗易懂):
    \x0A 16进制是\n
    \x00 16进制是0
    COMMAND + \x0A + 循环添加传递的headerdic参数(key+ : + value + \x0A )循环完后+\x0A + 若果有body需要加body + \x00
    之后再utf-8编码
    
    COMMAND可以为:CONNECT、SEND、SUBSCRIBE、UNSUBSCRIBE、BEGIN、COMMIT、ABORT、ACK、NACK、DISCONNECT、CONNECTED、ERROR、MESSAGE、RECEIPT
    

    其实我们也可以自己定义规则类似于STOMP的协议,只要前后端等定义相同的规则,按照所定义的规则实现,统一解析一样就可以。

    iOS客户端处理WebSocket可以使用第三方库jetfireSocketRocket,但是要想处理STOMP协议或者其他协议的就要自己写个实现类,来处理拼接、解析逻辑。这里我们先来看下基于SocketRocket的实现STOMP协议WebSocket的流程:

    流程图

    1.打开请求URL
    2.得到回调webSocketDidOpen
    3.连接Connect,发送连接消息
    核心发送消息代码,不管事要连接还是要处理发送各种Command都需要使用:

     private func sendFrame(command: String?, header: [String: String]?, body: AnyObject?) {
            if socket?.readyState == .OPEN {
                var frameString = ""
                if command != nil {
                    frameString = command! + "\n"
                }
                
                if let header = header {
                    for (key, value) in header {
                        frameString += key
                        frameString += ":"
                        frameString += value
                        frameString += "\n"
                    }
                }
                
                if let body = body as? String {
                    frameString += "\n"
                    frameString += body
                } else if let _ = body as? NSData {
                    
                }
                
                if body == nil {
                    frameString += "\n"
                }
                
                frameString += StompCommands.controlChar
                
                if socket?.readyState == .OPEN {
                    socket?.send(frameString)
                } else {
                    print("no socket connection")
                    if let delegate = delegate {
                        DispatchQueue.main.async(execute: {
                            delegate.stompClientDidDisconnect(client: self)
                        })
                        
                        
                    }
                }
            }
        }
    

    仔细看下这段代码你会觉得跟我之前说的通俗规则是不是一样呢?可以对比下。
    4.发送后就是等着服务端回调给我们消息,不过也可能是失败的消息.
    5.连接完成后基本操作实现就是send和subscribe
    6.如果不用的时候可以取消连接disConnect
    之前网上找了好多第三方库都是说可以实现Stomp协议,但是我使用后都不可以,最后亲身测试一下两个是完全可以的。通过验证也得出了上边的协议规则。
    下边是github上的两个实现库
    WebsocketStompKitStompClientLib
    也可以使用pod ZMBase(Swift)或者OTBase(OC),里边抽出了实现的类

    pod 'ZMBase', '~> 0.1.1'
    pod 'OTBase', '~> 0.1.1'
    

    这里整理了一个包含iOS(OC、Swift)、Android的实现Demo,如果需要请点击。
    感谢大家阅读!喜欢的点个赞、关注一波!

    相关文章

      网友评论

      • Lazyloading:请问我现在和h5通信连接时候只收到个请求头就没了查看js错误信息握手失败 h5作为客户端ios作为服务端 看了两天了还没找到原因 昨天试了下对方貌似是sockeio 我这边用的cocoasyncsocket
        Miaoz0070:@Lazyloading 我github上就是使用websocket,你可以下下来看看。
        Lazyloading:@Miaoz0070 他们用socketio的库那就是websocket了 我这边用的是cocoasyncsocket 原生socket 所以握手失败 但是现在能找到的库都是原生写websocket客户端 所以很头痛 我在想有什么对cocoasyncsocket 封装为websocket的方式 或者直接websocket在ios写服务端的库 大佬有主意吗
        Miaoz0070:@Lazyloading 你的确定你们两个用的是socket还是websocket
      • 扶摇丶:你好,使用StompClientLib,但是几个代理方法都不走,请问这个是什么原因
      • BeethOven:收到的中文 解析错误 想什么原因 前后端都是utf8,但是body里的中文解析变成很奇怪的字母了
      • yuezishenyou:您好,发送消息的时候,body里面某个字段有汉子,服务端就报错。
        yuezishenyou:@Miaoz0070 WebsocketStompKit 372行,
        long bodyLength = [body lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
        msgHeaders[kHeaderContentLength] = [NSNumber numberWithLong:bodyLength];
        这样写,才可以发送中文。
        Miaoz0070:@yuezishenyou 编码是统一的吗?后端前端要统一

      本文标题:iOS WebSocket(STOMP协议)使用对接

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