WebSocket
HTTP协议的局限性: 通信只能由客户端发起。
那么我们需要等待服务端回调的时候就出现了问题,我们无法获取服务端的回调信息,我们能做的只有两种方式:
1、按间隔不停的向服务端发送请求,直到收到服务端的结果。
2、HTTP连接始终打开连接
HTTP协议和TCP/IP协议的关系
HTTP协议长连接和短连接本质上是TCP的长连接和短连接。
HTTP 属于应用层协议,是建立在TCP协议基础上的,通过TCP建立与服务器的连接通道。
TCP是传输层协议,解决在IP协议上数据包的传输(可靠的面向连接的)。
IP是网络层协议,解决路由和寻址问题。
HTTP协议是无状态的
无状态是指HTTP协议对事务处理没有记忆能力,服务器不知道客户端的状态,每次都需要发送新连接请求。
HTTP长连接和短连接
HTTP1.0时协议默认是短连接,客户端和服务端进行一次http请求就需要建立一次连接,结束后就中断连接。我们再访问网页中含有其他的web资源(图片、js文件),每次去访问这样一个web资源,浏览器就会新建立一个http会话。
HTTP1.1时协议默认就是长连接了,保持其连接的持续性,在使用http长连接的时候,http相应的响应头会有Connection:keep-alive.
长连接,当浏览器打开网页之后,客户端和服务端之间用于传输HTTP数据的TCP连接通道不会关闭,客户端再次访问服务器时会继续使用已经存在的连接,但是这个Connection:keep-alive也不会永远保持连接,保持的时间是有服务器端设定的,而实现这种长连接是服务端和客户端都支持才行的。
所以HTTP协议的长连接和短连接实质上是TCP协议的长连接和短连接。
TCP连接
连接建立需要三次握手,释放需要四次挥手。
三次握手:
第一次:
客户端 - - > 服务器
客户端告诉服务端需要建立连接,服务器知道了客户端要建立连接了
第二次:
客户端 < - - 服务器
服务端告诉客户端收到建立连接请求,客户端知道服务器收到连接请求了
第三次:
客户端 - - > 服务器
客户端告诉服务端收到建立连接的请求回应,服务器知道客户端收到了自己的回应
此时客户端与服务器已经建立了连接.
四次挥手:
第一次:
客户端 - - > 服务器
客户端告诉服务器端断开连接,并停止发送数据,服务端知道客户端要断开连接
第二次:
客户端 < - - 服务器
服务端告诉客户端收到断开连接的请求,客户端等待最终确认断开连接报文,服务端发送为发送完成的数据,客户端知道服务段收到断开连接请求
第三次:
客户端 < - - 服务器
服务端告诉客户端传输数据完成之后,发送断开连接的确认请求,客户端知道断开连接的确认报文。
第四次:
客户端 - - > 服务器
客户端向服务端发送收到服务端TCP断开连接报文,回复服务端断开请求
断开TCP连接
HTTP和WebSocket的区别
WebSocket协议
WebSocket是一种在单个TCP连接上进行全双工通信的协议。它为网络应用提供了一种在客户端和服务器之间建立持久连接的方式。这种连接一旦建立,就可以用于服务器向客户端推送消息,而不仅限于客户端向服务器发送请求的传统HTTP模式。
WebSocket协议的出现,使得web应用能够实现类似桌面应用的实时通信功能。与HTTP协议不同,WebSocket的握手过程仅需一次,后续通信则为连续的双向数据流,大大减少了消息交换的延迟,提高了数据传输效率。
实现WebSocket协议需要客户端和服务器端的支持。在客户端,通常浏览器会提供WebSocket API,开发者可以通过JavaScript代码与服务器建立WebSocket连接。服务器端则需要相应的WebSocket服务器支持,比如Node.js中就有很多优秀的WebSocket服务器库可供使用。
WebSocket协议的引入是为了克服HTTP协议在实时通信上的局限性,它是基于TCP的一种高效、低延迟的网络协议。WebSocket协议在Web开发中扮演着重要角色,特别是在实现双向通信,如在线聊天、实时游戏、股票交易等场景。SocketRocket是苹果iOS平台上一个著名的WebSocket库,它是一个轻量级、高性能的实现,旨在提供稳定且可靠的WebSocket连接。
WebSocket协议的核心特性包括:
-
持久连接:一旦建立连接,WebSocket连接会保持开放,直到客户端或服务器一方关闭连接。这不同于HTTP协议中的每次请求-响应模式,WebSocket允许数据在两端之间双向流动,无需频繁地创建和销毁连接。
-
握手协议:WebSocket连接的建立需要通过一个HTTP升级请求来完成。这个过程称为WebSocket握手,客户端首先发送一个"Upgrade"头的HTTP请求,服务器回应同意升级,然后双方就可以通过WebSocket协议进行通信了。
-
二进制帧:WebSocket协议支持文本和二进制数据帧,使得传输更高效,特别是对于非文本数据(如图片、音频或视频流)。
-
扩展性:WebSocket定义了一个可扩展的框架,允许添加自定义头部和编码机制,以适应各种应用场景。
SocketRocket库特性与优势
1 SocketRocket的设计理念
1.1 库的设计目标与应用场景
SocketRocket作为一个Objective-C语言编写的WebSocket客户端库,其设计目标是为了在iOS平台提供一个简单、高效、并且具备良好兼容性的WebSocket通信解决方案。它不仅遵循WebSocket协议的最新标准,同时也考虑到移动设备的网络状况波动较大,因此在设计时特别强化了在不稳定网络环境下的表现。
SocketRocket被广泛应用于需要实时数据交互的移动应用,例如即时通讯、游戏、股票交易以及实时监控类应用。由于其轻量级和高效的特性,SocketRocket非常适合在资源受限的移动平台上使用,不会给APP带来过于沉重的负担。
1.2 相较于其他库的优势分析
相比于其他语言或平台上的WebSocket库,SocketRocket具有以下几个明显优势:
- 性能优化 :SocketRocket对于IO操作进行了优化,减少了资源的消耗,提升消息处理的速度。
- 异步非阻塞模型 :SocketRocket采用异步事件驱动模型,避免了网络操作的阻塞,使得UI线程可以专注于处理用户交互,从而提升应用的响应性。
- 简洁易用的API :SocketRocket的API设计得非常简洁,使得开发者可以很轻松地将WebSocket通信集成到他们的iOS应用中。
- 广泛的平台支持 :它支持iOS 5及以上版本,也支持macOS。
2 SocketRocket的核心功能
2.1 实现的WebSocket功能概述
SocketRocket库实现了WebSocket协议中的所有基本功能,包括:
- 连接建立 :通过标准的handshake过程与服务器建立连接。
- 消息传输 :支持文本和二进制数据的发送和接收。
- 关闭连接 :允许程序或用户主动关闭连接。
- Ping/Pong协议 :通过发送Ping消息并接收Pong响应,帮助监测连接的活跃状态。
- TLS/SSL支持 :确保通信的安全性,支持使用wss://协议进行安全的WebSocket连接。
2.2 性能特点与效率分析
性能是SocketRocket的另一个亮点。其性能特点主要体现在以下几个方面:
- 高效的IO处理 :通过优化IO操作,SocketRocket实现了高吞吐量的数据处理。
- 资源使用率低 :相比于其他库,SocketRocket在内存和CPU的使用上更为高效。
- 自适应的网络策略 :根据网络状况自动调整消息的发送频率和重试机制,保证了最佳的连接稳定性和数据传输效率。
对于使用SocketRocket进行实际开发的开发者来说,理解上述代码中的 initWithURL 、 open 和 send 等方法是基础。 initWithURL: 用于初始化WebSocket客户端对象并设置服务器URL, open 方法用于打开连接,而 send: 用于向服务器发送消息。这仅仅是开始,开发者还需要对回调函数有深刻理解,以便处理如 webSocketDidOpen: 、 webSocket:didReceiveMessage: 等事件。
sequenceDiagram
participant User
participant App
participant WebSocket
participant Server
User->>App: 初始化WebSocket
App->>WebSocket: 创建实例
App->>WebSocket: 连接服务器
WebSocket->>Server: 握手请求
Server-->>WebSocket: 握手响应
WebSocket-->>App: 连接成功
App->>WebSocket: 发送消息
WebSocket->>Server: 发送消息
Server-->>WebSocket: 确认消息
WebSocket-->>App: 消息发送成功
在上述时序图中,展示了从用户到App,再到WebSocket和Server之间的交互过程。这是开发者在使用SocketRocket时的基本流程,了解这个流程对于在项目中应用WebSocket协议至关重要。
3. 线程安全的WebSocket连接
在现代网络应用开发中,多线程编程已经成为处理并发任务的常见手段。然而,将WebSocket通信集成到多线程环境中时,线程安全问题往往成为开发者必须解决的挑战。本章将详细探讨线程安全在WebSocket连接中遇到的问题,以及SocketRocket库如何处理这些问题。
3.1 多线程环境下WebSocket的挑战
3.1.1 线程安全问题的理论基础
在多线程环境下,线程安全问题通常发生在多个线程同时访问和修改共享资源时。如果没有适当的同步机制,可能会导致数据不一致、资源竞争等问题。
对于WebSocket连接来说,线程安全问题主要体现在以下几个方面:
- 消息接收 :当多个线程试图同时读取同一连接上的数据时,需要确保消息的顺序不会被打乱,且每条消息都能被正确处理。
- 消息发送 :多个线程可能同时向同一个WebSocket连接发送消息,需要避免发送操作之间的干扰。
- 连接管理 :如关闭、重连等连接状态的变更,需要被所有相关线程一致地感知到。
3.1.2 线程安全问题在SocketRocket中的处理
SocketRocket库通过其内部的设计确保了在多线程环境下的线程安全性。主要的机制包括:
- 内部同步机制 :SocketRocket使用内部锁或其他同步机制来确保当一个线程处理WebSocket事件时,其他线程不能干扰这一过程。
- 状态机设计 :库内部使用状态机来管理WebSocket连接的状态,确保连接状态的变更能正确反映到所有线程中。
- 消息队列 :对于读取操作,SocketRocket可以使用消息队列来保证消息的有序处理。
接下来,我们将详细介绍SocketRocket库的线程模型,以及它如何确保在多线程环境中的线程安全。
3.2 SocketRocket的线程模型
3.2.1 线程模型的架构设计
SocketRocket库的线程模型设计主要为了处理消息的异步接收与发送,同时保证在多线程环境中的线程安全性。其基本架构如下:
-
单线程事件循环 :SocketRocket仅使用一个事件循环线程来处理所有WebSocket事件,包括接收和发送消息、连接的建立与关闭等。这样的设计简化了线程安全的实现,因为所有的事件处理都是顺序进行的。
-
锁机制 :为处理跨线程的事件和调用,SocketRocket内部使用锁机制来确保同一时间只有一个线程能够执行对WebSocket连接的操作。 3.2.2 线程模型在实际应用中的优势展示 在实际应用中,SocketRocket的线程模型表现出以下优势:
-
简洁的API :开发者不需要关心线程安全问题,可以专注于业务逻辑的实现。
-
高效的事件处理 :由于事件循环线程是唯一处理事件的线程,消息处理效率较高,避免了线程间切换的开销。
-
易于调试 :由于所有的WebSocket事件都在同一个线程内处理,开发者可以更容易地调试和追踪问题。
4. 自动重连机制
4.1 自动重连的重要性与机制
4.1.1 网络不可靠性的理论探讨
在网络编程中,一个不争的事实是网络环境是极其不稳定和不可靠的。无论是有线还是无线连接,都可能由于硬件故障、软件问题、网络拥堵、路由故障、DNS问题、ISP限制等因素导致通信中断。WebSocket协议虽然建立在TCP之上,可以提供一个持续的连接,但这种连接在遇到问题时也有可能断开。一旦连接中断,如果没有适当的处理机制,那么原本设计的实时通信功能就会失效。
在自动化和物联网应用中,这种实时性尤为重要。例如,一个实时监控系统在失去与传感器的连接后,若不能迅速恢复通信,可能会导致监控失效,进而影响决策和安全。因此,在设计应用时,必须考虑到网络的不稳定性,并实现自动重连机制以提高应用的健壮性。
4.1.2 自动重连机制的工作原理
自动重连机制的工作原理是检测到连接中断后,自动尝试重新建立连接而不是等待用户的干预。一般来讲,自动重连机制包括以下几个步骤:
- 状态监控 :在维持WebSocket连接的过程中,持续监控网络状态和连接状态。
- 断开检测 :当连接断开时,实时检测到连接丢失的事件。
- 重连策略 :根据预设的重连策略(如指数退避算法),在一定时间后尝试重新连接。
- 连接尝试 :发起重连尝试,并根据重连过程中的反馈调整重连策略。
- 重连成功 :一旦成功建立连接,则恢复到正常的通信流程。
4.2 自动重连的策略与调优
4.2.1 不同重连策略的优缺点
在设计自动重连机制时,常见的策略包括固定间隔重连、指数退避重连和随机退避重连。
-
固定间隔重连 :在这种策略下,每次重连尝试之间的时间间隔是固定的。该策略的优点在于实现简单,缺点是可能会加剧网络的负载,或者在一些情况下导致重连尝试过于频繁。
-
指数退避重连 :指数退避策略在每次连续失败后,都会增加重连等待时间,通常以指数形式增加。这种策略可以减少对网络资源的占用,同时在连续失败的情况下,逐步加大间隔时间,避免了网络的瞬时高负载。缺点是初始间隔设置不当可能会导致重连延迟时间过长。
-
随机退避重连 :在随机退避策略中,每次重连尝试之间的时间间隔是一个随机值。这种策略的目的是减少多个客户端在同一个网络故障下同时重连的冲突概率。随机退避重连策略的一个变种是随机指数退避,结合了随机性和指数退避的特性。
4.2.2 根据业务需求的重连策略调整
选择哪种重连策略取决于应用的具体需求。例如:
- 如果应用要求连接尽可能快地恢复,可以考虑固定间隔重连,并设置较短的间隔时间。
- 如果应用在重连失败时可以容忍较长时间的延迟,并且不希望给服务器造成过大压力,指数退避重连可能是更好的选择。
- 在移动应用中,由于网络环境的多变性,随机退避策略或随机指数退避策略可能更为合适。
调整重连策略时还需要考虑以下因素:
- 网络环境 :网络环境的稳定性很大程度上决定了重连策略的调整方向。在不稳定网络环境下,指数退避策略更为合理。
- 业务特性 :不同的业务对实时性的要求不同,需要根据业务特性来调整重连的及时性和重连间隔。
- 服务器负载 :如果服务器负载较高,应尽量避免频繁的重连尝试,以防止给服务器造成额外的负担。
5. 丰富的错误处理
5.1 错误处理在SocketRocket中的实现
5.1.1 错误处理机制的原理与必要性
在任何网络通信过程中,错误处理机制的引入都是至关重要的。错误处理机制能够确保在发生异常情况时,系统能够进行适当的错误诊断、记录以及尽可能的恢复,以保证应用的稳定性和用户的良好体验。在SocketRocket库中,错误处理机制是通过一套复杂的逻辑来实现的,这包括了错误的捕获、分类和后续处理。
错误处理机制的必要性可以从以下几个方面来考虑:
- 稳定性 :在网络不稳定或数据传输出错时,确保应用能够继续运行而不是直接崩溃。
- 用户体验 :提供清晰的错误提示信息,帮助用户理解发生了什么问题。
- 维护性 :错误信息可以帮助开发者快速定位问题所在,提高代码的可维护性。
- 安全性 :在遇到潜在的安全问题时,能够立即进行响应,阻止可能的安全威胁。
SocketRocket中错误处理的实现机制通常包括: - 异常捕获 :在代码的各个部分设置异常捕获点,确保异常能够被及时发现。 - 错误类型 :定义不同类型的错误码,区分是网络问题、协议错误还是应用层错误。 - 错误回调 :提供错误回调函数,让使用者可以自定义错误处理逻辑。
5.1.2 SocketRocket中错误捕获与反馈机制
SocketRocket库为了支持复杂的错误处理机制,提供了多层次的错误捕获与反馈机制。在库内部,从底层的网络传输到上层的应用数据处理,每一个环节都设计有错误检测点,这些检测点能够将发生的异常情况转换为可以识别的错误码或者异常对象,然后通过提供的接口反馈给开发者。
开发者可以利用这些接口来捕获这些错误,并根据错误类型采取相应的措施。例如,在遇到网络断开的情况下,开发者可能需要触发重连逻辑;在接收到不合法的数据时,可能需要记录日志并通知用户等。
5.2 错误处理的实践指导
5.2.1 常见错误类型的分析与应对
在使用SocketRocket时,可能会遇到多种类型的错误。以下是一些常见的错误类型及相应的处理方法:
- 连接失败 :可能是由于网络问题导致的,应尝试重新连接。
- 消息解析错误 :可能是服务器发送的数据格式有误,应记录错误信息并通知服务器端。
- 心跳超时 :如果长时间没有收到服务器的心跳消息,可能是网络问题或者服务器异常,需要处理。
对于每种错误类型,开发者应结合实际情况编写相应的错误处理逻辑。例如,对于连接失败,可以实现指数退避算法来增加重连的间隔时间,避免对服务器造成过大压力。
6. HTTP/HTTPS兼容性
6.1 WebSocket与HTTP/HTTPS的关系
6.1.1 WebSocket与HTTP/HTTPS的通信模型比较
在Web开发中,HTTP/HTTPS协议是构建客户端和服务端交互的主要方式。然而,HTTP是一个基于请求-响应模式的协议,在这种模式下,客户端发送一个请求,然后等待服务器的响应。一旦服务器响应,连接就关闭了,这导致了每次交互都需要一个全新的连接。随着Web应用变得越来越动态和交互性强,这种模式逐渐显示出性能上的不足,特别是在需要服务器主动向客户端推送信息的场景中。
WebSocket是一种全新的协议,它实现了服务器和客户端之间的全双工通信,允许服务器向客户端发送消息,而无需客户端的显式请求。WebSocket与HTTP/HTTPS相比,显著的优势在于其能够支持实时通信和更高效的长连接。虽然WebSocket能够在传输层复用HTTP协议建立的连接,但是它提供了完全不同的通信模型。
WebSocket握手阶段使用HTTP或HTTPS进行连接的建立,但是握手成功后,协议会升级到WebSocket,之后的数据传输就不再依赖于HTTP/HTTPS的请求-响应模型。这意味着在建立连接之后,WebSocket提供了与HTTP/HTTPS截然不同的通信模型,能够更好地支持实时应用。
6.1.2 在SocketRocket中实现HTTP/HTTPS兼容性的方案 SocketRocket库支持WebSocket协议,并且在连接建立过程中利用了HTTP/HTTPS协议。它实现了所谓的HTTP升级机制,允许客户端和服务器通过HTTP请求和响应来协商升级到WebSocket协议。
具体来说,当客户端需要与服务器建立WebSocket连接时,会首先通过HTTP请求发送一个特殊的WebSocket握手请求。这个握手请求遵循WebSocket协议的规定,并且包含必要的Upgrade头部,通知服务器希望升级连接。服务器端的SocketRocket库会识别出这个握手请求,并且根据请求中提供的信息(如协议版本,支持的扩展等)来完成握手过程。如果成功,连接将被升级到WebSocket协议,之后的通信将基于WebSocket,而不是HTTP。
这种方式的优点在于,它允许开发者复用现有的HTTP/HTTPS基础设施来建立WebSocket连接,从而在不需要额外端口的情况下进行实时通信。这使得SocketRocket非常适合需要与现有HTTP/HTTPS服务无缝集成的场景。
6.2 SocketRocket的HTTP/HTTPS适配器
6.2.1 适配器的设计与实现
SocketRocket的HTTP/HTTPS适配器是其核心组件之一。适配器负责处理与HTTP/HTTPS层的交互,确保WebSocket握手过程正确无误,并且兼容各种Web服务器和代理服务器。
在设计上,适配器提供了灵活的配置选项,允许开发者根据实际需求调整握手过程中的HTTP请求头。例如,开发者可以选择发送特定的User-Agent,修改请求超时设置,或者指定SSL/TLS的版本。
在实现上,适配器封装了底层的HTTP/HTTPS连接和数据传输细节。当需要升级到WebSocket时,适配器会自动处理升级握手请求,并且透明地切换到WebSocket协议进行数据传输。这一过程对开发者来说是完全透明的,无需手动干预。
6.2.2 适配器在实际应用中的表现与优势
在实际应用中,SocketRocket的HTTP/HTTPS适配器展示了强大的兼容性和灵活性。适配器不仅支持标准的WebSocket连接,而且能够在复杂的网络环境中表现良好,比如通过反向代理或者负载均衡器等。适配器能够处理不同类型的HTTP服务器产生的各种响应,使得SocketRocket库能够在不同的部署环境中稳定运行。
适配器的优势还在于它的配置选项非常丰富。在很多情况下,开发者可以根据自己的需求,对HTTP请求头进行定制,这使得SocketRocket能够适应各种不同的业务场景。比如,在需要严格的安全控制的环境下,开发者可以选择支持最新的TLS协议版本,并且设置合适的加密套件。
此外,适配器还可以根据服务器返回的状态码和头部信息,自动处理重试和重连逻辑。当遇到服务器拒绝升级或者请求超时时,适配器可以自动回退到HTTP请求,并且尝试重新握手。这种智能的错误处理机制大大提高了SocketRocket库的可用性,减少了因网络问题或配置错误导致的连接中断。
通过这种方式,SocketRocket的HTTP/HTTPS适配器不仅为WebSocket通信提供了一个可靠的桥梁,而且也大大提升了Web应用的整体用户体验。
7. SocketRocket库的集成与使用
随着实时通信在现代Web应用中的日益重要,WebSocket协议的应用变得越来越广泛。SocketRocket是一个被广泛使用的iOS WebSocket客户端库,其简洁的API和高效的性能赢得了众多开发者的青睐。本章节将详细介绍SocketRocket库的集成与使用,包括基础的安装配置,以及一些高级使用技巧和性能优化。
SocketRocket 是facebook下对websocket的封装,具体源码就需要在github上看了。 我们直接通过pod 引入就好了:
platform :ios, '9.0'
target 'iosWebSocket' do
pod 'SocketRocket'
pod 'AFNetworking','~>3.1.0'
end
环境配置与依赖管理
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
7.1、建立连接
-(void)connectServer{
if(self.webScoket){
return;
}
self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://127.0.0.1:7272"]];
self.webScoket.delegate = self;
[self.webScoket open];
}
7.2、建立连接之后我们需要向服务端发送心跳,此时我们需要在SRWebSocketDelegate的delegate中等待成功之后去发送心跳请求。
心跳的方法就不展开了,后面去看源码或者demo吧,其实就是调用SRWebSocket 的 sendPing方法
//已经连接
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
NSLog(@"已经连接,开启心跳");
self.isConnect = YES;
self.socketStatus = WebSocketStatusConnect;
[self initHeartBeat];//开始心跳
}
7.3、发送数据
注意:我们在sendData的时候,我们一定要判断webScoket.readyState 是SR_OPEN,此时才能发送数据,否则会crash的。
//发送数据给服务器
-(void)sendDataToServer:(NSString *)data{
[self.sendDataArray addObject:data];
//没有网络
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
//开启网络检测定时器
[self noNetWorkStartTesting];
}else{
if (self.webScoket != nil) {
//只有长连接OPEN开启状态才能调用send方法
if (self.webScoket.readyState == SR_OPEN) {
[self.webScoket send:data];
}else if (self.webScoket.readyState == SR_CONNECTING){
//正在连接
NSLog(@"正在连接中,重连后会去自动同步数据");
}else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
//调用 reConnectServer 方法重连,连接成功后 继续发送数据
[self reConnectServer];
}
}else{
[self connectServer];//连接服务器
}
}
}
7.4、服务端的回调
服务端的回调,我们只需要监听didReceiveMessage方法就好了。
自己定义delegate方法,之后在使用的地方实现代理协议,这样我们就能获取到服务端的回调了。
//接收消息
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
NSLog(@"接收消息 ---- %@", message);
if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
[self.delegate webSocketDidReceiveMessage:message];
}
}
7.5、关闭webSocket
//重新连接
-(void)reConnectServer{
//关闭之前的连接
[self webSocketClose];
//重连10次 2^10 = 1024
if (self.reConnectTime > 1024) {
self.reConnectTime = 0;
return;
}
__weak typeof(self)ws = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
return ;
}
[ws connectServer];
NSLog(@"重新连接......");
if (ws.reConnectTime == 0) {//重连时间2的指数级增长
ws.reConnectTime = 2;
}else{
ws.reConnectTime *= 2;
}
});
}
WebSocketManager.h
//
// WebSocketManager.h
#import <Foundation/Foundation.h>
#import "SRWebSocket.h"
#import "AFNetworking.h"
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, WebSocketStatus){
WebSocketStatus_Default = 0,
WebSocketStatus_Connecting,
WebSocketStatus_Connected,
WebSocketStatus_Connectjioning,
WebSocketStatus_Hearbeat,
WebSocketStatus_DisConnect
};
@protocol WebSocketManagerDelegate<NSObject>
/**
* @name webSocket收到消息回调
* @param string 消息体
*/
-(void)webSocketDidReceiveMessage:(NSString *)string;
/**
* @name 网络状态回调
* @param status 网络状态
*/
- (void)networkStatusUpdate:(AFNetworkReachabilityStatus)status;
@end
@interface WebSocketManager : NSObject
/**
* @name webSocket实例对象
*/
@property(nullable, nonatomic, strong) SRWebSocket *webScoket;
/**
* @name webSocket代理
*/
@property(nonatomic, weak) id<WebSocketManagerDelegate> delegate;
/**
* @name 是否连接
*/
@property(nonatomic, assign) BOOL isConnect; //是否连接
/**
* @name socket状态
*/
@property(nonatomic, assign) WebSocketStatus socketStatus;
#pragma mark === 网络状态
/**
* @name 网络状态
*/
@property (nonatomic,assign) AFNetworkReachabilityStatus networkStatus;
+(instancetype)shared;
-(void)connectServer;//建立长连接
-(void)reConnectServer;//重新连接
-(void)webSocketClose;//关闭连接
-(void)sendDataToServer:(NSString *)data; //向服务器发送数据
@end
NS_ASSUME_NONNULL_END
WebSocketManager.m
//
// WebSocketManager.m
#import "WebSocketManager.h"
#define dispatch_main_async_safe(block)\
if ([NSThread isMainThread]) {\
block();\
} else {\
dispatch_async(dispatch_get_main_queue(), block);\
}
@interface WebSocketManager ()<SRWebSocketDelegate>
@property(nonatomic, strong) NSTimer *headerBeatTimer; //心跳定时器
//@property(nonatomic, strong) NSTimer *networkTestingTimer; //没有网络的时候检测定时器
@property(nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
@property(nonatomic, strong) NSMutableArray *sendDataArray; //存储要发送给服务器的数据
@property(nonatomic, assign) BOOL isActiveClose; //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法
@property (nonatomic,strong) AFNetworkReachabilityManager *mgr;//网络状态监听
@property (nonatomic,assign) BOOL networkCanUse;
@end
@implementation WebSocketManager
+(instancetype)shared{
static WebSocketManager *__instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__instance = [[WebSocketManager alloc] init];
});
return __instance;
}
-(instancetype)init{
self = [super init];
if (self) {
self.reConnectTime = 0;
self.isActiveClose = NO;
self.sendDataArray = [[NSMutableArray alloc] init];
[self getNetworStatus];
}
return self;
}
//建立长连接
-(void)connectServer{
if(self.webScoket){
return;
}
self.socketStatus = WebSocketStatus_Connecting;
self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:@"ws://127.0.0.1:7272"]];
self.webScoket.delegate = self;
[self.webScoket open];
}
-(void)sendPing:(id)sender{
NSLog(@"sendPing heart");
// NSString *heart = @"heart";
NSData *heartData = [[NSData alloc] initWithBase64EncodedString:@"heart" options:NSUTF8StringEncoding];
[self.webScoket sendPing:heartData];
// [self.webScoket sendPing:nil error:NULL];
}
//关闭长连接
-(void)webSocketClose{
self.isActiveClose = YES;
self.isConnect = NO;
self.socketStatus = WebSocketStatus_Default;
if (self.webScoket) {
[self.webScoket close];
self.webScoket = nil;
}
//关闭心跳定时器
[self destoryHeartBeat];
//关闭网络检测定时器
// [self destoryNetWorkStartTesting];
}
#pragma mark socket delegate
//已经连接
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
NSLog(@"已经连接,开启心跳");
self.isConnect = YES;
self.socketStatus = WebSocketStatus_Connected;
[self initHeartBeat];//开始心跳
}
//连接失败
-(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
NSLog(@"连接失败");
self.isConnect = NO;
self.socketStatus = WebSocketStatus_DisConnect;
NSLog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
NSLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
NSLog(@"2.判断调用层是否需要连接,不需要的时候不k连接,浪费流量");
NSLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
//判断网络环境
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
//没有网络,开启网络监测定时器
// [self noNetWorkStartTesting];//开启网络检测定时器
}else{
[self reConnectServer];//连接失败,重新连接
}
}
//接收消息
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
NSLog(@"接收消息 ---- %@", message);
if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
[self.delegate webSocketDidReceiveMessage:message];
}
}
//关闭连接
-(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
self.isConnect = NO;
if (self.isActiveClose) {
self.socketStatus = WebSocketStatus_Default;
return;
}else{
self.socketStatus = WebSocketStatus_DisConnect;
}
NSLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
[self destoryHeartBeat]; //断开时销毁心跳
//判断网络
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
//没有网络,开启网络监测定时器
// [self noNetWorkStartTesting];
}else{
//有网络
NSLog(@"关闭网络");
self.webScoket = nil;
[self reConnectServer];
}
}
/**
接受服务端发生Pong消息,我们在建立长连接之后会建立与服务器端的心跳包
心跳包是我们用来告诉服务端:客户端还在线,心跳包是ping消息,于此同时服务端也会返回给我们一个pong消息
*/
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongData{
NSLog(@"接受ping 数据 --> %@",pongData);
}
#pragma mark NSTimer
//初始化心跳
-(void)initHeartBeat{
if (self.headerBeatTimer) {
return;
}
[self destoryHeartBeat];
dispatch_main_async_safe(^{
self.headerBeatTimer = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(senderheartBeat) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.headerBeatTimer forMode:NSRunLoopCommonModes];
});
}
//重新连接
-(void)reConnectServer{
//关闭之前的连接
[self webSocketClose];
//重连10次 2^10 = 1024
if (self.reConnectTime > 1024) {
self.reConnectTime = 0;
return;
}
__weak typeof(self)ws = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
return ;
}
[ws connectServer];
NSLog(@"重新连接......");
if (ws.reConnectTime == 0) {//重连时间2的指数级增长
ws.reConnectTime = 2;
}else{
ws.reConnectTime *= 2;
}
});
}
//发送心跳
-(void)senderheartBeat{
NSLog(@"senderheartBeat");
//和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
__weak typeof (self) ws = self;
dispatch_main_async_safe(^{
if (ws.webScoket.readyState == SR_OPEN) {
[ws sendPing:nil];
}else if (ws.webScoket.readyState == SR_CONNECTING){
NSLog(@"正在连接中");
[ws reConnectServer];
}else if (ws.webScoket.readyState == SR_CLOSED || ws.webScoket.readyState == SR_CLOSING){
NSLog(@"断开,重连");
[ws reConnectServer];
}else{
NSLog(@"没网络,发送失败,一旦断网 socket 会被我设置 nil 的");
}
});
}
//取消心跳
-(void)destoryHeartBeat{
__weak typeof(self) ws = self;
dispatch_main_async_safe(^{
if (ws.headerBeatTimer) {
[ws.headerBeatTimer invalidate];
ws.headerBeatTimer = nil;
}
});
}
/*
//没有网络的时候开始定时 -- 用于网络检测
-(void)noNetWorkStartTestingTimer{
__weak typeof(self)ws = self;
dispatch_main_async_safe(^{
ws.networkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:ws.networkTestingTimer forMode:NSDefaultRunLoopMode];
});
}
//定时检测网络
-(void)noNetWorkStartTesting{
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable) {
//关闭网络检测定时器
[self destoryNetWorkStartTesting];
//重新连接
[self reConnectServer];
}
}
//取消网络检测
-(void)destoryNetWorkStartTesting{
__weak typeof(self) ws = self;
dispatch_main_async_safe(^{
if (ws.networkTestingTimer) {
[ws.networkTestingTimer invalidate];
ws.networkTestingTimer = nil;
}
});
}*/
//发送数据给服务器
-(void)sendDataToServer:(NSString *)data{
[self.sendDataArray addObject:data];
//没有网络
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
//开启网络检测定时器
// [self noNetWorkStartTesting];
}else{
if (self.webScoket != nil) {
//只有长连接OPEN开启状态才能调用send方法
if (self.webScoket.readyState == SR_OPEN) {
[self.webScoket send:data];
}else if (self.webScoket.readyState == SR_CONNECTING){
//正在连接
NSLog(@"正在连接中,重连后会去自动同步数据");
}else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
//调用 reConnectServer 方法重连,连接成功后 继续发送数据
[self reConnectServer];
}
}else{
[self connectServer];//连接服务器
}
}
}
#pragma mark === 加载网络监听
- (void)getNetworStatus {
__weak typeof(self) weakSelf = self;
self.mgr = [AFNetworkReachabilityManager sharedManager];
[self.mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
weakSelf.networkStatus = status;
//获取联网可达状态
NSString * statusMsg = @"";
BOOL canUse = NO;
switch (status) {
case AFNetworkReachabilityStatusUnknown:
NSLog(@"NetworkingTypeUnknown");
canUse = NO;
statusMsg = @"未知网络";
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"NetworkingTypeNotReachable");
canUse = NO;
statusMsg = @"无网络访问";
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"NetworkingTypeReachableViaWWAN");
canUse = YES;
statusMsg = @"数据流量访问";
break;
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"NetworkingTypeReachableViaWiFi");
canUse = YES;
statusMsg = @"Wi-Fi热点访问";
break;
default:
NSLog(@"NetworkingTypeUnknown");
canUse = NO;
statusMsg = @"未知网络";
break;
}
dispatch_async(dispatch_get_main_queue(), ^{
if (weakSelf.delegate && [weakSelf.delegate respondsToSelector:@selector(networkStatusUpdate:)]) {
[weakSelf.delegate networkStatusUpdate:status];
}
});
NSLog(@"[双机互联]监听到网络变化:%@,状态:%ld",statusMsg,status);
weakSelf.networkCanUse = canUse;
if (canUse) {
//重新连接
[weakSelf reConnectServer];
}
}];
[self.mgr startMonitoring];
}
@end