@@ -56,6 +56,9 @@ @interface STOMPClient()
56
56
@property (nonatomic , retain ) GCDAsyncSocket *socket;
57
57
@property (nonatomic , copy ) NSString *host;
58
58
@property (nonatomic ) NSUInteger port;
59
+ @property (nonatomic ) NSString *clientHeartBeat;
60
+ @property (nonatomic , weak ) NSTimer *pinger;
61
+ @property (nonatomic , weak ) NSTimer *ponger;
59
62
60
63
@property (nonatomic , copy ) void (^disconnectedHandler)(NSError *error);
61
64
@property (nonatomic , copy ) void (^connectionCompletionHandler)(STOMPFrame *connectedFrame, NSError *error);
@@ -73,7 +76,7 @@ @interface STOMPFrame()
73
76
74
77
- (id )initWithCommand : (NSString *)theCommand
75
78
headers : (NSDictionary *)theHeaders
76
- body : (NSString *)theBody ;
79
+ body : (NSString *)theBody ;
77
80
78
81
- (NSData *)toData ;
79
82
@@ -298,9 +301,11 @@ @implementation STOMPClient
298
301
@synthesize socket, host, port;
299
302
@synthesize connectionCompletionHandler, disconnectedHandler, receiptHandler, errorHandler;
300
303
@synthesize subscriptions;
304
+ @synthesize pinger, ponger;
301
305
302
306
BOOL connected;
303
307
int idGenerator;
308
+ CFAbsoluteTime serverActivity;
304
309
305
310
#pragma mark -
306
311
#pragma mark Public API
@@ -315,6 +320,7 @@ - (id)initWithHost:(NSString *)aHost
315
320
idGenerator = 0 ;
316
321
connected = NO ;
317
322
self.subscriptions = [[NSMutableDictionary alloc ] init ];
323
+ self.clientHeartBeat = @" 5000,10000" ;
318
324
}
319
325
return self;
320
326
}
@@ -339,9 +345,12 @@ - (void)connectWithHeaders:(NSDictionary *)headers
339
345
340
346
NSMutableDictionary *connectHeaders = [[NSMutableDictionary alloc ] initWithDictionary: headers];
341
347
connectHeaders[kHeaderAcceptVersion ] = kVersion1_2 ;
342
- if (connectHeaders[kHeaderHost ]) {
348
+ if (! connectHeaders[kHeaderHost ]) {
343
349
connectHeaders[kHeaderHost ] = host;
344
350
}
351
+ if (!connectHeaders[kHeaderHeartBeat ]) {
352
+ connectHeaders[kHeaderHeartBeat ] = self.clientHeartBeat ;
353
+ }
345
354
346
355
[self sendFrameWithCommand: kCommandConnect
347
356
headers: connectHeaders
@@ -414,6 +423,8 @@ - (void)disconnect:(void (^)(NSError *error))completionHandler {
414
423
headers: nil
415
424
body: nil ];
416
425
[self .subscriptions removeAllObjects ];
426
+ [self .pinger invalidate ];
427
+ [self .ponger invalidate ];
417
428
[self .socket disconnectAfterReadingAndWriting ];
418
429
}
419
430
@@ -424,15 +435,74 @@ - (void)disconnect:(void (^)(NSError *error))completionHandler {
424
435
- (void )sendFrameWithCommand : (NSString *)command
425
436
headers : (NSDictionary *)headers
426
437
body : (NSString *)body {
438
+ if ([self .socket isDisconnected ]) {
439
+ return ;
440
+ }
427
441
STOMPFrame *frame = [[STOMPFrame alloc ] initWithCommand: command headers: headers body: body];
442
+ LogDebug (@" >>> %@ " , frame);
428
443
NSData *data = [frame toData ];
429
444
[self .socket writeData: data withTimeout: kDefaultTimeout tag: 123 ];
430
445
}
431
446
447
+ - (void )sendPing : (NSTimer *)timer {
448
+ if ([self .socket isDisconnected ]) {
449
+ return ;
450
+ }
451
+ [self .socket writeData: [GCDAsyncSocket LFData ] withTimeout: kDefaultTimeout tag: 123 ];
452
+ LogDebug (@" >>> PING" );
453
+ }
454
+
455
+ - (void )checkPong : (NSTimer *)timer {
456
+ NSDictionary *dict = timer.userInfo ;
457
+ NSInteger ttl = [dict[@" ttl" ] intValue ];
458
+
459
+ CFAbsoluteTime delta = CFAbsoluteTimeGetCurrent () - serverActivity;
460
+ if (delta > (ttl * 2 )) {
461
+ LogDebug (@" did not receive server activity for the last %f seconds" , delta);
462
+ [self disconnect: errorHandler];
463
+ }
464
+ }
465
+
466
+ - (void )setupHeartBeatWithClient : (NSString *)clientValues
467
+ server : (NSString *)serverValues {
468
+ NSInteger cx, cy, sx, sy;
469
+
470
+ NSScanner *scanner = [NSScanner scannerWithString: clientValues];
471
+ scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString: @" , " ];
472
+ [scanner scanInteger: &cx];
473
+ [scanner scanInteger: &cy];
474
+
475
+ scanner = [NSScanner scannerWithString: serverValues];
476
+ scanner.charactersToBeSkipped = [NSCharacterSet characterSetWithCharactersInString: @" , " ];
477
+ [scanner scanInteger: &sx];
478
+ [scanner scanInteger: &sy];
479
+
480
+ NSInteger pingTTL = ceil (MAX (cx, sy) / 1000 );
481
+ NSInteger pongTTL = ceil (MAX (sx, cy) / 1000 );
482
+
483
+ LogDebug (@" send heart-beat every %ld seconds" , pingTTL);
484
+ LogDebug (@" expect to receive heart-beats every %ld seconds" , pongTTL);
485
+
486
+ dispatch_async (dispatch_get_main_queue (), ^{
487
+ self.pinger = [NSTimer scheduledTimerWithTimeInterval: pingTTL
488
+ target: self
489
+ selector: @selector (sendPing: )
490
+ userInfo: nil
491
+ repeats: YES ];
492
+ self.ponger = [NSTimer scheduledTimerWithTimeInterval: pongTTL
493
+ target: self
494
+ selector: @selector (checkPong: )
495
+ userInfo: @{@" ttl" : [NSNumber numberWithInteger: pongTTL]}
496
+ repeats: YES ];
497
+ });
498
+
499
+ }
500
+
432
501
- (void )receivedFrame : (STOMPFrame *)frame {
433
- // CONNECTED
434
- if ([kCommandConnected isEqual: frame.command]) {
502
+ // CONNECTED
503
+ if ([kCommandConnected isEqual: frame.command]) {
435
504
connected = YES ;
505
+ [self setupHeartBeatWithClient: self .clientHeartBeat server: frame.headers[kHeaderHeartBeat ]];
436
506
if (self.connectionCompletionHandler ) {
437
507
self.connectionCompletionHandler (frame, nil );
438
508
}
@@ -477,21 +547,32 @@ - (void)readFrame {
477
547
- (void )socket : (GCDAsyncSocket *)sock
478
548
didReadData : (NSData *)data
479
549
withTag : (long )tag {
550
+ serverActivity = CFAbsoluteTimeGetCurrent ();
480
551
STOMPFrame *frame = [STOMPFrame STOMPFrameFromData: data];
481
552
[self receivedFrame: frame];
482
553
[self readFrame ];
483
554
}
484
555
556
+ - (void )socket : (GCDAsyncSocket *)sock didReadPartialDataOfLength : (NSUInteger )partialLength tag : (long )tag {
557
+ LogDebug (@" <<< PONG" );
558
+ serverActivity = CFAbsoluteTimeGetCurrent ();
559
+ }
560
+
485
561
- (void )socket : (GCDAsyncSocket *)sock didConnectToHost : (NSString *)host port : (uint16_t )port {
486
562
[self readFrame ];
487
563
}
488
564
489
565
- (void )socketDidDisconnect : (GCDAsyncSocket *)sock
490
566
withError : (NSError *)err {
567
+ LogDebug (@" socket did disconnect" );
491
568
if (!connected && self.connectionCompletionHandler ) {
492
569
self.connectionCompletionHandler (nil , err);
493
- } else if (connected && self.disconnectedHandler ) {
494
- self.disconnectedHandler (err);
570
+ } else if (connected) {
571
+ if (self.disconnectedHandler ) {
572
+ self.disconnectedHandler (err);
573
+ } else if (self.errorHandler ) {
574
+ self.errorHandler (err);
575
+ }
495
576
}
496
577
connected = NO ;
497
578
}
0 commit comments