A web socket is a computer communications protocol that provides full-duplex communication channels over a single TCP connection. TCP stands for Transmission Control Protocol and is one of the main communication protocols in the internet protocol suite. Establishing a full-duplex communication is the the power of the web socket.
What is Full-duplex communication? Picture a section of road with a post office on either end (A and B) that is 1 lane wide. Cars can go in either direction, BUT can only go when they are told it’s all clear by the other side or they may get in a head on collision (also, if they go even slightly off road they explode into a million shiny pieces). So, a car leaves A and travels towards B, and B knows not to go until the car from A reaches B and says the road is clear. Car A may have a request that something be sent back to post office A, and/or it may be communicating some info by dropping off some mail.
This happens a lot with web communication. Client sends a request and/or info, server gets the request/info, processes what to do with it and sends back some info. Turns are taken, my kindergarten teacher, Mrs. Smith, is happy and all is well in the world. But web sockets throw all that polite turn taking out the window.
We’ve picture a single lane road as standard communication where two entities communicate by taking turns sending requests and information. But, and I know this is crazy, what if there was a TWO lane road. What if there was a world where 2 entities could send information whenever they wanted, AND, could receive information whenever the other felt like sending it. This bi-directional road means that each side doesn’t have to send out requests because there is no need to control who’s turn it is, an entity simply needs to “subscribe” to the other to accept any information that may come from that direction.
Ok, another thought experiment. Entity A is a server that performs some function and returns an answer. However, this time there isn’t just entity B as a client, there are hundreds of clients and they all have this 2 lane road leading to/from the server. The server can now update all the clients without the need for request/response from each individual client. The server can simply “broadcast” some information to all the clients “subscribed” to that lane (or channel) at the same time and each client can send information to the server for processing.
Cool! No more polling or long-polling for a client to try to stay current with the state of the server. Information is now realtime, being sent by both sides upon processing, and received whenever it comes at them. We now live in a turn free world where things just do as they please. Now we can do cool things like make multiplayer realtime games!
To dip a little into web sockets, I made a 2 player tic-tac-toe game that used them. Ruby on rails and Action cable provide a pretty straight forward implementation of web sockets for beginners. Here is a some basic setup to get you started with Ruby and Actioncable with a React frontend to use websockets.
First generate your React front end by entering the following into your console:
create-react-app <your-frontend-project-name>
cd <your-frontend-project-name>
yarn add actioncable
yarn start
And then get you rails backend wireframe up by entering the following into your console:
rails new <your-backend-project-name> --api
cd <your-backend-project-name>
rails g scaffold <your-model-name> <your-models-attributes>
rails db:migrate
The next step is to persist an instance of your model by creating one in the rials console:
rails c
<your-model>.create!(attributes)
Be sure to setup cors by uncommenting the 'rack-cors' gem in your gemfile and uncommenting the following in your application.rb. Making sure origins is set to the acceptable url, or in this case I used * to make every url acceptable.
config.middleware.insert_before 0, Rack::Cors do
allow do
origins '*'
resource '*', :headers => :any, :methods => [:get, :post, :options]
end
end
Run Bundle install.
Now to wire up actioncable. The next few steps are fairly simple. Navigate to your config.routes.rb
and add the following before the last end
.
mount ActionCable.server => '/cable'
Then, go to your console and type in the following:
rails g channel <your-model-name>
Now Actioncable can pickup websockets that are coming into /cable
and your rails backend will have a new file app/channels/<your-model>_channel.rb
Open that file up.
class <your-model>Channel < ApplicationCable::Channel
def subscribed
# stream_from "some_channel"
stream_from '<your-model>'
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def receive(data)
<your-model> = <Your-Model>.find(data["id"])
<your-model>.update!(attr: data["attr"])
ActionCable.server.broadcast('<your-model>', data)
end
end
Here, we've added stream_from
to the subscribed method and then told rails what to do with received data in the receive method. Our backend is now setup for streaming!
Now, lets setup our frontend to subscribe and talk to our backend. We will need to do a few things in order to accomplish this. First, connect to /cable
that we setup in our backend. Second, subscribe to the our model channel. And last, send any data by the user on the front side.
To accomplish these 3 things we must first import actioncable by putting import ActionCable from 'actioncable'
at the top of your app.js and creating a consumer inside of your componentDidMount method. We must next setup our subscription and set a callback for when we receive data. The final file might look something like:
import React, { Component } from 'react'
import './App.css'
import ActionCable from 'actioncable'
class App extends Component {
state = { <your-state>: undefined }
componentDidMount() {
window.fetch('http://localhost:3001/<your-model>/1').then(data => {
data.json().then(res => {
this.setState({ <your-state>: res.<model-attribute> })
})
})
const cable = ActionCable.createConsumer('ws://localhost:3001/cable')
this.sub = cable.subscriptions.create('NotesChannel', {
received: this.handleReceiveNewText
})
}
handleReceiveNewData = ({ <model-attribute> }) => {
if (<model-attribute> !== this.state.<your-state>) {
this.setState({ <model-attribute> })
}
}
handleChange = e => {
this.setState({ <your-state>: e.target.value })
this.sub.send({ <your-state>: e.target.value, id: 1 })
}
That pretty much the gist of setting up a websocket with rails and react. There is obviously some more things that need to be done like displaying/grabbing information on the screen (controlled inputs are best), data sanitization, authorization, and more. But, this is a super simple setup to get you started.
Top comments (2)
great writeup--mind if i link to this on my mod4-project blog? (i specifically recommended getting familiar with the Channel methods, and lo and behold, here's a perfect example to use!)
Of course Isa!