Swift - PingManager,同时ping多个域名

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

Swift - PingManager,同时ping多个域名_第1张图片
单个域名.gif
Swift - PingManager,同时ping多个域名_第2张图片
多个域名.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 = self.pings
        let pings = self.pings
        weak var weakSelf = self
        
        mainQueue.async {
            for ping in pings{
                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
                            })
                        }
                        
                    }
                }
                setupBlock()
            }
            weakSelf?.isSettingUp = false
            
            weakSelf?.pings = newPings
            callBack?()
        }
        
    }

在PingManager的主线程队列中处理Ping实例的初始化。
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
            send()
            listen()
        }
    }

这个不用解释太多,重点是send和listen两个方法:

private func send(){
        weak var weakSelf =  self
        mainQueue.async {
            if weakSelf?.isPinging == true{
                weakSelf?.pings.removeAll(where: { (ping) -> Bool in
                    return ping.isPinging == false
                })
                if weakSelf?.pings.count ?? 0 > 0 {
                    let pings = self.pings
                    weakSelf?.sendQueue.async {
                        autoreleasepool{
                            let runUntil = CFAbsoluteTimeGetCurrent() + (weakSelf?.pingPeriod ?? 1)
                            for ping in pings{
                                ping.send()
                            }
                            var time : TimeInterval = 0;
                            while (runUntil > time) {
                                let runUntilDate = Date(timeIntervalSinceReferenceDate: runUntil)
                                RunLoop.current.run(until: runUntilDate)
                                time = CFAbsoluteTimeGetCurrent()
                            }
                            weakSelf?.send()
                        }
                    }
                }
                
            }
        }
        
    }
private func listen(){
        weak var weakSelf =  self
        mainQueue.async {
            if weakSelf?.isPinging == true{
                weakSelf?.pings.removeAll(where: { (ping) -> Bool in
                    return ping.isPinging == false
                })
                if weakSelf?.pings.count ?? 0 > 0 {
                    let pings = self.pings
                    weakSelf?.listenQueue.async {
                        autoreleasepool{
                            for ping in pings{
                                ping.listenOnce()
                            }
                            weakSelf?.listen()
                        }
                    }
                }
                
            }
        }
    }

首先在主队列中处理send和listen队列都用到的属性,然后在send队列中开始发送icmp包,发送完后等待下次发送时间,然后再次发送。linsten方法也是差不多,唯一不同的是不需要等待。

总结一下使用步骤:

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
如有问题,请联系[email protected]

你可能感兴趣的:(Swift - PingManager,同时ping多个域名)