ネットワークに多数のスイッチがあり、それらがオンラインかどうかを確認しようとしています。私は小さな UDP パケットを使ってこれを行い、彼らがそこにいることを伝えるために、彼ら自身の UDP パケットで応答します。実際には、テスト用に 200 百をシミュレートするスイッチは 1 つだけですが、これは重要ではありません。
必要がなければ低レベルで作業するのは好きではないので、UDPにはhttps://github.com/robbiehanson/CocoaAsyncSocketを使用します。
それは動作します...ほとんど。たとえば、10 個のスイッチに対して繰り返し ping を実行すると (同時に ping を実行し、すべての応答を待ちます)、最初のサイクルでは機能しているように見えますが、数回後には手に負えなくなり、ほとんどすべてが応答していないように見えます。 . どれがそうでどれがそうでないかは完全にランダムです。スイッチを追加すると悪化します (つまり、最初のサイクルからは機能しません)。サイクルでは、すべてのスイッチに ping を送信し、すべての応答 (またはタイムアウト) を待ってから、再度 ping を送信します。
パケット スニファでネットワーク トラフィックをチェックすると、「間違った」スイッチ (つまり、オフライン == タイムアウトとして表示されるが、実際にはオンラインである) について、スイッチごとに 3 つのケースが考えられることがわかります。
- iPhone からのパケットは送信されないため、応答がありません。
- iPhone はパケットを送信しましたが、スイッチは応答しませんでした。
- 応答パケットを受信しましたが、iPhone はそれを読みませんでした。
それは完全にランダムで、1 つのサイクルで混合されます。
すべてのスイッチが実際にオンラインになると、問題は最小限に抑えられますが (表示されるまでにより多くのサイクルがかかります)、問題は解決しません。
コードは非同期であるため、バグの可能性が高く、ある種の競合状態か何かがあるに違いないと思います。
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