iOS 中使用 webSocket
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的更多相关文章
- iOS中支付宝集成
iOS中支付宝集成 如今各种的App中都使用了三方支付的功能,现在将我在使用支付宝支付集成过程的心得分享一下,希望对大家都能有所帮助 要集成一个支付宝支付过程的环境,大致需要: 1>公司:先与支 ...
- iOS中数据库应用基础
iOS 数据库入门 一.数据库简介 1.什么是数据库? 数据库(Database) 是按照数据结构来组织,存储和管理数据的仓库 数据库可以分为2大种类 关系型数据库(主流) PC端 Oracle My ...
- 正则表达式在iOS中的运用
1.什么是正则表达式 正则表达式,又称正规表示法,是对字符串操作的一种逻辑公式.正则表达式可以检测给定的字符串是否符合我们定义的逻辑,也可以从字符串中获取我们想要的特定部分.它可以迅速地用极简单的方式 ...
- iOS 中的 HotFix 方案总结详解
相信HotFix大家应该都很熟悉了,今天主要对于最近调研的一些方案做一些总结.iOS中的HotFix方案大致可以分为四种: WaxPatch(Alibaba) Dynamic Framework(Ap ...
- iOS中使用正则
一.什么是正则表达式 正则表达式,又称正规表示法,是对字符串操作的一种逻辑公式.正则表达式可以检测给定的字符串是否符合我们定义的逻辑,也可以从字符串中获取我们想要的特定部分.它可以迅速地用极简单的方式 ...
- IOS中div contenteditable=true无法输入
在IOS中<div contenteditable="true"></div>中点击时可以弹出键盘但是无法输入.加一个样式-webkit-user-sele ...
- 谈谈iOS中的屏幕方向
众所周知,iOS中提供了[UIDevice currentDevice].orientation与[UIApplication sharedApplication].statusBarOrientat ...
- iOS中assign、copy 、retain等关键字的含义
iOS中assign.copy .retain等关键字的含义 转自:http://my.oschina.net/majiage/blog/267409 assign: 简单赋值,不更改索引计数cop ...
- Quartz 2D在ios中的使用简述二:创建画布
在iOS中使用Quartz画图时,第一步就是要获取画布(图形上下文),然后再画布上做各种操作.先看下CoreGraphics.h这个头文件,就可以知道能够创建多少种上下文类型. #include &l ...
随机推荐
- MySQL必知必会(Insert into)
########################## # Populate customers table ########################## INSERT INTO custome ...
- SpringBoot中JdbcTemplate
步骤如下: 依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId& ...
- Codeforces Round #595 (Div. 3) D2Too Many Segments,线段树
题意:给n个线段,每个线段会覆盖一些点,求删最少的线段,使得每个点覆盖的线段不超过k条. 思路:按右端点排序,之后依次加入每个线段,查询线段覆盖区间内的每个点,覆盖的最大线段数量,如果不超过k,那就可 ...
- 《Java基础知识》Java super关键字
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类. super也有三种用法: 1.普通的直接引用 与this类似,super相当于是指向当前对象的父类,这样 ...
- 《Java基础知识》Java异常处理详解
1. Java 中的异常 前言:Java 中的异常处理是处理程序运行错误时的强大机制之一,它可以保证应用程序的正常流程. 首先我们将了解java异常.异常的类型以及受查和非受查异常之间的区别. 1.1 ...
- Base64编码原理及应用
最近在做一个H5上传图片并压缩的项目,其过程主要是先将图片上传通过readAsDataURL获取上传图片base64编码,然后根据高宽比将图片画到canvas上实现压缩,在通过toDataURL获取压 ...
- JVM CPU Profiler技术原理及源码深度解析
研发人员在遇到线上报警或需要优化系统性能时,常常需要分析程序运行行为和性能瓶颈.Profiling技术是一种在应用运行时收集程序相关信息的动态分析手段,常用的JVM Profiler可以从多个方面对程 ...
- Winform中实现拖拽文件到ListView获取文件类型(附代码下载)
场景 效果 注: 博客主页: https://blog.csdn.net/badao_liumang_qizhi关注公众号 霸道的程序猿 获取编程相关电子书.教程推送与免费下载. 实现 新建一个for ...
- 利用ExecuteMultipleRequest来批量导入数据,成功的成功失败的失败,并生成导入结果文件
我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...
- iOS 和 H5 页面交互(WKWebview 和 UIWebview cookie 设置)
iOS 和 H5 页面交互(WKWebview 和 UIWebview cookie 设置) 主要记录关于cookie相关的坑 1. UIWebview 1. UIWebview 相对比较简单 直接通 ...