我一直在尝试获取主机的延迟,但一直卡住了。已经尝试过 Simple Ping,但似乎它没有返回延迟时间。最接近成功的是当我使用 MAC OS 的 TKC-PingTest 时。这个测试非常完美,但只能在 iPhone 模拟器上运行,因为在 iPhone 上使用时会出现错误,原因是 TKC 使用的补丁 "/sbin/ping"。除了这两个,我已经尝试了许多其他工具,但都没有收到任何结果。
以下是一个完整的工作示例,它可以对给定的地址进行一次精确的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使用主机,这会阻塞您的主线程,因此在单独的线程中调用它可能会很有用。
您可以轻松地扩展简单的ping来计算延迟。 Simpleping.h定义了SimplePingDelegate协议。 有两个感兴趣的方法 - didSendPacket
和didReceivePingResponsePacket
。 计时延迟的一个天真的实现是
@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
}
我认为这是一个很简陋的实现,因为它没有考虑到在收到响应之前另一个数据包被发送或数据包丢失的情况。为了解决这个问题,你需要检查数据包数据以确定发送和接收事件之间的序列号是否一致。
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)
}
}
我从@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)")
}
}
}
注意:
SimplePing.h
和SimplePing.m
文件到你的Xcode项目中#include "SimplePing.h"
这一行在 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
NSTimer
来计时从发送ping到接收响应所需的时间一样简单。 - lxt