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 上的項目主頁 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM