iOS TCP Server/Client Communications: 5 Things About Persistent Connection
IT copywriter
Reading time:
If a mobile application has client-server architecture, we have to choose a way of exchanging data with the server at the design stage. This is a rather common question in mobile development and there is a number of well-known approaches to solve it. For our recent project we’ve been choosing between HTTP requests and a persistent connection. Each method has its advantages and disadvantages.
The advantages of using HTTP requests are:
- Simple to implement
- Suitable for many projects, which require networking functionality
We hadn’t previously used the persistent connection approach but it provides several known advantages over the HTTP request approach:
- Faster data transfer
- No need to constantly poll the server to get messages from it
- Persistent connection does not require storing and sending a connection ID
So finally we decided to go the second way, and in addition to that, to use SSL for sending sensitive data (bank card numbers) to the server. This article is dedicated to the key moments of implementing a persistent connection in iOS apps.
Establishing the connection
iOS SDK has a lot of ready-made classes for working with data streams. Unfortunately, they’re subclasses of NSStream which doesn’t provide a way to configure SSL encryption, so we had to use the lower level component called CFStream:
CFReadStreamRef _aReadStream; CFWriteStreamRef _aWriteStream; CFStreamCreatePairWithSocketToHost(NULL, serverAddress, serverPort, &_aReadStream, &_aWriteStream);
Code sample 1. Opening protocols
It’s okay to typecast our CFStream variables to NSStream for convenience:
NSInputStream *readStream = (NSInputStream *)_aReadStream; NSOutputStream *writeStream = (NSOutputStream *)_aWriteStream;
Code sample 2. Typecasting CFStream
Setting SSL connection parameters:
[self.readStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey]; [self.writeStream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey]; NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithBool:NO], kCFStreamSSLAllowsExpiredCertificates, [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot, [NSNumber numberWithBool:YES], kCFStreamSSLValidatesCertificateChain, kCFNull,kCFStreamSSLPeerName, nil]; CFReadStreamSetProperty((CFReadStreamRef)self.readStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings); CFWriteStreamSetProperty((CFWriteStreamRef)self.writeStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
Code sample 3. Setting SSL connection parameters
To set the SSL connection properties, we had to use CFStream interface.
Asynchronous data exchange
An application should never block the UI while waiting for a response on the socket. That’s why any data exchange with the server should happen asynchronously. This way, an application isn’t going to wait for the server response after sending a request. Instead, it can do some other work and handle the response upon receiving it from the server. To implement the non-blocking behaviour we have to use a delegate suitable for handling server message. In our case, the delegate is the same object that we use to establish a connection:
self.readStream.delegate = self; CFReadStreamScheduleWithRunLoop (_aReadStream, CFRunLoopGetMain(), kCFRunLoopDefaultMode); self.writeStream.delegate = self; CFWriteStreamScheduleWithRunLoop (_aWriteStream, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
Code sample 4. Setting the read/write stream event handling delegate.
The class whose instance will handle messages from the streams should implement the NSStreamDelegate protocol. This protocol defines a stream:handleEvent method that looks like this:
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { switch (eventCode) { case NSStreamEventOpenCompleted: ... break; case NSStreamEventHasSpaceAvailable: ... break; case NSStreamEventHasBytesAvailable: ... break; case NSStreamEventErrorOccurred: ... break; } }
Code sample 5. Server message handling method.
This method handles several types of server messages. The most interesting is a NSStreamEventHasBytesAvailable event which is fired when the server has data to send to the client. Connection opening and closing events, stream status notifications and errors are also handled in this method.
If there are no errors, it’s okay to open the streams and start exchanging data with the server:
if (!err && CFReadStreamOpen(_aReadStream) == 0) { NSLog(@"Error Opening Read Socket"); err = YES; } if (!err && CFWriteStreamOpen(_aWriteStream) == 0) { NSLog(@"Error Opening Write Socket"); err = YES; }
For both serializing and deserializing data we used Google’s protobuf library that is available for many platforms including iOS. We have already used protobuf in our past projects, so implementing serialization was the easy part.
Problems
We had an unusually long initial connection time when testing the project on a real device, while it worked perfectly from a simulator. In an attempt to fix this we revised a lot of code, moved the networking logic to a separate thread and tried some other tweaks, but the problem persisted.
It was an especially strange problem, because we used a really basic and well-documented way to establish the connection. The problem was finally solved by moving the server software to another machine. After that all the connection problems disappeared. Lesson learned: a problem can appear in an unexpected place and it’s a good idea to debug client-server applications on both ends.
Advantages
Persistent connections have a number of advantages:
- Less CPU and memory usage (because fewer connections are open simultaneously).
- Less network load, again because of fewer connections.
- Lower times of subsequent requests, no need to re-establish the connection.
- Persistent connection enables push messages from server to to the client.
- Hardware requirements are lower than for request-based applications.
- Better response timing for the client application.
- Faster network operations on the client side.
If you have difficulties deciding on the data exchange approach to take in your project and the above example didn’t help you make a decision, feel free to contact us for the help.
Comments