本片我们说下WebSocket,之前项目中有几个轮询的情况,使用基于http协议的接口,每隔几秒调用一下,感觉有点浪费资源。Http1.0默认是短连接,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接客户端主动请求,请求过后关闭,这样每隔几秒钟都要去进行三次握手,请求打开-关闭操作很浪费资源。Http1.1之后,默认使用长连接,用以保持连接特性,虽然可以使用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可以使用第三方库jetfire、SocketRocket,但是要想处理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上的两个实现库
WebsocketStompKit、StompClientLib
也可以使用pod ZMBase(Swift)或者OTBase(OC),里边抽出了实现的类
pod 'ZMBase', '~> 0.1.1'
pod 'OTBase', '~> 0.1.1'
这里整理了一个包含iOS(OC、Swift)、Android的实现Demo,如果需要请点击。
感谢大家阅读!喜欢的点个赞、关注一波!