Brightec use WebSockets for mobile app development

Communicating between two devices using WebSockets

How we used WebSockets to enable two devices to communicate with each other.

Enabling connectivity

One of the projects that we’ve been working on recently required two devices to communicate with each other. The first device would primarily send commands and the other would receive them.

We started off looking at Bluetooth but found that it didn’t fit all our requirements. We then came across WebSocket. This is a client-server protocol primarily designed for use by web browsers and web servers but it can be used in any client-server scenario.

This particular project needed communication between iOS and Android, so we needed a WebSocket library for both platforms. We used SocketRocket for iOS and Java-WebSocket for Android. The server side of our app was on Android so we’ll look at that first.

Android Server

Adding the library to your project is as simple as adding the following line to your build.gradle:

compile 'org.java-websocket:Java-WebSocket:1.3.0'

We can now create a basic server implementation by extending the WebSocketServer class

public class MySocketServer extends WebSocketServer {

    private WebSocket mSocket;

    public MySocketServer(InetSocketAddress address) {
        super(address);
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        mSocket = conn;
    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {

    }

    @Override
    public void onMessage(WebSocket conn, String message) {

    }

    @Override
    public void onError(WebSocket conn, Exception ex) {

    }
}

EventBus library

There are many different ways that you could handle messages coming in to the server, we’ll use the EventBus library for this example. Similar to broadcasts, this allows us to distribute the message through the app for anyone to act on. First add the library to your build.gradle:

compile 'de.greenrobot:eventbus:2.4.0'

Then create an event object:

public class SocketMessageEvent {
    private String mMessage;

    public SocketMessageEvent(String message) {
        mMessage = message;
    }

    public String getMessage() {
        return mMessage;
    }
}

Then update the onMessage method in MySocketServer:

@Override
public void onMessage(WebSocket conn, String message) {
    EventBus.getDefault().post(new SocketMessageEvent(message));
}

Now any object can listen for a message being received by the socket and process it by calling EventBus.getDefault().register(this); and implementing:

public void onEvent(SocketMessageEvent event) {

}

We also add a way for the server to send a message back to the client:

public void sendMessage(String message) {
   mSocket.send(message);
}

Initialise the server

To initialise the server we extend the Application class and create an instance of our MySocketServer class:

public class MyApplication extends Application {
   private static final String TAG = "MyApplication";
   private static final int SERVER_PORT = 12345;

   private MySocketServer mServer;

   @Override
   public void onCreate() {
       super.onCreate();

       startServer();
   }

   private void startServer() {
       InetAddress inetAddress = getInetAddress();
       if (inetAddress == null) {
           Log.e(TAG, "Unable to lookup IP address");
           return;
       }

       mServer = new MySocketServer(new InetSocketAddress(inetAddress.getHostAddress(), SERVER_PORT));
       mServer.start();
   }

   private static InetAddress getInetAddress() {
       try {
           for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
               NetworkInterface networkInterface = (NetworkInterface) en.nextElement();

               for (Enumeration enumIpAddr = networkInterface.getInetAddresses(); enumIpAddr.hasMoreElements();) {
                   InetAddress inetAddress = (InetAddress) enumIpAddr.nextElement();

                   if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
                       return inetAddress;
                   }
               }
           }
       } catch (SocketException e) {
           e.printStackTrace();
           Log.e(TAG, "Error getting the network interface information");
       }

       return null;
   }
}

iOS Client

As mentioned earlier, we used SocketRocket for the iOS app. Unfortunately the latest tag is quite old so to use the master branch we add the following to our Podfile:

pod "SocketRocket", :git => 'https://github.com/square/SocketRocket.git'

We need to instantiate a SRWebSocket object and create a class that implements the SRWebSocketDelegate protocol. Both of these will be handled by WSDSocket. The header file for this class is:

@class WSDSocket;


@protocol WSDSocketDelegate 
@optional
- (void)socketDidOpen:(WSDSocket *)socket;
- (void)socket:(WSDSocket *)socket didReceiveMessage:(id)message;
- (void)socket:(WSDSocket *)socket didFailWithError:(NSError *)error;
- (void)socket:(WSDSocket *)socket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
@end


@interface WSDSocket : NSObject
@property (weak, nonatomic) id delegate;

- (instancetype)initWithHost:(NSString *)host port:(NSInteger)port;
- (void)open;
- (void)close;
- (BOOL)isConnectionOpen;
- (BOOL)sendString:(NSString *)string;

@end

This gives us our own delegate for whoever is holding on to the socket (we’ll come back to that later) and also provides methods for interacting with the socket. In the implementation file, we’ll create a private property to hold the socket:

@interface WSDSocket () 
@property (strong, nonatomic) SRWebSocket *socket;
@end

Passing the calls to the socket

The implementation of the methods that we defined in the header is fairly straightforward, just passing the calls to the socket:

- (instancetype)initWithHost:(NSString *)host port:(NSInteger)port
{
    self = [super init];
    if (self) {
        NSString *urlString = [NSString stringWithFormat:@"ws://%@:%ld", host, port];
        NSURLRequest *request  = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        _socket = [[SRWebSocket alloc] initWithURLRequest:request];
        _socket.delegate = self;
    }
    return self;
}


- (void)open
{
    if (self.socket.readyState == SR_OPEN) {
        return;
    }

    [self.socket open];
}


- (void)close
{
    if (self.socket.readyState != SR_OPEN) {
        return;
    }

    [self.socket close];
}


- (BOOL)sendString:(NSString *)string
{
    if (self.socket.readyState != SR_OPEN) {
        return NO;
    }

    [self.socket send:string];
    return YES;
}


- (BOOL)isConnectionOpen
{
    return self.socket.readyState == SR_OPEN;
}

App Delegate

We’ll use the AppDelegate to open the socket and hold on to the connection so add a property to the AppDelegate interface:

@interface AppDelegate ()
@property (strong, nonatomic, readwrite) WSDSocket *socket;
@end

Then add a method to create the socket:

- (void)openSocketWithIP:(NSString *)ipAddress port:(NSInteger)port delegate:(id)delegate
{
    self.socket = [[WSDSocket alloc] initWithHost:ipAddress port:port];
    self.socket.delegate = delegate;
    [self.socket open];
}

This also needs to go into the AppDelegate header file.

Working example

To show the code working, we’ll create a view controller to send messages to the server and show the response. In the viewDidLoad method, open the connection:

- (void)viewDidLoad
{
    [super viewDidLoad];

    AppDelegate *delegate = [UIApplication sharedApplication].delegate;
    [delegate openSocketWithIP:kIpAddress port:kPort delegate:self];
}

kIpAddress and kPort will need to be defined as the IP Address of the Android device and the port that it’s listening on (defined as SERVER_PORT in the code above). The interface for the view controller should have a textfield to enter the message, a button to send the message and a label to display any messages from the server. A simple target action for the button would be:

- (IBAction)sendMessageButtonWasTouched:(UIButton *)sender
{
    AppDelegate *delegate = [UIApplication sharedApplication].delegate;
    [delegate.socket sendString:self.textField.text];
}

The view controller should also implement the WSDSocketDelegate protocol so we’ll add:

- (void)socket:(WSDSocket *)socket didReceiveMessage:(NSString *)message
{
    self.responseLabel.text = message;
}

To make the server respond to our message, we add a couple of bits to the Application class. First, make the application listen for events by adding the following line to the onCreate method of MyApplication:

EventBus.getDefault().register(this);

Then implement the onEvent method to listen for the socket messages:

@SuppressWarnings("UnusedDeclaration")
public void onEvent(SocketMessageEvent event) {
   String message = event.getMessage();
   mServer.sendMessage("echo: " + message);
}

Now, when the iOS client sends a message to the Android server, it will respond by echoing the message back.

Well, that should have WebSockets all set up and your devices connected.

You can find our example WebSockets projects on GitHub for both Android & iOS.

Top