Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS/OS X bandwidth management NSURLSession

Application

I have very complex application which uses multiple network services. Some of them are bandwidth demanding and time critical (for example SIP service) and some are more tolerant for poor internet connection (for example power point presentation).

Problem

Now there is starvation problem (one service can dominate the whole bandwidth).

Receiving data has been solved easily. Data rate is calculated for each service and application sends desired data rate to a server and server controls speed of incoming data.

The hard problem is to control speed of data when sending data. For raw socket connection this is quite easy. The sockets output stream is simply wrapped by subclass of NSOutputStream which delays stream events HasSpaceAvailable, depending on how many bytes has been written to the socket in some time unit.

Question

The question is how do this properly for NSURLSession? I could do the same trick for input stream of HTTP body which is delivered in delegate method:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler

but this approach will not take into account HTTP frame (headers). For some service, which will do intensive polling, HTTP frame can be significant part of sent data. So to control data rate properly HTTP frame should be taken into account. Problem is that there is no API which could help to control data rate for the whole HTTP protocol. I can see API which allows to control data rete of the HTTP request body only.

Update

I've tried to use API needNewBodyStream and it doesn't work. NSURLSession uses delivered stream synchronously. Any attempts to split data into chunks which could be delayed for sending leads to some strange errors and request is not sent at all.

So I'm looking for any alternative. Solution proposed in answer: own implementation of NSURLProtocol have lots of drawbacks:

  • lots of complex code which not relay related to my instance of NSURLSession
  • problems to distinguish what services is is so to which bandwidth bucket assign a request
  • this has impact on whole application and I'm providing a framework

So I'm still looking for better solution

like image 475
Marek R Avatar asked Mar 30 '16 20:03

Marek R


People also ask

What is NSURLSession in iOS?

Overview. The NSURLSession class and related classes provide an API for downloading data from and uploading data to endpoints indicated by URLs. Your app can also use this API to perform background downloads when your app isn't running or, in iOS, while your app is suspended.

What is COM Apple NSURLSession delegate?

A protocol that defines methods that URL session instances call on their delegates to handle session-level events, like session life cycle changes. iOS 7.0+ iPadOS 7.0+ macOS 10.9+ Mac Catalyst 13.1+ tvOS 9.0+ watchOS 2.0+


1 Answers

I'm not 100% sure if it will work out in your use case. I believe you might need to take a look at NSURLSessionStreamTask API.

It seems to offer manual control on number of bytes you can write to an underlying socket (same as in your use case). Use readData: to read a fraction of your input body data stream.

/* Read minBytes, or at most maxBytes bytes and invoke the completion
 * handler on the sessions delegate queue with the data or an error.
 * If an error occurs, any outstanding reads will also fail, and new
 * read requests will error out immediately.
 */
- (void)readDataOfMinLength:(NSUInteger)minBytes maxLength:(NSUInteger)maxBytes timeout:(NSTimeInterval)timeout completionHandler:(void (^) (NSData * __nullable data, BOOL atEOF, NSError * __nullable error))completionHandler;

Then you can use writeData: to write this packet to your socket.

/* Write the data completely to the underlying socket.  If all the
 * bytes have not been written by the timeout, a timeout error will
 * occur.  Note that invocation of the completion handler does not
 * guarantee that the remote side has received all the bytes, only
 * that they have been written to the kernel. 
 */
- (void)writeData:(NSData *)data timeout:(NSTimeInterval)timeout completionHandler:(void (^) (NSError * __nullable error))completionHandler;

I'm not sure if it will be able to solve your problem with HTTPHeader & Body data being different packets. This sounds like the right path to follow at the moment.

Hope it helps you.

like image 160
Tarun Tyagi Avatar answered Sep 29 '22 00:09

Tarun Tyagi