-
Notifications
You must be signed in to change notification settings - Fork 83
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
queue based stream #25
base: master
Are you sure you want to change the base?
Changes from 1 commit
3feb247
91a40b1
813a9be
8ac27b3
a19d3b7
65bad09
d42d4fc
7d4948a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,13 +59,13 @@ @interface JFRWebSocket ()<NSStreamDelegate> | |
@property(nonatomic, strong)NSInputStream *inputStream; | ||
@property(nonatomic, strong)NSOutputStream *outputStream; | ||
@property(nonatomic, strong)NSOperationQueue *writeQueue; | ||
@property(nonatomic, assign)BOOL isRunLoop; | ||
@property(nonatomic, strong)NSMutableArray *readStack; | ||
@property(nonatomic, strong)NSMutableArray *inputQueue; | ||
@property(nonatomic, strong)NSData *fragBuffer; | ||
@property(nonatomic, strong)NSMutableDictionary *headers; | ||
@property(nonatomic, strong)NSArray *optProtocols; | ||
@property(nonatomic, assign)BOOL isCreated; | ||
@property(nonatomic)dispatch_queue_t callbackQueue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you name this something less likely to be confused with the the delegate callback queue? Perhaps "networkQueue" or "workQueue"? |
||
|
||
@end | ||
|
||
|
@@ -100,6 +100,9 @@ - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray*)protocols | |
self.readStack = [NSMutableArray new]; | ||
self.inputQueue = [NSMutableArray new]; | ||
self.optProtocols = protocols; | ||
self.callbackQueue = dispatch_queue_create("com.vluxe.jetfire.socket", DISPATCH_QUEUE_SERIAL); | ||
self.writeQueue = [[NSOperationQueue alloc] init]; | ||
self.writeQueue.maxConcurrentOperationCount = 1; | ||
} | ||
|
||
return self; | ||
|
@@ -112,12 +115,9 @@ - (void)connect | |
return; | ||
} | ||
|
||
//everything is on a background thread. | ||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ | ||
self.isCreated = YES; | ||
[self createHTTPRequest]; | ||
self.isCreated = NO; | ||
}); | ||
self.isCreated = YES; | ||
[self createHTTPRequest]; | ||
self.isCreated = NO; | ||
} | ||
///////////////////////////////////////////////////////////////////////////// | ||
- (void)disconnect | ||
|
@@ -195,10 +195,11 @@ - (void)createHTTPRequest | |
(__bridge CFStringRef)headerWSHostName, | ||
(__bridge CFStringRef)[NSString stringWithFormat:@"%@:%@",self.url.host,port]); | ||
|
||
for(NSString *key in self.headers) { | ||
NSDictionary *heads = [self.headers copy]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this method was moved unto the client thread, the I had suggested adding |
||
for(NSString *key in heads) { | ||
CFHTTPMessageSetHeaderFieldValue(urlRequest, | ||
(__bridge CFStringRef)key, | ||
(__bridge CFStringRef)self.headers[key]); | ||
(__bridge CFStringRef)heads[key]); | ||
} | ||
|
||
NSData *serializedRequest = (__bridge_transfer NSData *)(CFHTTPMessageCopySerializedMessage(urlRequest)); | ||
|
@@ -245,19 +246,18 @@ - (void)initStreamsWithData:(NSData *)data port:(NSNumber*)port | |
[self.inputStream setProperty:settings forKey:key]; | ||
[self.outputStream setProperty:settings forKey:key]; | ||
} | ||
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; | ||
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; | ||
CFReadStreamSetDispatchQueue(readStream, self.callbackQueue); | ||
CFWriteStreamSetDispatchQueue(writeStream, self.callbackQueue); | ||
[self.inputStream open]; | ||
[self.outputStream open]; | ||
NSInteger len = [self.outputStream write:[data bytes] maxLength:[data length]]; | ||
if(len < 0 || len == NSNotFound) { | ||
[self doWriteError]; | ||
return; | ||
} | ||
self.isRunLoop = YES; | ||
while (self.isRunLoop) { | ||
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; | ||
} | ||
[self.writeQueue addOperationWithBlock:^{ | ||
NSInteger len = [self.outputStream write:[data bytes] maxLength:[data length]]; | ||
if(len < 0 || len == NSNotFound) { | ||
dispatch_async(self.callbackQueue, ^{ | ||
[self doWriteError]; | ||
}); | ||
} | ||
}]; | ||
} | ||
///////////////////////////////////////////////////////////////////////////// | ||
|
||
|
@@ -297,14 +297,11 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode | |
///////////////////////////////////////////////////////////////////////////// | ||
-(void)disconnectStream:(NSError*)error | ||
{ | ||
[self.writeQueue waitUntilAllOperationsAreFinished]; | ||
[self.inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; | ||
[self.outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; | ||
[self.writeQueue cancelAllOperations]; | ||
[self.outputStream close]; | ||
[self.inputStream close]; | ||
self.outputStream = nil; | ||
self.inputStream = nil; | ||
self.isRunLoop = NO; | ||
_isConnected = NO; | ||
|
||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
|
@@ -327,8 +324,11 @@ - (void)processInputStream | |
if(!self.isConnected) { | ||
_isConnected = [self processHTTP:buffer length:length]; | ||
if(!self.isConnected) { | ||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"Invalid HTTP upgrade" code:1]]; | ||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"Invalid HTTP upgrade" code:1]]; | ||
}); | ||
} | ||
} | ||
} else { | ||
BOOL process = NO; | ||
|
@@ -450,23 +450,32 @@ -(void)processRawMessage:(uint8_t*)buffer length:(NSInteger)bufferLen | |
uint8_t payloadLen = (JFRPayloadLenMask & buffer[1]); | ||
NSInteger offset = 2; //how many bytes do we need to skip for the header | ||
if((isMasked || (JFRRSVMask & buffer[0])) && receivedOpcode != JFROpCodePong) { | ||
|
||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"masked and rsv data is not currently supported" code:JFRCloseCodeProtocolError]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"masked and rsv data is not currently supported" code:JFRCloseCodeProtocolError]]; | ||
}); | ||
} | ||
[self writeError:JFRCloseCodeProtocolError]; | ||
return; | ||
} | ||
BOOL isControlFrame = (receivedOpcode == JFROpCodeConnectionClose || receivedOpcode == JFROpCodePing); //|| receivedOpcode == JFROpCodePong | ||
if(!isControlFrame && (receivedOpcode != JFROpCodeBinaryFrame && receivedOpcode != JFROpCodeContinueFrame && receivedOpcode != JFROpCodeTextFrame && receivedOpcode != JFROpCodePong)) { | ||
|
||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:[NSString stringWithFormat:@"unknown opcode: 0x%x",receivedOpcode] code:JFRCloseCodeProtocolError]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:[NSString stringWithFormat:@"unknown opcode: 0x%x",receivedOpcode] code:JFRCloseCodeProtocolError]]; | ||
}); | ||
} | ||
[self writeError:JFRCloseCodeProtocolError]; | ||
return; | ||
} | ||
if(isControlFrame && !isFin) { | ||
|
||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"control frames can't be fragmented" code:JFRCloseCodeProtocolError]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"control frames can't be fragmented" code:JFRCloseCodeProtocolError]]; | ||
}); | ||
} | ||
[self writeError:JFRCloseCodeProtocolError]; | ||
return; | ||
|
@@ -494,7 +503,9 @@ -(void)processRawMessage:(uint8_t*)buffer length:(NSInteger)bufferLen | |
} | ||
[self writeError:code]; | ||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"continue frame before a binary or text frame" code:code]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"continue frame before a binary or text frame" code:code]]; | ||
}); | ||
} | ||
return; | ||
} | ||
|
@@ -534,17 +545,23 @@ -(void)processRawMessage:(uint8_t*)buffer length:(NSInteger)bufferLen | |
response = nil; //don't append pings | ||
} | ||
if(!isFin && receivedOpcode == JFROpCodeContinueFrame && !response) { | ||
|
||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"continue frame before a binary or text frame" code:JFRCloseCodeProtocolError]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"continue frame before a binary or text frame" code:JFRCloseCodeProtocolError]]; | ||
}); | ||
} | ||
[self writeError:JFRCloseCodeProtocolError]; | ||
return; | ||
} | ||
BOOL isNew = NO; | ||
if(!response) { | ||
|
||
if(receivedOpcode == JFROpCodeContinueFrame) { | ||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"first frame can't be a continue frame" code:JFRCloseCodeProtocolError]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"first frame can't be a continue frame" code:JFRCloseCodeProtocolError]]; | ||
}); | ||
} | ||
[self writeError:JFRCloseCodeProtocolError]; | ||
return; | ||
|
@@ -555,11 +572,15 @@ -(void)processRawMessage:(uint8_t*)buffer length:(NSInteger)bufferLen | |
response.bytesLeft = dataLength; | ||
response.buffer = [NSMutableData dataWithData:data]; | ||
} else { | ||
|
||
if(receivedOpcode == JFROpCodeContinueFrame) { | ||
response.bytesLeft = dataLength; | ||
} else { | ||
|
||
if([self.delegate respondsToSelector:@selector(websocketDidDisconnect:error:)]) { | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"second and beyond of fragment message must be a continue frame" code:JFRCloseCodeProtocolError]]; | ||
dispatch_async(self.queue,^{ | ||
[self.delegate websocketDidDisconnect:self error:[self errorWithDetail:@"second and beyond of fragment message must be a continue frame" code:JFRCloseCodeProtocolError]]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This 8 lines of disconnect logic occurs 8 times – begging to be abstracted into to a method. Could do it in a separate commit though. |
||
}); | ||
} | ||
[self writeError:JFRCloseCodeProtocolError]; | ||
return; | ||
|
@@ -631,15 +652,12 @@ -(BOOL)processCloseCode:(uint16_t)code | |
///////////////////////////////////////////////////////////////////////////// | ||
-(void)dequeueWrite:(NSData*)data withCode:(JFROpCode)code | ||
{ | ||
if(!self.writeQueue) { | ||
self.writeQueue = [[NSOperationQueue alloc] init]; | ||
self.writeQueue.maxConcurrentOperationCount = 1; | ||
} | ||
//we have a queue so we can be thread safe. | ||
__weak JFRWebSocket *weakSelf = self; | ||
[self.writeQueue addOperationWithBlock:^{ | ||
//stream isn't ready, let's wait | ||
int tries = 0; | ||
while(!self.outputStream || !self.isConnected) { | ||
while(!weakSelf.outputStream || !weakSelf.isConnected) { | ||
if(tries < 5) { | ||
sleep(1); | ||
} else { | ||
|
@@ -683,12 +701,12 @@ -(void)dequeueWrite:(NSData*)data withCode:(JFROpCode)code | |
} | ||
uint64_t total = 0; | ||
while (true) { | ||
if(!self.outputStream) { | ||
if(!weakSelf.outputStream) { | ||
break; | ||
} | ||
NSInteger len = [self.outputStream write:([frame bytes]+total) maxLength:(NSInteger)(offset-total)]; | ||
NSInteger len = [weakSelf.outputStream write:([frame bytes]+total) maxLength:(NSInteger)(offset-total)]; | ||
if(len < 0 || len == NSNotFound) { | ||
[self doWriteError]; | ||
[weakSelf doWriteError]; | ||
break; | ||
} else { | ||
total += len; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you rename this to something more explicit like "delegateQueue"? (see suggestion on callbackQueue as well). I found it to be confusing.