Working With Sockets

This lesson guides us through the process of setting up client-server communication in a LiveVM.

In this lesson, we will learn how to set up a socket connection in a client-server model. A sample app is provided as an example.

Supporting Sockets

Socket connections can be enabled in the context of a live VM (we have already seen how to make such docker jobs in this lesson). To establish a socket connection between a client and server, we will specify a port at which the server will be listening for connections.

Sample App

We will be creating a javascript chatting application (using the socket.io library).

please note that sockets can be set up in any programming language and are not specific to javascript socket.io library

Creating a Docker File

We will set the gcr.io/educative-exec-env/educative:latest as the base image for our app.

FROM gcr.io/educative-exec-env/educative:latest

Creating a Docker Job

Below is a sample docker job:

"Job Name": ChatApp
"InputFileName": foo
"Build Script":
"Run Script": echo "Testing Chat App"
"Run in Live Container": True
"Application Port": 3000
"Start Script": cd /usercode && npm i && node_modules/nodemon/bin/nodemon.js /usercode/server.js

Creating a Server #

The following snippet shows the server code. The server listens at port 3000

Press + to interact
const fs = require('fs'),
http = require('http'),
socketio = require('socket.io')
const readFile = file =>
new Promise((resolve, reject) =>
fs.readFile(file, (err, data) =>
err ? reject(err) : resolve(data)))
const server = http.createServer(async (request, response) => {
try {
let fileName = request.url.substr(1);
if (!fileName) fileName = 'client.html'
const data = await readFile(fileName)
response.end(data)
} catch (err) {
response.end()
}
})
const io = socketio(server)
users = []
io.sockets.on('connection', socket => {
socket.emit('connected', true);
users = [...users, socket];
socket.on('msg', (data) => {
user_id = null;
users.forEach((usr, id) => {
if (usr === socket)
user_id = id;
});
const msg = `User ${user_id}: ${data}`
io.emit('msg', msg);
})
socket.on('disconnect', () => {
console.log('disconnected');
})
})
server.listen(3000)

Creating a Client #

The client establishes a connection with the server and communicates user input.

Press + to interact
let socket = io()
class Root extends React.Component {
state = {
msg: '',
msgList: [],
}
componentDidMount() {
socket.on("connected", () => {
this.setState({ msg: ''})
});
socket.on('msg', (data) => {
this.setState({ msgList: [...this.state.msgList, data]});
})
}
sendMsg = () => {
socket.emit('msg', this.state.msg);
this.setState({ msg: ''})
}
onPressEnter = (e) => {
if (e.keyCode === 13) this.sendMsg();
}
render() {
const { msg, msgList } = this.state
return (
<div>
<input
type="text"
value={msg}
onChange={(ev) => this.setState({ msg: ev.target.value})}
onKeyDown={this.onPressEnter}
/>
<button onClick={this.sendMsg}>{'SEND'}</button>
<div>
{msgList.map(msg => (
<div>
<p>{msg}</p>
</div>
))}
</div>
</div>
)
}
}
const dest = document.getElementById('root');
ReactDOM.render(<Root />, dest);

Putting It All Together #

The complete application code can be found in the single page app widget below.

Click RUN and open the app url in multiple tabs. All the tabs can send messages to the server on socket and the server in turn will broadcast the message to all other connections.

const fs = require('fs'),
	http = require('http'),
	socketio = require('socket.io')

const readFile = file =>
	new Promise((resolve, reject) =>
		fs.readFile(file, (err, data) =>
			err ? reject(err) : resolve(data)))

const server = http.createServer(async (request, response) => {
	try {
		let fileName = request.url.substr(1);
		if (!fileName) fileName = 'client.html'
		const data = await readFile(fileName)
		response.end(data)
	} catch (err) {
		response.end()
	}
})

const io = socketio(server)
users = []

io.sockets.on('connection', socket => {
  socket.emit('connected', true);
  users = [...users, socket];

  socket.on('msg', (data) => {
    user_id = null;
    users.forEach((usr, id) => {
      if (usr === socket)
        user_id =  id;
    });
    const msg = `User ${user_id}: ${data}`
    io.emit('msg', msg);
  })

	socket.on('disconnect', () => {
		console.log('disconnected');
	})
})

server.listen(3000)