iOS使用webSocket通信

一、什么是webSocket

webSocket是HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。对大部分web开发者来说,上面这段描述有点枯燥,其实只要记住几点:

  1. WebSocket可以在浏览器里使用
  2. 支持双向通信
  3. 使用很简单

有哪些优点

说到优点,这里的对比参照物是HTTP协议,概括地说就是:支持双向通信,更灵活,更高效,可扩展性更好。

  1. 支持双向通信,实时性更强。
  2. 更好的二进制支持。
  3. 较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不包含头部的情况下,服务端到客户端的包头只有2~10字节(取决于数据包长度),客户端到服务端的的话,需要加上额外的4字节的掩码。而HTTP协议每次通信都需要携带完整的头部。
  4. 支持扩展。ws协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议。(比如支持自定义压缩算法等)

二、iOS中如何使用webSocket

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

1、根据url创建socket

Swift 复制代码
            var request = URLRequest(url: URL(string: "url")!)

            request.timeoutInterval = 5//超时时间

            socket = WebSocket(request: request)

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

2、发送消息

Swift 复制代码
socket.write(string: sendStr)

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

Swift 复制代码
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {

//接收到字符串消息

}

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

        printLog("\(data)")//接收到data消息

}

三、常见问题

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

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

Swift 复制代码
 func sendTextTo(deviceIDs: [String], text: String) {

        if socket == nil  {

            return

        }

        if socket.isConnected == false {

            return

        }

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

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

            socket.write(string: sendStr)

        }

    }

2.如何保持链接

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

Swift 复制代码
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

            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) {

        

                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

Swift 复制代码
func websocketDidReceiveMessage(socket: WebSocketClient, text: String) {

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

        lastReceivedMessageTime = Date().timeIntervalSince1970


        if text == "$" {

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

            return

        }

        

        guard let messageArray = DQMessage.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 MessageRecord 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 MessageRecord where messageGID = '\(message.MsgGID)'"), count > 0 {

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

                        
                        shouldReturn = true

                    }

                } catch {

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

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

                    self.createTable()

                }

            }

            

            if shouldReturn == true {

                return

            }

            

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

            let aMessage = essageRecord()

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

相关推荐
久绊A29 分钟前
网络信息系统的整个生命周期
网络
_PowerShell34 分钟前
[ DOS 命令基础 3 ] DOS 命令详解-文件操作相关命令
网络·dos命令入门到精通·dos命令基础·dos命令之文件操作命令详解·文件复制命令详解·文件对比命令详解·文件删除命令详解·文件查找命令详解
_.Switch3 小时前
高级Python自动化运维:容器安全与网络策略的深度解析
运维·网络·python·安全·自动化·devops
qq_254674413 小时前
工作流初始错误 泛微提交流程提示_泛微协同办公平台E-cology8.0版本后台维护手册(11)–系统参数设置
网络
JokerSZ.3 小时前
【基于LSM的ELF文件安全模块设计】参考
运维·网络·安全
小松学前端6 小时前
第六章 7.0 LinkList
java·开发语言·网络
城南vision6 小时前
计算机网络——TCP篇
网络·tcp/ip·计算机网络
Ciderw6 小时前
块存储、文件存储和对象存储详细介绍
网络·数据库·nvme·对象存储·存储·块存储·文件存储
石牌桥网管6 小时前
OpenSSL 生成根证书、中间证书和网站证书
网络协议·https·openssl