How should you handle the NSStream blocking feature?

In the boxes "Survey versus run cycle":

[ hasSpace/BytesAvailable ] may mean that there are available bytes or spaces, or that the only way to find out is to try a read or write operation (which may lead to an instant block).

The document does not explicitly state that hasSpace / BytesAvailable events behave the same, but it is unclear that they have "identical semantics."

Is it possible to conclude that writing / reading streamError or reading / writing in bytes less than the expected amount can be caused by an "instant block"?

If so, should the transfer be retried? Should I use some kind of timer mechanism to give the lock the ability to clear? That would be a lot of implementation work, so Id probably wouldn’t be if it is unlikely to help.

(His temptation is to initiate a limited polling cycle in such a case, for example, a while loop that makes 10 attempts, but I don’t know if it is safe to do this at the same time that the thread is scheduled in the execution loop, and I have no way to check it. )

+4
source share
3 answers

Here is a nice shell for sockets: https://github.com/robbiehanson/CocoaAsyncSocket

He will stand in line, reading and writing, if the connection is not available. You do not mention whether you are using UDP or TCP, however I suspect that you are using TCP, in which case it will handle any interrupts by itself - provided that the connection is not disconnected.

+1
source

It has been a long way. Here are some questions about this:

In the beginning, I abandoned the idea of ​​saving and checking the remaining cache, because it would work only for the output stream, when further reflection suggested that the input stream could also be blocked.

Instead, I configured while-loops to idle:

 - (void) stream:(NSStream *)theStream handleEvent:(NSStreamEvent)eventCode { switch (eventCode) // RECEIVING case NSStreamEventHasBytesAvailable: { if (self.receiveStage == kNothingToReceive) return; // Get the data from the stream. (This method returns NO if bytesRead < 1.) if (![self receiveDataViaStream:(NSInputStream *)theStream]) { // If nothing was actually read, consider the stream to be idling. self.bStreamIn_isIdling = YES; // Repeatedly retry read, until (1) the read is successful, or (2) stopNetwork is called, which will clear the idler. // (Just in case, add nil stream property as a loop breaker.) while (self.bStreamIn_isIdling && self.streamIn) { if ([self receiveDataViaStream:(NSInputStream *)theStream]) { self.bStreamIn_isIdling = NO; // The stream will have started up again; prepare for next event call. [self assessTransmissionStage_uponReadSuccess]; } } } else // Prepare for what happens next. [self assessTransmissionStage_uponReadSuccess]; break; // SENDING case NSStreamEventHasSpaceAvailable: if (self.sendStage == kNothingToSend) return; if (![self sendDataViaStream:(NSOutputStream *)theStream]) { self.bStreamOut_isIdling = YES; while (self.bStreamOut_isIdling && self.streamOut) { if ([self sendDataViaStream:(NSOutputStream *)theStream]) { self.bStreamOut_isIdling = NO; [self assessTransmissionStage_uponWriteSuccess]; } } } else [self assessTransmissionStage_uponWriteSuccess]; break; // other event cases… 

Then it’s time to test the user’s canceled cancellation using the Cancel button. Halfway through the synchronization, there is a pause on the Cocoa side, awaiting user input. If the user cancels this point, the Cocoa application closes the streams and removes them from runloop, so I expected the streams on the other side of the connection to NSStreamEventEndEncountered events NSStreamEventEndEncountered or possibly NSStreamEventErrorOccurred . But no, only one event has passed, NSStreamEventHasBytesAvailable ! Hover over your mouse.

Of course, in fact there were no "available bytes" since the stream was closed on the Cocoa side and not written, so the stream handler on the iOS side went into an infinite loop. Not very good.

Then I checked what would happen if one of the devices went to sleep. During a pause for user input, I enable iPhone using automatic lock *, and then feeds user input to the Cocoa side. Surprise again: The Cocoa application continued uninterrupted until the end of the synchronization, and when I woke up with the iPhone, the iOS application also completed its part of the synchronization.

Could there be a hiccup on the side of the iPhone that was fixed by my idle cycle? I started the stop network procedure to check:

 if (![self receiveDataViaStream:(NSInputStream *)theStream]) [self stopNetwork]; // closes the streams, etc. 

Synchronization continued until completion. There was no hiccups.

Finally, I checked what happened if the Mac (Cocoa side) slept during this pause for input. This caused a bounce back: two NSStreamEventErrorOccurred events were received - on the Mac side, after which there was no longer any write to the output stream. There were no events on the side of the iPhone, but if I tested the status of the iPhone stream, it would return 5, NSStreamStatusAtEnd.

CONCLUSIONS AND PLAN:

  • A "temporary block" is a kind of unicorn. Either the network runs smoothly or is completely turned off.
  • If there really is such a thing as a temporary block, there is no way to distinguish it from a complete shutdown. The only stream status constants that seem logical for the time block are NSStreamStatusAtEnd and NSStreamStatusError . But in the above experiments, they indicate a separation.
  • As a result, Im discarding while-loops and detecting a trip only by checking for bytesRead / Written <1.

* iPhone will never sleep if it is subordinate to Xcode. You must run it directly from the iPhone.

+1
source

You can expect a "disconnect" whenever you try to write 0 bytes to the output stream or when you get 0 bytes on the input stream. If you want streams to be alive, make sure you check the length of the bytes you write to the output stream. Thus, the input stream never accepts 0 bytes, which triggers an event handler for closed threads.

There is no such thing as an idle output stream. Only a single byte supplier to the output stream, which should not indicate its idleness.

If you disconnect from your network connection with a sleep timer, you can disconnect this when opening your threads, and then disconnect it when you close them:

 - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { switch(eventCode) { case NSStreamEventOpenCompleted: { [UIApplication sharedApplication].idleTimerDisabled = YES; break; } case NSStreamEventEndEncountered: { [UIApplication sharedApplication].idleTimerDisabled = NO; break; } } } 

I would not understand in detail the specifics of your situation, because I can say that you did not quite understand what flows are. I understand that the flow documentation is really unsatisfactory for novice beginners and boring to download; but they model the same threads that have been around for 30 years, so any thread documentation for any operating system (except Windows) will work just fine to speed up the process.

By the way, another, inextricable part of the flows is the code for your network connection, which you did not provide. I believe that if you are not using NSNetService and NSNetServiceBrowser to search for peers, connect to them and, accordingly, get your streams. This allows you to easily monitor the status of your network connection and quickly and easily open your streams if they unexpectedly close.

I have a very detailed but easy to use sample code for this that does not require any configuration at your end to use if someone likes it.

0
source

Source: https://habr.com/ru/post/1389678/


All Articles