Using websockets in native iOS and Android apps
This is an example of using websockets to communicate in real time from browser, iOS, and Android clients.
Why would we want to do that? Since the same real time backend can be reused for all platforms, we can keep the architecture simple and the number of distinct components that can fail to a minimum. Or maybe we just want to add a native client to an existing web service that already uses websockets, without having to change stuff on the backend. This post will demonstrate that it is quite straightforward to connect to a websocket server from iOS and Android as well as from the browser.
We recently used this approach in the Need for Speed™ Network application, which is available for all three platforms. Check out the trailer video on YouTube to see it in action. We used websockets to send the real time in-game positions of the user’s friends in order to display them on a map, among other things.
The websocket client API is very simple, and is pretty much identical across the different client implementations used in this example. Adding TLS encryption is also pretty easy from the client’s point of view, since we just need to change the ws://
URL to a wss://
one.
The example app is a simple chat service. There are no separate channels or nicknames to keep it as bare bones as possible. When a new client connects, it automatically sends a message including what device or browser it is running on.
The full example code (server and the three clients) is available at https://github.com/varvet/mobile-websocket-example. The apps are very basic and only intended to show how to use the API - they contain very little error handling, no tests, are not prepared for localization, etc.
Server
This is a basic websocket server implemented in Ruby using the EM-WebSocket gem.
Here is the entire code for the server implementation:
require "em-websocket"
EventMachine.run do
@channel = EM::Channel.new
EventMachine::WebSocket.start(host: "0.0.0.0", port: 8080, debug: true) do |ws|
ws.onopen do
sid = @channel.subscribe { |msg| ws.send(msg) }
@channel.push("#{sid} connected")
ws.onmessage { |msg| @channel.push("<#{sid}> #{msg}") }
ws.onclose { @channel.unsubscribe(sid) }
end
end
end
The server accepts websocket connections on port 8080. When a new client connects, it subscribes to an internal channel. Every time a client sends data it is pushed to the channel, and when new data is available on the channel, it is sent to all connected clients. Clients are identified by their subscription ID which is an incrementing integer. When the client disconnects, it is unsubscribed from the channel.
To get the server running, run bundle install
and then ruby server.rb
.
Browser client
The biggest reason to use websockets in the first place (instead of, say, regular sockets) is that they are possible to use from a modern browser context. The JavaScript code looks like this:
$(document).ready(function() {
ws = new WebSocket("ws://" + location.hostname + ":8080/");
ws.onmessage = function(event) {
$("#messages").append("<p>" + event.data + "</p>");
};
ws.onclose = function() {
console.log("Socket closed");
};
ws.onopen = function() {
console.log("Connected");
ws.send("Hello from " + navigator.userAgent);
};
$("#new-message").bind("submit", function(event) {
event.preventDefault();
ws.send($("#message-text").val());
$("#message-text").val("");
});
});
We connect to the server on port 8080 (the port we listened to in the server) and implement callbacks for when the connection is established, when a message arrives, and when the connection is closed.
We also add an event handler for sending a new message when the user submits a form.
This code only supports modern browsers. There are projects that can be used to support older browsers via a Flash implementation.
To start serving the browser client on port 8000, run python -m SimpleHTTPServer
from the directory with the HTML and JS file.
iOS client
To build the iOS client you need a Mac and the Apple developer tools (Xcode) installed. To run it on an actual device (and not the simulator), an Apple developer program membership is required.
For iOS, we use the websocket client implementation SocketRocket. The easiest way to include the library is via Cocoapods. This is what our Podfile looks like:
platform :ios, "7.0"
pod "SocketRocket"
If you haven’t got Cocoapods installed, run gem install cocoapods
and pod setup
. Then run pod install
from the iOS directory with the Podfile, and we’re good to go.
The relevant code is in ViewController.m. To connect to the websocket server:
- (void)connectWebSocket {
webSocket.delegate = nil;
webSocket = nil;
NSString *urlString = @"ws://localhost:8080";
SRWebSocket *newWebSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:urlString]];
newWebSocket.delegate = self;
[newWebSocket open];
}
Replace localhost with the relevant hostname if not running the simulator on the same computer that runs the websocket server.
Then we implement the SRWebSocketDelegate protocol:
- (void)webSocketDidOpen:(SRWebSocket *)newWebSocket {
webSocket = newWebSocket;
[webSocket send:[NSString stringWithFormat:@"Hello from %@", [UIDevice currentDevice].name]];
}
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
[self connectWebSocket];
}
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
[self connectWebSocket];
}
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
self.messagesTextView.text = [NSString stringWithFormat:@"%@\n%@", self.messagesTextView.text, message];
}
Note that this is very similar to the Websockets browser Javascript API.
Here is how to send a new message:
- (IBAction)sendMessage:(id)sender {
[webSocket send:self.messageTextField.text];
self.messageTextField.text = nil;
}
The remaining code in the iOS project is mainly for dealing with resizing views when showing or hiding the keyboard. The app targets iOS 7.0 and is not optimized for iPad.
Android client
The Android app is built using Android Studio.
For Android we will use the client library Java WebSockets. Since we’re using Android Studio, the easiest way to include the code from Maven is by listing it in the gradle dependencies in build.gradle:
dependencies {
compile "org.java-websocket:Java-WebSocket:1.3.0"
}
Gradle will take care of downloading it and making it available to the project the next time we build.
private void connectWebSocket() {
URI uri;
try {
uri = new URI("ws://websockethost:8080");
} catch (URISyntaxException e) {
e.printStackTrace();
return;
}
mWebSocketClient = new WebSocketClient(uri) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
Log.i("Websocket", "Opened");
mWebSocketClient.send("Hello from " + Build.MANUFACTURER + " " + Build.MODEL);
}
@Override
public void onMessage(String s) {
final String message = s;
runOnUiThread(new Runnable() {
@Override
public void run() {
TextView textView = (TextView)findViewById(R.id.messages);
textView.setText(textView.getText() + "\n" + message);
}
});
}
@Override
public void onClose(int i, String s, boolean b) {
Log.i("Websocket", "Closed " + s);
}
@Override
public void onError(Exception e) {
Log.i("Websocket", "Error " + e.getMessage());
}
};
mWebSocketClient.connect();
}
Make sure to change websockethost
to whichever address hosts the websocket server.
And to send a new message:
public void sendMessage(View view) {
EditText editText = (EditText)findViewById(R.id.message);
mWebSocketClient.send(editText.getText().toString());
editText.setText("");
}
The Android app targets Android 4.0+. It requires the android.permission.INTERNET
permission to connect to the websocket server.
Hopefully this example illustrates that using Websockets from native mobile apps is a viable alternative, especially if there is a web browser client consuming the same API.
Again, all the example code above is available at https://github.com/varvet/mobile-websocket-example. If you clone that repo you should have everything you need to build and run the server and all three clients.
Comments