我的网络中有很多交换机,我尝试检查它们是否在线。我用一个小的 UDP 数据包来做这个,他们用他们自己的 UDP 数据包来响应,告诉我他们在那里。实际上它只是一个开关模拟20000进行测试,但这并不重要。
因为我不喜欢在不需要的情况下工作低级别,所以我使用https://github.com/robbiehanson/CocoaAsyncSocket来处理 UDP 的事情。
它工作......几乎。当我重复 ping 例如 10 个开关时(我同时 ping 它们,然后等待所有响应),它似乎在第一个周期工作,但在几个周期后它失控了,几乎所有似乎都没有响应. 哪个是哪个不是完全随机的。当我添加更多开关时它会变得更糟(这意味着它从第一个周期开始不起作用)。使用循环我:我为所有开关发送 ping,然后等待所有答案(或超时),然后再次发送 ping。
当我使用数据包嗅探器检查我的网络流量时,我看到对于“错误”的交换机(意味着它们显示为离线 == 超时,但实际上是在线的),每个交换机都有三种可能的情况:
- 来自 iPhone 的数据包永远不会发送,因此没有收到任何答复。
- iPhone 发送数据包,但交换机没有响应。
- 收到了应答包,但 iPhone 没有读取它。
这是完全随机的,它们在一个循环中混合。
当所有交换机实际上都在线时,问题被最小化(需要更多周期才能出现)但仍然存在。
由于代码是异步的,因此很有可能出现错误,我认为肯定存在某种竞争条件或其他东西。
我将在这里发布我的 ping 课程。这不是很复杂。我盯着这个看了几个小时,但找不到错误。
用法如下:您创建一个实例:
- (id)initWithTimeout:(NSTimeInterval)timeout delegate:(id<NMASwitchPingerDelegate>)delegate;
比您只需为要 ping 的每个 ipAddress 调用以下方法。该方法立即返回。这意味着 ping 是同时处理的:
- (void)sendPingToAddress:(NSString*)address;
为方便起见,我将其添加到粘贴箱中:http: //pastebin.com/0LuiXsXY
#import "NMASwitchPinger.h"
/**
* Private interface.
*/
@interface NMASwitchPinger () {
/**
* The delegate of this class.
*/
__weak id<NMASwitchPingerDelegate> delegate;
/**
* The timeout after which the pinger stops waiting for a response.
*/
NSTimeInterval timeout;
/**
* The socket which is used to send the ping.
*/
GCDAsyncUdpSocket *socket;
/**
* List of pings which are awaiting a response.
*/
NSMutableDictionary *pendingPings;
/**
* Dispatch queue which serializes access to the pendingPings dictionary.
*/
dispatch_queue_t pendingPingsAccessQueue;
/**
* The queue on which the delegate methods of the socket are executed.
*/
dispatch_queue_t socketDelegateQueue;
/**
* Is set to true when the SwitchPinger started receiving responses (after first send)
*/
bool receiving;
}
@end
@implementation NMASwitchPinger
#pragma mark - Initialization
- (id)initWithTimeout:(NSTimeInterval)newTimeout delegate:(id<NMASwitchPingerDelegate>)newDelegate {
self = [super init];
if (self) {
// setting passed values
timeout = newTimeout;
delegate = newDelegate;
// init data structures
pendingPings = [[NSMutableDictionary alloc] init];
pendingPingsAccessQueue = dispatch_queue_create("de.nexans-ans.pingerPendingAccess", DISPATCH_QUEUE_SERIAL);
// create the socket for udp sending
socketDelegateQueue = dispatch_queue_create("de.nexans-ans.pingerDelegate", DISPATCH_QUEUE_CONCURRENT);
socket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:socketDelegateQueue];
}
return self;
}
- (id)init {
NSAssert(NO, @"Use the designated initializer");
return nil;
}
#pragma mark - Sending a ping
- (void)sendPingToAddress:(NSString *)address {
// we allow only one ping at a time to the same ip
__block BOOL alreadyInList = NO;
dispatch_sync(pendingPingsAccessQueue, ^{
if (pendingPings[address]) {
alreadyInList = YES;
} else {
pendingPings[address] = [[NSDate alloc] init];
}
});
// don't send a second ping to the same address
if (alreadyInList) {
NSLog(@"SimplePinger: did not send ping because already a ping pending to this addres: %@", address);
return;
}
// create a minimal packet (3 bytes)
NSMutableData *packet = [[NSMutableData alloc] initWithCapacity:3];
uint16_t vendor_value = CFSwapInt16HostToBig(266);
uint8_t request_type = 1;
[packet appendBytes:&vendor_value length:sizeof(vendor_value)];
[packet appendBytes:&request_type length:sizeof(request_type)];
// send over the wire
[socket sendData:packet toHost:address port:50266 withTimeout:timeout tag:0];
// schedule timeout handler
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
dispatch_after(popTime, pendingPingsAccessQueue, ^(void){
[self removeTimedOutPingWithAddress:address];
});
// start receiving when not already receiving
if (!receiving) {
bool recvGood = [socket beginReceiving:nil];
NSAssert(recvGood, @"SimplePinger: could not start receiving");
receiving = YES;
}
}
#pragma mark - GCDAsyncSocket delegate
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext {
NSString *ipAddress = [GCDAsyncUdpSocket hostFromAddress:address];
__block BOOL pingStillPending = NO;
dispatch_sync(pendingPingsAccessQueue, ^{
NSDate *sendDate = pendingPings[ipAddress];
if (sendDate) {
[pendingPings removeObjectForKey:ipAddress];
pingStillPending = YES;
}
});
if (pingStillPending) {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate switchPinger:self didReceiveResponse:data fromAddress:ipAddress];
});
}
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error {
NSLog(@"didnt send data");
}
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag {
NSLog(@"did send");
}
#pragma mark - Private methods
/**
* Removes a timed out ping. A call of this function gets scheduled when a ping is send.
*
* @param address The address of the ping which should be removed.
*/
- (void)removeTimedOutPingWithAddress:(NSString*)address {
NSDate *sendDate = pendingPings[address];
if (sendDate) {
NSLog(@"timeout: %@", address);
NSAssert(fabs([sendDate timeIntervalSinceNow]) >= timeout, @"SimplePing: removed ping before timout");
[pendingPings removeObjectForKey:address];
dispatch_async(dispatch_get_main_queue(), ^{
[delegate switchPinger:self didReceiveTimeoutFromAddress:address];
});
}
}
@end