I am trying to create a simple multiplayer turn-based game between iPhones. Right now all I want to do is pass in some string to my method, and have the method send the string through the NSOutputStream. I think I have properly connected my NSNetServices using NSNetServiceBrowser. Once they connect, my NSNetServiceDelegate has netService:didAcceptConnectionWithInputStream:outputStream: called, which should give me my i/o NSStream pair. My method looks like this:
-(void)netService:(NSNetService *)sender didAcceptConnectionWithInputStream:(NSInputStream *)inputStream outputStream:(NSOutputStream *)outputStream{
[self.myNet getInputStream:&inputStream outputStream:&outputStream];
self.inStream = inputStream;
self.outStream = outputStream;
[self.inStream setDelegate:self];
[self.inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inStream open];
}
I think I have correctly set up NSInputStream. I also have a delegate for NSStream which is implementing stream:handleEvent:
It looks like this:
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
NSInputStream *inStream = (NSInputStream *)aStream;
BOOL shouldClose = NO;
switch(eventCode) {
case NSStreamEventEndEncountered:
shouldClose = YES;
// If all data hasn't been read, fall through to the "has bytes" event
if(![inStream hasBytesAvailable]) break;
case NSStreamEventHasBytesAvailable: ; // We need a semicolon here before we can declare local variables
uint8_t *buffer;
NSUInteger length;
BOOL freeBuffer = NO;
// The stream has data. Try to get its internal buffer instead of creating one
if(![inStream getBuffer:&buffer length:&length]) {
// The stream couldn't provide its internal buffer. We have to make one ourselves
buffer = malloc(BUFFER_LEN * sizeof(uint8_t));
freeBuffer = YES;
NSInteger result = [inStream read:buffer maxLength:BUFFER_LEN];
if(result < 0) {
// error copying to buffer
break;
}
length = result;
}
// length bytes of data in buffer
if(freeBuffer) free(buffer);
break;
case NSStreamEventErrorOccurred:
// some other error
shouldClose = YES;
break;
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
}
if(shouldClose){
[inStream close];
}
}
I took that code from: this page. In that code, aStream should be self.inStream. I have looked at pages describing what to do for NSOutputStream, but none of them seem to be geared at a beginner like me. I have a few questions. Firstly, how do I set up a method that I pass the data in (NSData or maybe an NSString) and it sends it out through self.outStream. I would prefer an answer that explains the code, rather than just giving it to me. Secondly, should I open both of my streams in netService:didAcceptConnectionWithInputStream:outputStream:, and should I also scheduleInRunLoop the NSOutputStreamself.outStream`. Finally, am I doing everything wrong?
Thanks for your answers. Also this is my first question, so any constructive criticism is welcomed.
To write to output stream, check stream has space available and write data using
- (NSInteger)write:(const uint8_t *)buffer maxLength:(NSUInteger)length
So, code may be like following.
if ( self.outStream.hasSpaceAvailable ) [ self.outStream write:... maxLength: ];
If output stream doesn't have space available, later
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
method of the delegate of output stream would be called with eventCode = NSStreamEventHasSpaceAvailable.
You should open output stream and schedule it in netService:didAcceptConnectionWithInputStream:outputStream or when you receive NSStreamEventOpenCompleted of input stream. Both are OK.
If you don't want writing block thread, you need to prepare queue.
So code is like following
NSMutableData* uQueue;
NSInputStream* uIStream;
NSOutputStream* uOStream;
:
:
:
uIStream.delegate = self;
uOStream.delegate = self;
[ uIStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode ];
[ uOStream scheduleInRunLoop:NSRunLoop.currentRunLoop forMode:NSDefaultRunLoopMode ];
[ uIStream open ];
[ uOStream open ];
uQueue = NSMutableData.data;
:
:
:
- (void)
Send
{ if ( uQueue.length )
{ NSInteger wLength = [ uOStream write:(const uint8_t*)uQueue.bytes maxLength:uQueue.length ];
if ( wLength > 0 ) [ uQueue replaceBytesInRange:NSMakeRange( 0, wLength ) withBytes:NULL length:0 ];
}
}
- (void)
Write:(NSData*)p
{ [ uQueue appendData:p ];
if ( uOStream.hasSpaceAvailable ) [ self Send ];
}
- (void)
stream:(NSStream*)pS
handleEvent:(NSStreamEvent)p
{ switch( p )
{
:
:
:
case NSStreamEventHasSpaceAvailable:
[ self Send ];
break;
}
}
Related
I am working with a package called DSBridge to connect a mobile application (iOS code in this case) to javascript code containing the main logic of my application. This involves running the JavaScript on an invisible WKWebView.
My JavaScript code needs to call a method in the native iOS application which needs to execute and return a value to the JavaScript.
My Objective C function reads:
- (NSString* )read:(NSDictionary *) args{
NSLog(#"In Read");
while(self->connection != NULL) {
if([self->connection isMessageAvailable]) {
[self->connection messageRetrieved];
NSLog(#"Message Received in ViewController");
return [self->connection getMessage];
}
}
return NULL;
}
Which is called from my JavaScript, I need this to return on the main thread so that the return value is sent into my WebView.
The issue is that my the message from IO I am waiting for will never appear as my NSStream is blocked by the while loop.
My connection methods and NSStream look like this:
-(void)readData:(NSData*)receivedData{
NSData *decoded = [self btXOR:receivedData withMask:0x26];
message = [[NSString alloc] initWithData:decoded encoding:NSUTF8StringEncoding];
NSLog(#"message: %#", message);
messageAvailable = true;
}
- (BOOL) isMessageAvailable {
return messageAvailable;
}
- (void) messageRetrieved {
messageAvailable = false;
}
- (NSString*) getMessage {
NSLog(#"Message: %#", message);
return message;
}
#pragma mark StreamDelegate
-(void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode)
{
//Reading in from host done here
case NSStreamEventHasBytesAvailable:
DDLogInfo(#"NSStreamEventHasBytesAvailable");
DDLogInfo(#"stream is input: %i " , (theStream == inputStream));
NSLog(#"NSStreamEventHasBytesAvailable");
NSLog(#"stream is input: %i " , (theStream == inputStream));
if (theStream == inputStream)
{
uint8_t buffer[1024];
long len;
while ([inputStream hasBytesAvailable])
{
len = [inputStream read:buffer maxLength:sizeof(buffer)];
DDLogDebug(#"InputStream still has bytes");
NSLog(#"InputStream still has bytes");
if (len > 0)
{
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes: (const void *)buffer length:(int)len];
[self readData:data];
}
}
}
break;
The 'NSStreamEventHasBytesAvailable' case should occur once the message is ready, and should be picked up by my read function so it can be returned to my WebViews JavaScript.
How can I take the while loop off of the main thread and still return once I have the message? Is it possible to take the NSStream off the main runloop in a way that it will not be blocked by my read function?
I tried making read async and using a callback to the JavaScript as seen here, but this makes my JavaScript codebase rather unwieldy with callbacks.
Sample code from Stream Programming Guide:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if(!_data) {
_data = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len) {
[_data appendBytes:(const void *)buf length:len];
// bytesRead is an instance variable of type NSNumber.
[bytesRead setIntValue:[bytesRead intValue]+len];
} else {
NSLog(#"no buffer!");
}
break;
}
// continued
What if the number of bytes available is larger than the buffer size?
In such a case, calling - read:maxLength: once only consumes part of available bytes but the whole event. If it is the last NSStreamEventHasBytesAvailable then the remaining bytes are lost.
So it seems to me this code is not correct. The correct code should use a loop to consume all available bytes for every NSStreamEventHasBytesAvailable.
Am I right? Is the sample code wrong?
Calling read:maxLength: once will work. If you do not read all of the available data then you will receive another HasBytesAvailable event.
Looping to read all of the data can be a problem. If data continues to arrive then you may starve other tasks scheduled on that run loop. If you instead only read once then other run loop tasks will be allowed to run before the next HasBytesAvailable event is delivered.
I've got a Java server (and it's able to correctly read a request from my iOS client -- it even generates a response and appears to send it correctly, though I got First message response from server every time but not getting other messages after receiving first message):
sequence of communication
Step 1-> client send login message to server
Step 2-> server validate the user and sends login info to the clients
Step 3-> Message Packet1
Message Packet2
Message Packet3
Message Packet4
step-4-> I have checked server log and it says server has send 4 string
messages
Step-5 -> On client side I am receiving only first message i.e. Message
Packet1, and there no other packets on NSInputStream showing. or NSStreamEventHasBytesAvailable option in delegate method->
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
Not calling most of the time more than one but sometimes It calls and gets MessagePacket2 or MessagePacket4 data.
Please help me out, I am unable to figure out why I am receiving only first packet from server instead of 4 packets, as server sending 4 packets.
I have used code from the below tutorial ->
http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server#comments
My code is here->
#interface NetworkManager()<NSStreamDelegate>
#property (strong, nonatomic)NSInputStream *objInputStream;
#property (strong, nonatomic)NSOutputStream *objOutputStream;
#end
- (void)initializeNetworkCommunicationToServer
{
self.networkOpened = NO;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)SERVER_HOSTNAME,
SERVER_PORT_ADDR,
&readStream,
&writeStream);
self.objInputStream = (__bridge_transfer NSInputStream *)readStream;
self.objOutputStream = (__bridge_transfer NSOutputStream*)writeStream;
[self.objInputStream setDelegate:self];
[self.objOutputStream setDelegate:self];
[self.objInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objInputStream open];
[self.objOutputStream open];
}
//------------------------------------------------------
pragma NSStreamDelegate delegate method
//------------------------------------------------------
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
{
switch (streamEvent)
{
case NSStreamEventNone:
{
NSLog(#"NSStreamEventNone");
break;
}
case NSStreamEventOpenCompleted:
{
NSLog(#"NSStreamEventOpenCompleted");
}
break;
case NSStreamEventHasBytesAvailable:
{
NSLog(#"NSStreamEventHasBytesAvailable:");
if (theStream == self.objInputStream)
{
while ([self.objInputStream hasBytesAvailable])
{
uint8_t buffer[1024];
unsigned int len = 0;
len = [self.objInputStream read:buffer
maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output =
[[NSString alloc] initWithBytes:buffer
length:len
encoding:NSASCIIStringEncoding];
if (nil != output)
{
NSLog(#"server said: %#", output);
}
}//end of if(len > 0)
}//end of while
} //end of if (theStream == self.objInputStream)
}
break;
case NSStreamEventErrorOccurred:
{
NSLog(#"NSStreamEventErrorOccurred: Can not connect to the host!");
}
break;
case NSStreamEventEndEncountered:
{
NSLog(#"NSStreamEventEndEncountered & network connection ended");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
theStream = nil;
}
break;
// The NSStreamEventHasSpaceAvailable event indicates that you can write (at least one byte!) to the stream without blocking. That does not mean that previously written data is completely delivered to the other endpoint of the connection.
case NSStreamEventHasSpaceAvailable:
{
NSLog(#"NSStreamEventHasSpaceAvailable");
if(NO == self.networkOpened)
{
self.networkOpened = YES;
[self sendMessage:#"login:username,password"];
}
}
break;
default:
{
NSLog(#"Unknown event");
}
}
}
//------------------------------------------------------
#pragma mark - send packet
//------------------------------------------------------
- (void)sendMessage:(NSString*)lstrMessage
{
NSMutableData *data = [[NSMutableData alloc] initWithData:
[lstrMessage dataUsingEncoding:NSASCIIStringEncoding]];
unsigned char suffixBytes[] = {1, 1, 0};
[data appendBytes:suffixBytes length:3];
[self.objOutputStream write:[data bytes] maxLength:[data length]];
NSLog(#"message sent->%#",[NSString stringWithUTF8String:[data bytes]]);
}
I have no idea what is wrong with your code, but my experience with streams tuning tells me that there will be make sence to try another way to subscribe to Core Foundation streams using CF API instead of toll-free-bridging it to NSInputStream. I mean CFReadStreamSetClient and CFReadStreamScheduleWithRunLoop functions. You can see example how to do so in my helper class for testing POSInputStreamLibrary.
I am trying to use ReactiveCococa for network connection using NSInputStream & NSOutputStream . The connect code looks as follows:
-(RACSignal*) connect: (NSString *) url {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> theSubscriber) {
self.subscriber = theSubscriber;
// create inputStream and outputStream, initialize and open them
[self.inputStream open]
[self.outputStream open];
}];
return nil;
}
-(void) stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventHasBytesAvailable:
//read from input stream
NSArray * data = read_InputStream;
[subscriber sendNext:data];
break;
}
...
}
I have to store the value of subscriber and call sendNext on it, when I receive data in the callback method.
Is there a better way to handle this in ReactiveCocoa and avoid declaring subscriber property. Besides, this will not work with multiple subscribers.
You can use rac_signalForSelector to turn a delegate callback method into a signal. Then you can subscribe to this signal inside createSignal's didSubscribe block. Something like this:
- (RACSignal*)connect:(NSString *)url
{
return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> theSubscriber)
{
// create inputStream and outputStream, initialize and open them
[self.inputStream open];
[self.outputStream open];
[[self rac_signalForSelector:#selector(stream:handleEvent:)
fromProtocol:#protocol(NSStreamDelegate)]
subscribeNext:^(RACTuple *tuple)
{
NSStream *aStream = (NSStream *)tuple.first;
NSStreamEvent *eventCode = (NSStreamEvent *)tuple.second;
// check eventCode here
NSArray *data = read_InputStream;
[theSubscriber sendNext:data];
}];
return nil;
}];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
}
When using rac_signalForSelector, the signal will pass the arguments from the method through as a tuple which you can then look at to decide what action to take.
Depending on what you're trying to achieve here, there are probably more elegant reactive solutions, but rac_signalForSelector at least solves the problem of needing the subscriber property.
(UPDATED) I am trying to read a large file ( a video or a picture) and send it to a remote server via a SOAP request. I need to encode the data as a Base64 string. I am trying to do this as follows:
Create a template xml for the SOAP request that will go "around" the base64 encoded data
push the first part of the SOAP xml into a buffer
open the video file and encode it in chunks and push each encoded chunk into the buffer
finally, push the second part of the SOAP xml
To be able to "enqueue" parts as above, I am trying to use GCDAsyncSocket with its buffering capabilities. I figure that since GCDAsyncSocket operates on TCP level, I need to write the HTTP POST header myself. So, there are many moving parts which I only vaguely understand and I might be doing it all incorrectly. But my socket never seems to even take off and I am not even sure how to debug it. Here is my relevant code, try to see if you spot any obvious errors:
NSString *soapBody = ...; //Create the SOAP xml here with the part in the middle reserved for the Base64 encoded data (marked with string "CUT_HERE";
NSArray *soapBodyArray = [soapBody componentsSeparatedByString:#"CUT_HERE"];
self.p_soapBodyPart1 = [soapBodyArray[0] dataUsingEncoding:NSUTF8StringEncoding];
self.p_soapBodyPart2 = [soapBodyArray[1] dataUsingEncoding:NSUTF8StringEncoding];
socketQueue = dispatch_queue_create("socketQueue", NULL);//socketQueue is an ivar
self.p_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];//create the socket
NSError *err = nil;
if (![p_socket connectToHost:myURL onPort:80 error:&err]) // Asynchronous!
{
NSLog(#"I goofed: %#", err);
return;
}
NSString* httpHeader = [NSString stringWithFormat:#"POST %# HTTP/1.1\r\nHost: %#\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: text/xml\r\nAccept-Language: en-us\r\nAccept: */*\r\nSOAPAction: http://tempuri.org/myAction\r\nConnection: keep-alive\r\nUser-Agent: ...\r\n\r\n", webserviceOperationsPath, webserviceHost];//Create the HTTP POST header
[p_socket writeData:[httpHeader dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:1]; //enqueue the HTTP header
[p_socket writeData:self.p_soapBodyPart1 withTimeout:-1 tag:2]; //enqueue the first part of the SOAP xml
[self setUpStreamsForInputFile: [self.p_mediaURL path]];//set up NSInputStream to read from media file and encode it as Base64
The socket seems to always connect all right, which I see using this delegate method:
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(#"Socket Connected");
}
setUpStreamsForInputFile method (that is called in the first code listing above):
- (void)setUpStreamsForInputFile:(NSString *)inpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^ {
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
[[NSRunLoop currentRunLoop] run];
});
}
Now, the NSInputStream setup in the previous method will send events to this delegate:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if (stream == self.p_iStream){
if(!self.p_tempMutableData) {
self.p_tempMutableData = [NSMutableData data];
}
uint8_t buf[24000];
unsigned int len = 0;
len = [p_iStream read:buf maxLength:24000];//read a chunk from the file
if(len) {
[p_tempMutableData appendBytes:(const void *)buf length:len];
NSString* base64encData = [Base64 encodeBase64WithData:self.p_tempMutableData];//encode the chunk
self.p_streamEncData = [base64encData dataUsingEncoding:NSUTF8StringEncoding];
[p_socket writeData:self.p_streamEncData withTimeout:-1 tag:3];//write the encoded chunk to the socket
}
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream = nil;
[p_socket writeData:self.p_soapBodyPart2 withTimeout:-1 tag:4];//write the second part of SOAP xml
break;
}
... //some other events handled here
}
}
The socket is supposed to output things to the log with this delegate
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if (tag == 1)
NSLog(#"HTTP Header Written");
else if (tag == 2)
NSLog(#"Soap Part 1 written");
else if (tag == 3)
NSLog(#"File written");
else if (tag == 4)
NSLog(#"Soap Part 2 written");
}
but this happens kind of randomly. For example, sometimes I see the first 2 if's called and sometimes not. When I do and it reaches the third "if" (the one where I am writing the actual encoded data), it writes it only 2 or 3 times and that's it - too few times, I think, given the size of the file. I never see it reach the last "if", where it should write the last part of SOAP xml.
Would appreciate any help! Thanks in advance.
Further update (3/19/13)
Today testing the socket I am no longer getting the write events at all, which tells me that it is random and I am doing something terribly wrong. Today the connection opens but then times out at some point, as I can see with the following delegate method:
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{ // This method is executed on the socketQueue (not the main thread)
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSLog(#"socketDidDisconnect:withError: \"%#\"", err);
}
});
}
which returns
socketDidDisconnect:withError: "Error Domain=NSPOSIXErrorDomain Code=60 "Operation timed out" UserInfo=0x1cd89b00 {NSLocalizedFailureReason=Error in connect() function, NSLocalizedDescription=Operation timed out}"
while I am still running writes of Base64 data in the stream delegate above.