Skip to content

Commit 5d53de1

Browse files
committed
Merge pull request #10 from jmesnil/heartbeat
add heart-beat
2 parents a3d8328 + 9c6178f commit 5d53de1

File tree

2 files changed

+88
-8
lines changed

2 files changed

+88
-8
lines changed

StompKit/StompKit.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737

3838
typedef void (^STOMPFrameHandler)(STOMPFrame *frame);
3939
typedef void (^STOMPMessageHandler)(STOMPMessage *message);
40-
typedef void (^ErrorHandler)(NSError *error);
4140

4241
#pragma mark STOMP Frame
4342

@@ -86,7 +85,7 @@ typedef void (^ErrorHandler)(NSError *error);
8685
@interface STOMPClient : NSObject
8786

8887
@property (nonatomic, copy) STOMPFrameHandler receiptHandler;
89-
@property (nonatomic, copy) ErrorHandler errorHandler;
88+
@property (nonatomic, copy) void (^errorHandler)(NSError *error);
9089

9190
- (id)initWithHost:(NSString *)theHost
9291
port:(NSUInteger)thePort;

StompKit/StompKit.m

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ @interface STOMPClient()
5656
@property (nonatomic, retain) GCDAsyncSocket *socket;
5757
@property (nonatomic, copy) NSString *host;
5858
@property (nonatomic) NSUInteger port;
59+
@property (nonatomic) NSString *clientHeartBeat;
60+
@property (nonatomic, weak) NSTimer *pinger;
61+
@property (nonatomic, weak) NSTimer *ponger;
5962

6063
@property (nonatomic, copy) void (^disconnectedHandler)(NSError *error);
6164
@property (nonatomic, copy) void (^connectionCompletionHandler)(STOMPFrame *connectedFrame, NSError *error);
@@ -73,7 +76,7 @@ @interface STOMPFrame()
7376

7477
- (id)initWithCommand:(NSString *)theCommand
7578
headers:(NSDictionary *)theHeaders
76-
body:(NSString *)theBody;
79+
body:(NSString *)theBody;
7780

7881
- (NSData *)toData;
7982

@@ -298,9 +301,11 @@ @implementation STOMPClient
298301
@synthesize socket, host, port;
299302
@synthesize connectionCompletionHandler, disconnectedHandler, receiptHandler, errorHandler;
300303
@synthesize subscriptions;
304+
@synthesize pinger, ponger;
301305

302306
BOOL connected;
303307
int idGenerator;
308+
CFAbsoluteTime serverActivity;
304309

305310
#pragma mark -
306311
#pragma mark Public API
@@ -315,6 +320,7 @@ - (id)initWithHost:(NSString *)aHost
315320
idGenerator = 0;
316321
connected = NO;
317322
self.subscriptions = [[NSMutableDictionary alloc] init];
323+
self.clientHeartBeat = @"5000,10000";
318324
}
319325
return self;
320326
}
@@ -339,9 +345,12 @@ - (void)connectWithHeaders:(NSDictionary *)headers
339345

340346
NSMutableDictionary *connectHeaders = [[NSMutableDictionary alloc] initWithDictionary:headers];
341347
connectHeaders[kHeaderAcceptVersion] = kVersion1_2;
342-
if (connectHeaders[kHeaderHost]) {
348+
if (!connectHeaders[kHeaderHost]) {
343349
connectHeaders[kHeaderHost] = host;
344350
}
351+
if (!connectHeaders[kHeaderHeartBeat]) {
352+
connectHeaders[kHeaderHeartBeat] = self.clientHeartBeat;
353+
}
345354

346355
[self sendFrameWithCommand:kCommandConnect
347356
headers:connectHeaders
@@ -414,6 +423,8 @@ - (void)disconnect:(void (^)(NSError *error))completionHandler {
414423
headers:nil
415424
body:nil];
416425
[self.subscriptions removeAllObjects];
426+
[self.pinger invalidate];
427+
[self.ponger invalidate];
417428
[self.socket disconnectAfterReadingAndWriting];
418429
}
419430

@@ -424,15 +435,74 @@ - (void)disconnect:(void (^)(NSError *error))completionHandler {
424435
- (void)sendFrameWithCommand:(NSString *)command
425436
headers:(NSDictionary *)headers
426437
body:(NSString *)body {
438+
if ([self.socket isDisconnected]) {
439+
return;
440+
}
427441
STOMPFrame *frame = [[STOMPFrame alloc] initWithCommand:command headers:headers body:body];
442+
LogDebug(@">>> %@", frame);
428443
NSData *data = [frame toData];
429444
[self.socket writeData:data withTimeout:kDefaultTimeout tag:123];
430445
}
431446

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+
432501
- (void)receivedFrame:(STOMPFrame *)frame {
433-
// CONNECTED
434-
if([kCommandConnected isEqual:frame.command]) {
502+
// CONNECTED
503+
if([kCommandConnected isEqual:frame.command]) {
435504
connected = YES;
505+
[self setupHeartBeatWithClient:self.clientHeartBeat server:frame.headers[kHeaderHeartBeat]];
436506
if (self.connectionCompletionHandler) {
437507
self.connectionCompletionHandler(frame, nil);
438508
}
@@ -477,21 +547,32 @@ - (void)readFrame {
477547
- (void)socket:(GCDAsyncSocket *)sock
478548
didReadData:(NSData *)data
479549
withTag:(long)tag {
550+
serverActivity = CFAbsoluteTimeGetCurrent();
480551
STOMPFrame *frame = [STOMPFrame STOMPFrameFromData:data];
481552
[self receivedFrame:frame];
482553
[self readFrame];
483554
}
484555

556+
- (void)socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag {
557+
LogDebug(@"<<< PONG");
558+
serverActivity = CFAbsoluteTimeGetCurrent();
559+
}
560+
485561
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
486562
[self readFrame];
487563
}
488564

489565
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock
490566
withError:(NSError *)err {
567+
LogDebug(@"socket did disconnect");
491568
if (!connected && self.connectionCompletionHandler) {
492569
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+
}
495576
}
496577
connected = NO;
497578
}

0 commit comments

Comments
 (0)