Always On, Always Connected Week 11 - Network Sockets

Network Socket

Sockets enable inter application communication over a network. Most networking software that you use, web browsers, web servers, SSH clients, email applications leverage sockets to communicate with remote applications.

Java and Android have basic support for regular socket communication. While this is fantastic, it is a bit lower level than I would like to delve into and less useful for us in the short term.

WebSockets

WebSockets are a relatively new protocol that is supported by browsers and web servers that facilitate more real time connectivity between client and server than HTTP alone has traditionally supported. WebSockets different only a bit from regular sockets so anything that supports regular socket communication can be made to support WebSockets. The benefit to going this direction is that you can write a server side application that uses WebSockets and every client under the sun should be able to work with it. Browser, Java Applet, Android app, iOS app, desktop application and so on.

Node.js is a JavaScript platform for creating applications. JavaScript is a nice way to create synchronous socket based server applications. I like to use a library called Socket.IO with Node.js.

Sam Slover put together a great getting started with Amazon guide: http://itp.nyu.edu/~sve204/alwaysonalwaysconnected_spring14/amazon-node-server-setup.txt

Here is a quick example (from http://itp.nyu.edu/~sve204/liveweb_fall2013/week3.html)


// HTTP Portion
var http = require('http');
var fs = require('fs'); // Using the filesystem module
var httpServer = http.createServer(requestHandler);
httpServer.listen(8080);

// Any normal http request
function requestHandler(req, res) {
	// Read index.html
	fs.readFile(__dirname + '/index.html', 
		// Callback function for reading
		function (err, data) {
			// if there is an error
			if (err) {
				res.writeHead(500);
				return res.end('Error loading index.html');
			}
			// Otherwise, send the data, the contents of the file
			res.writeHead(200);
			res.end(data);
  		}
  	);
}


// WebSocket Portion
// WebSockets work with the HTTP server
var io = require('socket.io').listen(httpServer);

// Register a callback function to run when we have an individual connection
// This is run for each individual user that connects
io.sockets.on('connection', 
	// We are given a websocket object in our function
	function (socket) {
	
		console.log("We have a new client: " + socket.id);
	
		// When this user "send" from clientside javascript, we get a "message"
		// client side: socket.send("the message");  or socket.emit('message', "the message");
		socket.on('message', 
			// Run this function when a message is sent
			function (data) {
				console.log("message: " + data);
				
				// Call "broadcast" to send it to all clients (except sender), this is equal to
				// socket.broadcast.emit('message', data);
				socket.broadcast.send(data);
				
				// To all clients, on io.sockets instead
				// io.sockets.emit('message', "this goes to everyone");
			}
		);
		
		// When this user emits, client side: socket.emit('otherevent',some data);
		socket.on('otherevent', function(data) {
			// Data comes in as whatever was sent, including objects
			console.log("Received: 'otherevent' " + data);
		});
		
		
		socket.on('disconnect', function() {
			console.log("Client has disconnected");
		});
	}
);
Save this file as whatever.js and upload it to a new directory on your server.

Here is the corresponding index.html file that should be uploaded to the same directory. (Don't forget to replace the SERVER_NAME with your server's name or address.):
<html>
	<head>
		<script src="/socket.io/socket.io.js"></script>
		<script>
			var socket = io.connect('http://SERVER_NAME:8080/');
			
			socket.on('connect', function() {
				console.log("Connected");
			});

			// Receive a message
			socket.on('message', function(data) {
				console.log("Got: " + data);
				document.getElementById('messages').innerHTML += data;
			});

			// Receive from any event
			socket.on('otherevent', function (data) {
				console.log(data);
			});
			
			var sendmessage = function() {
				var message = document.getElementById('message').value;
				console.log("Sending: " + message);
				
				// Send a messaage
				socket.send(message);
			};
			
			var sendother = function() {
				var othermessage = document.getElementById('message').value;
				console.log("sending: " + othermessage);
				
				// Send any kind of data with a custom event
				//socket.emit('otherevent',{ othermessage: othermessage });
				socket.emit('otherevent', othermessage);
			};
		</script>
	</head>
	<body>
 		<div id="messages">
			No Messages Yet
		</div>
		<div>
			<input type="text" id="message" name="message">
			<input type="button" value="message" onclick="sendmessage();">
			<input type="button" value="other" onclick="sendother();">
		</div>
	</body>
</html>

More documentation and examples: https://github.com/LearnBoost/socket.io/wiki/Exposed-events

As you can see, sockets programming is very event driven (as is JavaScript). When a message comes in a function is called to handle that message.

Android + WebSockets

Of course, that works great on the web but how about an Android app that can connect to the same server and interact with the web page.

This library provides the Socket.io interface to Android applications. https://github.com/Gottox/socket.io-java-client

To use it, you have to clone and build it. The readme is a good place to start.

This blog post is also a worthwhile read: http://nkzawa.tumblr.com/post/46850605422/connecting-to-a-socket-io-server-from-android

try {
			SocketIO socket = new SocketIO("http://54.221.90.69:8080");
			socket.connect(new IOCallback() {
			    @Override
			    public void on(String event, IOAcknowledge ack, Object... args) {
			        if (event.equals("otherevent")) {
			            Log.d("SocketIO", "otherevent " + args[0]);
			        }
			    }
	
			    @Override
			    public void onMessage(JSONObject json, IOAcknowledge ack) {
			    		Log.v("SocketIO", json.toString());
			    }
			    @Override
			    public void onMessage(String data, IOAcknowledge ack) {
			    		Log.v("SocketIO", data);
			    	}
			    @Override
			    public void onError(SocketIOException socketIOException) {
			    		socketIOException.printStackTrace();
			    }
			    @Override
			    public void onDisconnect() {
	    				Log.v("SocketIO", "Disconnected");    			
			    }
			    @Override
			    public void onConnect() {
		    			Log.v("SocketIO", "Connected");
			    }		
			});
			socket.emit("message", "android says hello");	
		} catch (Exception e) {
			e.printStackTrace();
		}

WebSockets + UI Thread

As with BlueTooth and other threaded operations, in order for those threads to work with the UI thread and affect the user interface or display anything to the user, we need to use a handler to pass the data from the data thread to the UI thread.

The following snippet assumes a TextView in the Layout with the id textView1.

	TextView tv;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		tv = (TextView) this.findViewById(R.id.textView1);
		
		try {
			SocketIO socket = new SocketIO("http://54.221.90.69:8080");
			socket.connect(new IOCallback() {
			    @Override
			    public void on(String event, IOAcknowledge ack, Object... args) {
			        if (event.equals("otherevent")) {
			            Log.v("SocketIO", "otherevent " + args[0]);
			        } else {
			        	Log.v("SocketIO", event + " " + args[0]);
			        }
			    }
	
			    @Override
			    public void onMessage(JSONObject json, IOAcknowledge ack) {
			    		Log.v("SocketIO", json.toString());
			    		handleMessage(json.toString());
			    }
			    
			    @Override
			    public void onMessage(String data, IOAcknowledge ack) {
			    		Log.v("SocketIO", data);
			    		handleMessage(data);
			    	}
			    
			    @Override
			    public void onError(SocketIOException socketIOException) {
			    		socketIOException.printStackTrace();
			    }
			    
			    @Override
			    public void onDisconnect() {
	    				Log.v("SocketIO", "Disconnected");    			
			    }
			    
			    @Override
			    public void onConnect() {
		    			Log.v("SocketIO", "Connected");
			    }		
			});
			socket.emit("message", "android says hello");	
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	
	// Handlers let us interact with threads on the UI thread
	// The handleMessage method receives messages from other threads and will act upon it on the UI thread
	Handler handler = new Handler() {
		  @Override
		  public void handleMessage(Message msg) {
		    
		    // Pull out the data that was packed into the message with the key "message"
			String messageData = msg.getData().getString("message");
			
			// Send it over to the view
			tv.setText(messageData);
		  }
	};	

	public void handleMessage(String message) {
		// First we obtain a message object
		Message msg = handler.obtainMessage();
		
		// Create a bundle to hold data
		Bundle bundle = new Bundle();
		
		// Put our value with the key "message"
		bundle.putString("message", message);
		
		// Set the message data to our bundle
		msg.setData(bundle);
		
		// and finally send the message via the handler
		handler.sendMessage(msg);
	}
	

Full example: https://github.com/vanevery/AndroidSocketIOTest