0

ネットワークに多数のスイッチがあり、それらがオンラインかどうかを確認しようとしています。私は小さな UDP パケットを使ってこれを行い、彼らがそこにいることを伝えるために、彼ら自身の UDP パケットで応答します。実際には、テスト用に 200 百をシミュレートするスイッチは 1 つだけですが、これは重要ではありません。

必要がなければ低レベルで作業するのは好きではないので、UDPにはhttps://github.com/robbiehanson/CocoaAsyncSocketを使用します。

それは動作します...ほとんど。たとえば、10 個のスイッチに対して繰り返し ping を実行すると (同時に ping を実行し、すべての応答を待ちます)、最初のサイクルでは機能しているように見えますが、数回後には手に負えなくなり、ほとんどすべてが応答していないように見えます。 . どれがそうでどれがそうでないかは完全にランダムです。スイッチを追加すると悪化します (つまり、最初のサイクルからは機能しません)。サイクルでは、すべてのスイッチに ping を送信し、すべての応答 (またはタイムアウト) を待ってから、再度 ping を送信します。

パケット スニファでネットワーク トラフィックをチェックすると、「間違った」スイッチ (つまり、オフライン == タイムアウトとして表示されるが、実際にはオンラインである) について、スイッチごとに 3 つのケースが考えられることがわかります。

  1. iPhone からのパケットは送信されないため、応答がありません。
  2. iPhone はパケットを送信しましたが、スイッチは応答しませんでした。
  3. 応答パケットを受信しましたが、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
4

0 に答える 0