Get ping latency from host
Asked Answered
M

5

7

I'm trying to get the latency from host for a pretty good time and I'm stuck in. Already tried Simple Ping , but seems it doesn't return the latency. The closest I've done was when I use the TKC-PingTest for MAC OS. That works perfect but only in the iPhone Simulator because when use the iPhone I get an error due the patch "/sbin/ping" TKC uses. Besides these two, I already tried many others and got nothing.

Mousetail answered 18/3, 2014 at 3:28 Comment(2)
Why can't you just adapt the Simple Ping example code you linked to? It is surely as simple as adding a NSTimer to time how long it takes from sending out your ping to receiving a response?Schistosome
I don't think I understand you. I didn't use NSTimer. The Simple Ping doesn't return the latency.Mousetail
B
8

You can easily extend simple ping to calculate the latency. Simpleping.h defines the SimplePingDelegate protocol. There are two methods of interest - didSendPacket and didReceivePingResponsePacket. A naive implementation for timing the latency would be

@property (strong,nonatomic) NSDate *start;

- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet
{
    self.start=[NSDate date];
}

- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet
{
    NSDate *end=[NSDate date];
    double latency = [end timeIntervalSinceDate:self.start]*1000.0;

    //TODO - Do something with latency
}

I say this is a niave implementation because it doesn't deal with the case where another packet is sent before the response is received or where packets are dropped. To deal with this you would need to examine the packet data to determine whether the sequence number was consistent between the send and receive events.

Bodine answered 18/3, 2014 at 5:35 Comment(1)
I set my NetworkTestViewController.h to @interface NetworkTestViewController : UITableViewController<SimplePingDelegate> and implement these two methods. When I start the pinger it doesn't call the methods in my class.Mousetail
S
20

Following is full working example which pings exactly once given address and then returns ping time in miliseconds:

Objective-C

@interface SimplePingClient : NSObject<SimplePingDelegate>

+(void)pingHostname:(NSString*)hostName andResultCallback:(void(^)(NSString* latency))result;

@end

@interface SimplePingClient()
{
    SimplePing* _pingClient;
    NSDate* _dateReference;
}

@property(nonatomic, strong) void(^resultCallback)(NSString* latency);

@end

@implementation SimplePingClient

+(void)pingHostname:(NSString*)hostName andResultCallback:(void(^)(NSString* latency))result
{
    static SimplePingClient* singletonPC = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        singletonPC = [[SimplePingClient alloc] init];
    });

    //ping hostname
    [singletonPC pingHostname:hostName andResultCallBlock:result];
}

-(void)pingHostname:(NSString*)hostName andResultCallBlock:(void(^)(NSString* latency))result
{
    _resultCallback = result;
    _pingClient = [SimplePing simplePingWithHostName:hostName];
    _pingClient.delegate = self;
    [_pingClient start];
}

#pragma mark - SimplePingDelegate methods
- (void)simplePing:(SimplePing *)pinger didStartWithAddress:(NSData *)address
{
    [pinger sendPingWithData:nil];
}

- (void)simplePing:(SimplePing *)pinger didFailWithError:(NSError *)error
{
    _resultCallback(nil);
}

- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet
{
    _dateReference = [NSDate date];
}

- (void)simplePing:(SimplePing *)pinger didFailToSendPacket:(NSData *)packet error:(NSError *)error
{
    [pinger stop];
    _resultCallback(nil);
}

- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet
{
    [pinger stop];
    NSDate *end=[NSDate date];
    double latency = [end timeIntervalSinceDate:_dateReference] * 1000;//get in miliseconds
    _resultCallback([NSString stringWithFormat:@"%.f", latency]);
}

- (void)simplePing:(SimplePing *)pinger didReceiveUnexpectedPacket:(NSData *)packet
{
    [pinger stop];
    _resultCallback(nil);
}

@end

And example usage is as follows:

[SimplePingClient pingHostname:@"www.apple.com"
             andResultCallback:^(NSString *latency) {

                 NSLog(@"your latency is: %@", latency ? latency : @"unknown");

             }];

Swift

import Foundation

public typealias SimplePingClientCallback = (String?)->()

public class SimplePingClient: NSObject {
    static let singletonPC = SimplePingClient()

    private var resultCallback: SimplePingClientCallback?
    private var pingClinet: SimplePing?
    private var dateReference: NSDate?

    public static func pingHostname(hostname: String, andResultCallback callback: SimplePingClientCallback?) {
        singletonPC.pingHostname(hostname, andResultCallback: callback)
    }

    public func pingHostname(hostname: String, andResultCallback callback: SimplePingClientCallback?) {
        resultCallback = callback
        pingClinet = SimplePing(hostName: hostname)
        pingClinet?.delegate = self
        pingClinet?.start()
    }
}

extension SimplePingClient: SimplePingDelegate {
    public func simplePing(pinger: SimplePing!, didStartWithAddress address: NSData!) {
        pinger.sendPingWithData(nil)
    }

    public func simplePing(pinger: SimplePing!, didFailWithError error: NSError!) {
        resultCallback?(nil)
    }

    public func simplePing(pinger: SimplePing!, didSendPacket packet: NSData!) {
        dateReference = NSDate()
    }

    public func simplePing(pinger: SimplePing!, didFailToSendPacket packet: NSData!, error: NSError!) {
        pinger.stop()
        resultCallback?(nil)
    }

    public func simplePing(pinger: SimplePing!, didReceiveUnexpectedPacket packet: NSData!) {
        pinger.stop()
        resultCallback?(nil)
    }

    public func simplePing(pinger: SimplePing!, didReceivePingResponsePacket packet: NSData!) {
        pinger.stop()

        guard let dateReference = dateReference else { return }

        //timeIntervalSinceDate returns seconds, so we convert to milis
        let latency = NSDate().timeIntervalSinceDate(dateReference) * 1000

        resultCallback?(String(format: "%.f", latency))
    }
}

Usage:

SimplePingClient.pingHostname("www.apple.com") { latency in

            print("Your latency is \(latency ?? "unknown")")
        }

Just for convenience I'm using SimplePing which as stated in docs is fully compatible with iOS:

SimplePing runs on Mac OS X 10.7 and later, although the core code works just fine on all versions of iOS and the underlying approach works on earlier versions of Mac OS X (back to 10.2).

Please note that I'm using singleton, as I repeatedly check latency, however if you need this just once you can adopt it without singleton instance. Also SimplePing uses hosts, which will block your main thread so calling it in separate thread might be useful.

Salon answered 15/9, 2014 at 8:13 Comment(2)
Fantastic implementation!Tautomer
Can I add a timeout for the ping using the above code?Bavaria
B
8

You can easily extend simple ping to calculate the latency. Simpleping.h defines the SimplePingDelegate protocol. There are two methods of interest - didSendPacket and didReceivePingResponsePacket. A naive implementation for timing the latency would be

@property (strong,nonatomic) NSDate *start;

- (void)simplePing:(SimplePing *)pinger didSendPacket:(NSData *)packet
{
    self.start=[NSDate date];
}

- (void)simplePing:(SimplePing *)pinger didReceivePingResponsePacket:(NSData *)packet
{
    NSDate *end=[NSDate date];
    double latency = [end timeIntervalSinceDate:self.start]*1000.0;

    //TODO - Do something with latency
}

I say this is a niave implementation because it doesn't deal with the case where another packet is sent before the response is received or where packets are dropped. To deal with this you would need to examine the packet data to determine whether the sequence number was consistent between the send and receive events.

Bodine answered 18/3, 2014 at 5:35 Comment(1)
I set my NetworkTestViewController.h to @interface NetworkTestViewController : UITableViewController<SimplePingDelegate> and implement these two methods. When I start the pinger it doesn't call the methods in my class.Mousetail
S
4

Swift 3 implementation of hris.to's answer:

import Foundation
public typealias SimplePingClientCallback = (String?)->()

public class SimplePingClient: NSObject {
    fileprivate static let singletonPC = SimplePingClient()

    fileprivate var resultCallback: SimplePingClientCallback?
    fileprivate var pingClinet: SimplePing?
    fileprivate var dateReference: Date?

    public static func pingHostname(hostname: String, andResultCallback callback: SimplePingClientCallback?) {
        singletonPC.pingHostname(hostname: hostname, andResultCallback: callback)
    }

    public func pingHostname(hostname: String, andResultCallback callback:  SimplePingClientCallback?) {
        resultCallback = callback
        pingClinet = SimplePing(hostName: hostname)
        pingClinet?.delegate = self
        pingClinet?.start()
    }
}

extension SimplePingClient: SimplePingDelegate {

    public func simplePing(_ pinger: SimplePing, didSendPacket packet: Data, sequenceNumber: UInt16){
        dateReference = Date()
    }

    public func simplePing(_ pinger: SimplePing, didStartWithAddress address: Data) {
        pinger.send(with: nil)
    }

    public func simplePing(_ pinger: SimplePing, didFailWithError error: Error) {
        resultCallback?(nil)
    }

    public func simplePing(_ pinger: SimplePing, didReceiveUnexpectedPacket packet: Data) {
        pinger.stop()
        resultCallback?(nil)
    }

    public func simplePing(_ pinger: SimplePing, didReceivePingResponsePacket packet: Data, sequenceNumber: UInt16) {
        pinger.stop()
        guard let dateReference = dateReference else { return }

      //timeIntervalSinceDate returns seconds, so we convert to milis
        let latency = Date().timeIntervalSince(dateReference) * 1000
        resultCallback?(String(format: "%.f", latency))
    }

    public func simplePing(_ pinger: SimplePing, didFailToSendPacket packet: Data, sequenceNumber: UInt16, error: Error) {
        pinger.stop()
        resultCallback?(nil)
    }

}
Superabound answered 7/12, 2016 at 23:13 Comment(0)
S
4

I took the beautiful code from @hris.to (Thanks!) and updated it to the latest Swift version. Thought I'd share:

Swift 5.2

public class SimplePingClient: NSObject {
    public typealias PingResultCompletion = (Result<Double, Error>) -> Void

    static let singletonPC = SimplePingClient()

    private var completion: PingResultCompletion?
    private var pingClient: SimplePing?
    private var dateReference: Date?

    public static func ping(hostname: String, completion: PingResultCompletion?) {
        singletonPC.ping(hostname: hostname, completion: completion)
    }

    public func ping(hostname: String, completion: PingResultCompletion?) {
        self.completion = completion
        pingClient = SimplePing(hostName: hostname)
        pingClient?.delegate = self
        pingClient?.start()
    }
}

extension SimplePingClient: SimplePingDelegate {
    public func simplePing(_ pinger: SimplePing, didStartWithAddress address: Data) {
        pinger.send(with: nil)
    }

    public func simplePing(_ pinger: SimplePing, didFailWithError error: Error) {
        completion?(.failure(error))
    }

    public func simplePing(_ pinger: SimplePing, didSendPacket packet: Data, sequenceNumber: UInt16) {
        dateReference = Date()
    }

    public func simplePing(_ pinger: SimplePing, didFailToSendPacket packet: Data, sequenceNumber: UInt16, error: Error) {
        pinger.stop()
        completion?(.failure(error))
    }

    public func simplePing(_ pinger: SimplePing, didReceiveUnexpectedPacket packet: Data) {
        pinger.stop()
        completion?(.failure(PingError.receivedUnexpectedPacket))
    }

    public func simplePing(_ pinger: SimplePing, didReceivePingResponsePacket packet: Data, sequenceNumber: UInt16) {
        pinger.stop()
        guard let dateReference = dateReference else { return }

        //timeIntervalSinceDate returns seconds, so we convert to milis
        let latency = Date().timeIntervalSince(dateReference) * 1000
        completion?(.success(latency))
    }

    enum PingError: Error {
        case receivedUnexpectedPacket
    }
}

Usage:

func pingApple() {
    SimplePingClient.ping(hostname: "www.apple.com") { result in
        switch result {
        case .success(let latency):
            print("Latency: \(latency)")
        case .failure(let error):
            print("Ping got error: \(error.localizedDescription)")
        }
    }
}

Notes:

  • add to your Xcode project the SimplePing.h and SimplePing.m files from developer.apple.com SimplePing
  • let Xcode create a bridging header and add the line #include "SimplePing.h"
Sweepback answered 3/5, 2020 at 15:7 Comment(2)
Can I add a timeout for the ping using the above code?Bavaria
looks like SimplePing does not have api for a timeout. so you will have to do it yourself.Sweepback
P
0

What would be the correct way to program this usage of SimplePingclient to iterate over an array containing 3 url's for Swift 5?

    let hostnames:[String] = ["url0","url1","url2"]
    var i: Int = 0
    var counterror: Int = 0
    var countsuccess: Int = 0
    
    for (index, element) in hostnames.enumerated() {
      
        print(index, ":", element)
        SimplePingClient.ping(hostname: element) { result in
            switch result {
            case .success(let latency):
                print("Latency: \(latency)")
                countsuccess += 1
                print("countsuccess : \(countsuccess)")
            case .failure(let error):
                print("Error: \(error)")
                counterror += 1
                print("counterror : \(counterror)")
            }
        }
        i += 1
        print("count : \(i)")
    }

This provides the console logging:

0 : 1.1.1.1
count : 1
1 : url1
count : 2
2 : apps.apple.com
count : 3
Latency: 16.40796661376953
countsuccess : 1
Ping answered 2/10, 2022 at 8:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.