获取主机的ping延迟

7
我一直在尝试获取主机的延迟,但一直卡住了。已经尝试过 Simple Ping,但似乎它没有返回延迟时间。最接近成功的是当我使用 MAC OS 的 TKC-PingTest 时。这个测试非常完美,但只能在 iPhone 模拟器上运行,因为在 iPhone 上使用时会出现错误,原因是 TKC 使用的补丁 "/sbin/ping"。除了这两个,我已经尝试了许多其他工具,但都没有收到任何结果。

为什么你不能只是适应你链接的简单Ping示例代码呢?这肯定就像添加一个NSTimer来计时从发送ping到接收响应所需的时间一样简单。 - lxt
我不认为我理解你的意思。我没有使用NSTimer。Simple Ping不会返回延迟。 - Lucas Brito
5个回答

20

以下是一个完整的工作示例,它可以对给定的地址进行一次精确的ping,并返回以毫秒为单位的ping时间:

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

以下是使用示例:

[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))
    }
}

用法:

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

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

为了方便起见,我正在使用SimplePing,如文档所述,它完全兼容iOS:

SimplePing在Mac OS X 10.7及更高版本上运行,尽管核心代码在所有版本的iOS上都可以正常工作,并且基础方法也适用于早期版本的Mac OS X(至少10.2)。

请注意,我正在使用单例模式,因为我需要多次检查延迟,但如果您只需要一次检查,您可以不使用单例实例。另外,SimplePing使用主机,这会阻塞您的主线程,因此在单独的线程中调用它可能会很有用。


我正在寻找的东西! :) - Alejandro Vargas
太棒了的实现! - Septronic
非常感谢你提供如此有用的示例。 - landonandrey
我可以使用上述代码添加ping的超时时间吗? - prabhu

8

您可以轻松地扩展简单的ping来计算延迟。 Simpleping.h定义了SimplePingDelegate协议。 有两个感兴趣的方法 - didSendPacketdidReceivePingResponsePacket。 计时延迟的一个天真的实现是

@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
}

我认为这是一个很简陋的实现,因为它没有考虑到在收到响应之前另一个数据包被发送或数据包丢失的情况。为了解决这个问题,你需要检查数据包数据以确定发送和接收事件之间的序列号是否一致。


我将NetworkTestViewController.h设置为@interface NetworkTestViewController : UITableViewController<SimplePingDelegate>并实现了这两个方法。当我启动pinger时,它不会调用我的类中的方法。 - Lucas Brito

4

hris.to的答案的Swift 3实现:

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

}

3

我从@hris.to处获取了一份漂亮的代码(感谢!),并将其更新到最新的Swift版本。想分享一下:

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
    }
}

使用方法:

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)")
        }
    }
}

注意:

  • developer.apple.com SimplePing中添加SimplePing.hSimplePing.m文件到你的Xcode项目中
  • 让Xcode创建桥接头文件并添加#include "SimplePing.h"这一行

我可以使用上述代码添加ping的超时时间吗? - prabhu
看起来SimplePing没有超时的API,所以你必须自己实现。 - Andy

0

在 Swift 5 中,如何正确编程以迭代包含 3 个 URL 的数组的 SimplePingclient 使用方式?

    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)")
    }

这提供了控制台日志记录:

0 : 1.1.1.1
count : 1
1 : url1
count : 2
2 : apps.apple.com
count : 3
Latency: 16.40796661376953
countsuccess : 1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接