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 上的项目主页 。

转载于:https://www.cnblogs.com/duzhaoquan/p/11009034.html

iOS 中使用 webSocket相关推荐

  1. HTML5中的websocket图片直播

    HTML5中的websocket实现直播 1. videojs-contrib-hls 直播功能         github:https://github.com/videojs/videojs-c ...

  2. ios中常用的第三方库

    下拉刷新 EGOTableViewPullRefresh – 最早的下拉刷新控件. SVPullToRefresh – 下拉刷新控件. MJRefresh – 仅需一行代码就可以为UITableVie ...

  3. iphone smtp服务器没有响应,电子邮件卡在iPhone或iPad上的发件箱?如何修复iOS中的未发送邮件 | MOS86...

    您曾经在iOS中发送电子邮件,只能将信息卡在iPhone,iPad或iPod touch的邮件应用发件箱中?你知道这是什么时候发生的,因为在iOS的Mail应用程序的底部,状态栏在iOS中显示1个未发 ...

  4. mui ios中form表单中点击输入框头部导航栏被推起及ios中form表单中同时存在日期选择及输入框时,日历选择页面错乱bug...

    一.ios header导航栏被推起解决方法 1 设置弹出软键盘时自动改变webview的高度 plus.webview.currentWebview().setStyle({ softinputMo ...

  5. iOS中UISearchBar(搜索框)使用总结

    2019独角兽企业重金招聘Python工程师标准>>> iOS中UISearchBar(搜索框)使用总结 初始化:UISearchBar继承于UIView,我们可以像创建View那样 ...

  6. iOS中几种数据持久化方案总结

    概论 所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案: plist文件(属性列表) ...

  7. iOS中关于NSTimer使用知多少

    看到这个标题,你可能会想NSTimer不就是计时器吗,谁不会用,不就是一个能够定时的完成任务的东西吗? 我想说你知道NSTimer会retain你添加调用方法的对象吗?你知道NSTimer是要加到ru ...

  8. iOS中JS 与OC的交互(JavaScriptCore.framework)

    iOS中实现js与oc的交互,目前网上也有不少流行的开源解决方案: 如:react native 当然一些轻量级的任务使用系统提供的UIWebView 以及JavaScriptCore.framewo ...

  9. 在iOS中使用tableView

    为什么80%的码农都做不了架构师?>>>    UITableView是iOS中最常用的控件了,所以使用起来也很简单. ViewContoller.h 文件 (继承UITableVi ...

  10. 关于ios中编译ffmpeg0.9.2库

    很多朋友在问如何在ios中编译ffmpeg库,虽说网上的教程很多,但是大部分都说按其操作,最后编译总是不成功,正好我最近的项目要用到ffmpeg,所以就再次编译了,同时在这里记下,方便需要参考的朋友. ...

最新文章

  1. ExtJS ComboBox 异步读取项后默认选中某项
  2. getCurrentSession()与openSession()的区别?
  3. PMCAFF推出咖啡日报 更多好内容等你来发现
  4. [编程题] 迷路的牛牛
  5. SNMP模型中,网管者、网管代理、网管协议及管理信息库MIB之间的工作流程
  6. boost::log::filter用法的测试程序
  7. 遭遇DBD::mysql::dr::imp_data_size unexpectedly
  8. Ubuntu下命令行cd进不了/home/用户目录
  9. Alex Hanna博士:Google道德AI小组研究员
  10. windows 下编译 jrtplib-3.9.1 和 jthread-1.3.1
  11. Promise源码学习(2)
  12. oracle 游标的替代,Oracle中replace函数和translate函数以及简单的游标
  13. 《DSP using MATLAB》示例Example 8.10
  14. 快速傅里叶变换python_图像傅里叶变换快速实现 python
  15. Multispectral Deep Neural Networks for Pedestrian Detection(BMVC 2016)论文解读
  16. 手机通讯录分组名称_个人通讯录如何批量导入/导出
  17. opencv录制视频 python_OpenCV Python 录制视频
  18. 4K工业级高清4进1出DP自动USB KVM多电脑切换器(MT-PK401)
  19. 【转】十大顶级奢侈品服装品牌
  20. 反相放大电路反馈电阻上并联电容的作用 [转载]

热门文章

  1. python实用程序育儿法下载_Python高级编程
  2. OVM虚拟化,做Openstack的减法
  3. Kali系列之网卡地址配置
  4. mysql的备份与还原步骤_MySQL备份与还原
  5. 博士申请 | 美国佐治亚理工学院陶默雷教授招收机器学习方向全奖博士生
  6. 笔记本无线共享上网(网络是有线)
  7. Latex各种箭头符号总结
  8. 大学生计算机自我鉴定500字,大学生计算机专业的自我鉴定范文
  9. ubuntu: 安装 摄像头驱动
  10. Java 总结4 数据流 文件处理