Swift - PingManager,同时ping多个域名

2018-12-08  本文已影响46人  小点草

  因为项目需求,需要同时对几十个服务器进行短时间(2秒内)的高频(每0.2秒发一次)ping,获取各个服务器的平均速度。在网上找了很久,都没有合适的库。
  开始的时候用官方的SimplePing,但是这个库会阻塞主线程,导致app出现卡顿。
  然后找到了一个基于OC的库GBPing,这个库试用了一下,它并不会阻塞主线程,而是独立创建两条线程用于收发包,结果处理后返回给主线程再做相应的操作。但是,在我同时ping几十条服务器的时候,它就有可能出现闪退,原因是短时间内调用的线程资源太多,导致内存不足,最后出现闪退。
  为了解决以上问题,并且将其转换成swift语言,根据GBPing的收发包原理,决定自定义一个PingMananger类并重写GBPing,这个类主要用于管理多个ping实例,把它们的初始化和收发包都控制在固定的线程中,最大化减少线程资源的浪费,这个类有三个自定义的线程,一个用于ping的icmp包初始化,一个用于发包,一个用于收包。
效果图:

单个域名.gif 多个域名.gif

  以下介绍使用步骤,顺便介绍一下原理。
1、首先创建一个ping实例,并设置代理和目标地址,然后附加到PingManager的pings数组上

let ping = Ping()
ping.delegate = self
ping.host = ip
PingMannager.shared.add(ping)

设置代理后需要实现相应的代理方法。

2、调用PingManager的setup

PingMannager.shared.setup {
          
}

查看setup方法的实现:

  func setup(_ callBack:(()->())? = nil){
        var newPings = pings
        
        if self.setupThread == nil{
            self.setupThread = Thread(target: self, selector: #selector(self.disposeAction), object: nil)
            self.setupThread?.name = "disposeThread"
            self.isSettingUp = true
            self.setupThread?.start()
        }
        for ping in pings{
            readyGroup.enter()
            weak var weakPing = ping
            let setupBlock = {()->() in
                weakPing?.setup { (success, error) in
                    if success{
                        weakPing?.startPinging()
                    }else{
                        newPings.removeAll(where: { (delete) -> Bool in
                            return delete.host == weakPing?.host
                        })
                    }
                    self.readyGroup.leave()
                }
            }
            self.disposeBlocks.append(setupBlock)
        }
        readyGroup.notify(queue: readyQueue) {
            self.isSettingUp = false
            self.disposeBlocks.removeAll()
            self.sendThread?.cancel()
            self.setupThread = nil
            self.pings = newPings
            callBack?()
        }
    }

首先创建了一个初始化线程,用来处理Ping实例的初始化,遍历pings数组,将每个ping的初始化都放到初始化线程中,其中使用了DispatchGroup来确保所有ping的初始化完成后,才执行callBack回调。
3、在回调中设置timeout(超时时间)和period(ping间隔),以及开始ping

PingMannager.shared.setup {
            PingMannager.shared.timeout = 1
            PingMannager.shared.pingPeriod = 1
            PingMannager.shared.startPing()
 }

startPing的实现如下:

func startPing(){
        if !self.isPinging{
            self.isPinging = true
            if self.sendThread == nil{
                self.sendThread = Thread(target: self, selector: #selector(self.sendAction), object: nil)
                self.sendThread?.name = "sendThread"
                self.listenThread = Thread(target: self, selector: #selector(self.listenAction), object: nil)
                self.listenThread?.name = "listenThread"
                self.listenThread?.start()
                self.sendThread?.start()
            }
        }
    }

初始化了两个线程,并在线程中分别调用了sendAction和listenActionl两个方法,当开始ping的时候,sendAction方法会进入第一级循环,这一级每循环一次,所有ping实例都会发一次包,然后通过对比开始发包的时间和当前时间来判断是否进入下一次循环:

@objc private func sendAction(){
        autoreleasepool {
            var pings = self.pings
            while isPinging,pings.count > 0{
                var i = 0
                let runUntil = CFAbsoluteTimeGetCurrent() + self.pingPeriod;
                while i < pings.count{
                    let ping = pings[i]
                    ping.send()
                    if ping.isPinging == false{
                        pings.remove(at: i)
                        i -= 1
                    }
                    i += 1
                }
                var time : TimeInterval = 0;
                while (runUntil > time) {
                    let runUntilDate = Date(timeIntervalSinceReferenceDate: runUntil)
                    RunLoop.current.run(until: runUntilDate)
                    time = CFAbsoluteTimeGetCurrent();
                }
            }
            self.sendThread?.cancel()
            self.sendThread = nil
        }
    }

和上面的原理差不多,但是并不会限制其接收间隔,都放到同一线程会不会出现丢包情况,有待测试。

@objc private func listenAction(){
        autoreleasepool {
            var pings = self.pings
            while isPinging,pings.count > 0{
                var i = 0
                while i < pings.count{
                    let ping = pings[i]
                    ping.listenOnce()
                    if ping.isPinging == false{
                        pings.remove(at: i)
                        i -= 1
                    }
                    i += 1
                }
            }
            self.listenThread?.cancel()
            self.listenThread = nil
        }
    }

总结一下使用步骤:

for host in hostArray{
            let ping = Ping()
            ping.delegate = self
            ping.host = host
            PingMannager.shared.add(ping)
}
PingMannager.shared.setup {
            PingMannager.shared.timeout = self.timeout
            PingMannager.shared.pingPeriod = self.period
            PingMannager.shared.startPing()
}

很简单吧,有需要就下载使用吧
PingManager
如有问题,请联系289193866@qq.com

上一篇下一篇

猜你喜欢

热点阅读