iOS 中使用 webSocket

是服务器和app之间的一种通信方式

webSocket 实现了服务端推机制(主动向客户端发送消息)。新的 web 浏览器全都支持 WebSocket,这使得它的使用超级简单。通过 WebSocket 能够打开持久连接,大部分网络都能轻松处理 WebSocket 连接。在 iOS 中使用 WebSocket 比较麻烦,你必须进行大量的设置,而且内置的 API 根本帮不上忙。这时 Starscream 出现了——这个小巧、易于使用的库让你所有的烦恼不翼而飞。

Client1 ——->   cloud  ————>client2,3,4…

<——-返回ack         <——-返回ack

一,基本使用

1根据url创建socket

var request = URLRequest(url: URL(string: "url")!)

request.timeoutInterval = 5//超时时间

socket = WebSocket(request: request)

socket.delegate = self//接收到消息走代理方法

2。发送消息

socket.write(string: sendStr)

3.接收消息,在代理方法中

func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {

//接收到字符串消息

}

func websocketDidReceiveData(socket: WebSocketClient, data: Data) {

printLog(“\(data)”)//接收到data消息

}

二 常见问题

1.如何确保client向特定的client发送消息

“\(storeID!)-\(deviceNumber)-\(deviceGlobalID!)”.uppercased()    这些标志客户端的唯一性

发送消息时带着要发送给哪些client(唯一标识性数组)发送给cloud,cloud根据要发送给的client数组向相应的client发送消息

/// 发送一条消息到指定的多个设备

///

/// - Parameters:

///   - deviceID: web socket 登陆名称数组

///   - text: 要发送的文本

func sendTextTo(deviceIDs: [String], text: String) {

if socket == nil  {

return

}

if socket.isConnected == false {

return

}

let cmdMessage = AldeloMessage(Type: 1, MsgGID: UUID().uuidString, Receivers: deviceIDs, Content: text, Time: nil, Publisher: nil, axOrderIDs: nil)

if let sendStr = AldeloMessage.toJsonString(messages: [cmdMessage]) {

socket.write(string: sendStr)

}

}

2.如何保持链接

十分钟发送一次心跳包,app进入前台时,app断网重连时,app失去web连接时,重新连接

NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)

do {

try reachability.startNotifier()

} catch {

print("Unable to start notifier")

}

reachability.whenReachable = { [weak self] reachability in

self?.reconnectTimes = 10

firstly {

after(seconds: 3)

}.done {

if self?.socket == nil {

return

}

self?.socket.connect()

}

}

if #available(iOS 10.0, *) {

timer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { timer in

let now = Date().timeIntervalSince1970

let s = now - self.lastReceivedMessageTime

if s >= 600 && s <= 660  {

self.sendHeartBeat()

} else if s > 660 {

self.reconnect()

}

}

} else {

timer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(handleTimer), userInfo: nil, repeats: true)

}

@objc func handleTimer(timer: Timer) {

let now = Date().timeIntervalSince1970

let s = now - self.lastReceivedMessageTime

if s >= 600 && s <= 660  {

self.sendHeartBeat()

} else if s > 660 {

self.reconnect()

}

}

@objc func appDidBecomeActive(_ application: UIApplication) {

firstly {

after(seconds: 3)

}.done {

if self.socket == nil {

return

}

if self.socket.isConnected == false && self.reachability.connection != .none {

self.socket.connect()

}

}

}

3.如何保证消息送达

client到cloud:client中维护一个message数据表(包括字段是否发送成功sent)cloud收到消息之后向client返回ack,client收到ack后将该条message标记为sent=1已发送

60秒client未收到ack,视为发送失败,从新发送

cloud端message表中已经存在该条消息,则忽略,但是向客户端client发送ack

cloud到clinet:client收到消息后向cloud返回ack,cloud收到ack标记消息为已发送成功, 60秒cloud未收到ack,视为发送失败,从新发送

client端message表中已经存在该条消息,则忽略,但是向客户端client发送ack

func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {

printLog("Web Socket receive \(text)")

lastReceivedMessageTime = Date().timeIntervalSince1970

if text == "$" {

printLog("Received Heart Beat!!!!")

return

}

guard let messageArray = AldeloMessage.from(jsonData: text.data(using: .utf8)!) else { return }

for message in messageArray {

if message.Type == 99 { //ACK

DBPool.write { db in

try? db.execute("Update AldeloMessageRecord Set sent = 1 Where messageGID = '\(message.MsgGID)'")

}

continue

}

//收到消息后回复ACK,这样服务器会标记这条消息发送成功

sendACK(message: message)

//从收到的消息列表中对比msgid, 如果已经收到过,则忽略这条消息, 去重处理

var shouldReturn = false

DBQueue.inDatabase { db in

do {

if let count = try Int.fetchOne(db, "Select count(*) from AldeloMessageRecord where messageGID = '\(message.MsgGID)'"), count > 0 {

//数据库里有这条消息,说明已经收到过,忽略掉

Log.shareInstance.log(message: "Websocket 收到重复消息,已忽略")

printLog("Websocket 收到重复消息,已忽略")

shouldReturn = true

}

} catch {

Log.shareInstance.log(message: "读取数据库错误")

printLog("读取数据库错误")

self.createTable()

}

}

if shouldReturn == true {

return

}

//            if let count = DatabaseOption().intForSql("Select count(*) from AldeloMessageRecord where messageGID = '\(message.MsgGID)'"), count > 0 {

//                //数据库里有这条消息,说明已经收到过,忽略掉

//                Log.shareInstance.log(message: "Websocket 收到重复消息,已忽略")

//                printLog("Websocket 收到重复消息,已忽略")

//                return

//            }

//发完ACK将message存到数据库

let aMessage = AldeloMessageRecord()

aMessage.messageGID = message.MsgGID

aMessage.type = message.Type

aMessage.time = message.Time

aMessage.publisher = message.Publisher

if message.Type == 1 { //text

guard let content = message.Content else { continue }

aMessage.message = message.Content

if content.hasPrefix("cmd::") {

let ar = content.components(separatedBy: "::")

var para: String? = nil

if ar.count == 3 {

para = ar[2]

}

let cmdString = "\(ar[0])::\(ar[1])".lowercased()

let command = AldeloCommand(rawValue: cmdString) ?? AldeloCommand.unknown

if command == .clinePrint {

if let ar = para?.components(separatedBy: ","), ar.count == 2 {

if let orderID = Int64(ar[0]), orderID > 0 {

gotPrintCommandBlock?([orderID],ar[1].boolValue(),true, message)

delegate?.receivedPrintCommand(axOrderIDs: [orderID], packingPrint: ar[1].boolValue(), isClientWebSocket: true)

}

}

} else {

gotCommandBlock?(command,para, message)

delegate?.receivedCommand(cmd: command,parameter: para,  message: message)

}

} else {

gotMessageBlock?(message)

delegate?.receivedMessage(message: message)

}

} else if message.Type == 2 { //print

guard let orderIDs = message.axOrderIDs else { return }

aMessage.message = "\(orderIDs)"

gotPrintCommandBlock?(orderIDs,true,false,message)

delegate?.receivedPrintCommand(axOrderIDs: orderIDs, packingPrint: true, isClientWebSocket: false)

} else if message.Type == 3 { //QR payment

guard let content = message.Content else { continue }

aMessage.message = content

gotQRPaymentBlock?(content)

delegate?.receivedQRPayment(content: content)

} else if message.Type == 4 { //cloud 强制反激活

printLog("cloud 强制反激活 .....")

}

DBPool.write { db in

try? aMessage.insert(db)

}

}

}

starscream地址:https://github.com/daltoniam/starscream

参考 Starscream 在 GitHub 上的项目主页 。

iOS 中使用 webSocket的更多相关文章

  1. iOS中支付宝集成

    iOS中支付宝集成 如今各种的App中都使用了三方支付的功能,现在将我在使用支付宝支付集成过程的心得分享一下,希望对大家都能有所帮助 要集成一个支付宝支付过程的环境,大致需要: 1>公司:先与支 ...

  2. iOS中数据库应用基础

    iOS 数据库入门 一.数据库简介 1.什么是数据库? 数据库(Database) 是按照数据结构来组织,存储和管理数据的仓库 数据库可以分为2大种类 关系型数据库(主流) PC端 Oracle My ...

  3. 正则表达式在iOS中的运用

    1.什么是正则表达式 正则表达式,又称正规表示法,是对字符串操作的一种逻辑公式.正则表达式可以检测给定的字符串是否符合我们定义的逻辑,也可以从字符串中获取我们想要的特定部分.它可以迅速地用极简单的方式 ...

  4. iOS 中的 HotFix 方案总结详解

    相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结.iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dynamic Framework(Ap ...

  5. iOS中使用正则

    一.什么是正则表达式 正则表达式,又称正规表示法,是对字符串操作的一种逻辑公式.正则表达式可以检测给定的字符串是否符合我们定义的逻辑,也可以从字符串中获取我们想要的特定部分.它可以迅速地用极简单的方式 ...

  6. IOS中div contenteditable=true无法输入

    在IOS中<div contenteditable="true"></div>中点击时可以弹出键盘但是无法输入.加一个样式-webkit-user-sele ...

  7. 谈谈iOS中的屏幕方向

    众所周知,iOS中提供了[UIDevice currentDevice].orientation与[UIApplication sharedApplication].statusBarOrientat ...

  8. iOS中assign、copy 、retain等关键字的含义

    iOS中assign.copy .retain等关键字的含义  转自:http://my.oschina.net/majiage/blog/267409 assign: 简单赋值,不更改索引计数cop ...

  9. Quartz 2D在ios中的使用简述二:创建画布

    在iOS中使用Quartz画图时,第一步就是要获取画布(图形上下文),然后再画布上做各种操作.先看下CoreGraphics.h这个头文件,就可以知道能够创建多少种上下文类型. #include &l ...

随机推荐

  1. 遍历json数据的几种方式。

    json(JavaScript Object Notation),json是一种多用于存储和交换文本信息的语法.他能够进行数据的传输,通常和ajax一起使用.它具有体积小.速度快,易解析等诸多优点. ...

  2. luogu P2515 [HAOI2010]软件安装 |Tarjan+树上背包

    题目描述 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一些软件安装到一台磁盘容量为MM计算机上,使得这些软件的价值尽可能大(即Vi的和最大). 但 ...

  3. Java 将Excel转为PDF

    本文将介绍在Java程序中如何将Excel工作簿转为PDF文档的,包括: 将整个工作簿转为PDF 将指定工作表转为PDF   使用工具:Free Spire.XLS for Java (免费版) Ja ...

  4. BZOJ2440完全平方数(莫比乌斯反演)

    Description 小 X 自幼就很喜欢数.但奇怪的是,他十分讨厌完全平方数.他觉得这些数看起来很令人难受.由此,他也讨厌所有是完全平方数的正整数倍的数.然而这丝毫不影响他对其他数的热爱. 这天是 ...

  5. swagger的配置

    // This method gets called by the runtime. Use this method to add services to the container. public ...

  6. Dubbo学习系列之七(分布式订单ID方案)

    既然选择,就注定风雨兼程! 开始吧! 准备:Idea201902/JDK11/ZK3.5.5/Gradle5.4.1/RabbitMQ3.7.13/Mysql8.0.11/Lombok0.26/Erl ...

  7. 贝壳2020——Java校招笔试题

    算法题4道: 题目描述: 给出n个正整数,要求找出相邻两个数字中差的绝对值最小的一对数字,如果有差的绝对值相同的,则输出最前面的一对数.(2<n<=100,正整数都在10^16范围内) 输 ...

  8. java面向对象基础知识

    一.面向对象与面向过程的区别: ①所处的角色不同:前者指挥者,后者执行者:②所需知道的事情不同:前者知道哪些是做这些事情的人就可以,不需了解具体的事情操作的过程.后者需要具备应有的技能来做这些事情. ...

  9. 11条MySQL规范,你知道的有几个?

    一.数据库命令规范 · 所有数据库对象名称必须使用小写字母并用下划线分割 · 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) · 数据库对象的命名 ...

  10. pv操作与信号量详解

    对于信号量,可以认为是一个仓库,有两个概念,容量和当前的货物个数. P操作从仓库拿货,如果仓库中没有货,线程一直等待,直到V操作,往仓库里添加了货物,为了避免P操作一直等待下去,会有一个超时时间. V ...