使用CocoaAsyncSocket发送多个UDP数据包

时间:2013-10-06 08:31:27

标签: ios cocoa-touch networking asynchronous grand-central-dispatch

我的网络中有很多交换机,我尝试检查它们是否在线。我用一个小的UDP数据包做到这一点,他们用自己的UDP数据包回复它,告诉我它们在那里。事实上,只有一个开关模拟200个用于测试,但这并不重要。

因为我不喜欢在低级别工作,所以我不必使用https://github.com/robbiehanson/CocoaAsyncSocket作为UDP的东西。

它几乎可以工作。当我重复ping例如10个开关(我同时ping它们然后等待所有响应)时,它似乎适用于第一个循环,但之后几个失控,几乎所有似乎都没有响应。它是完全随机的,哪些不是。当我添加更多开关时它会变得更糟(意味着它从第一个循环起不起作用)。有了循环,我会说:我发送所有开关的ping然后等待所有的答案(或超时),然后再发送ping。

当我使用数据包嗅探器检查网络流量时,我发现对于“错误”的交换机(意味着它们显示为脱机= =超时,但实际上是在线),每个交换机有三种可能的情况:

  1. 来自iPhone的数据包永远不会发送,因此无法收到答复。
  2. iPhone发送数据包但交换机没有响应。
  3. 收到了答案包,但iPhone没有阅读。
  4. 它是完全随机的,它们在一个循环中混合。

    当所有开关实际上都在线时,问题被最小化(需要更多周期出现),但仍然存在。

    由于代码是异步的,因此很有可能存在错误,我认为必定存在某种竞争条件。

    我会在这里发布我的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
    

0 个答案:

没有答案