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

  • 相关阅读:
    十万个为什么
    安装VmwareTools遇到的问题
    CentOS7没有ifconfig命令怎么办
    ftp/ http/ https/ tcp的关系
    C/S和B/S架构
    Nginx 安装以及验证教程
    osi七层模型
    在linux上安装tenginx
    Awvs、Snort的下载安装
    Laravel——DI(依赖注入)
  • 原文地址:https://www.cnblogs.com/duzhaoquan/p/11009034.html
Copyright © 2020-2023  润新知