3. Crazy geek

Original: https://blog.logrocket.com/we…


This article first send WeChat messages public number: front-end pioneer welcome attention, every day to you push fresh front-end technology articles


The Web has come a long way to support full duplex (or two-way) communication between clients and servers. This is the main purpose of the WebSocket protocol: to provide persistent real-time communication between the client and server over a single TCP socket connection.

The WebSocket protocol has only two agendas: 1) to open the handshake, and 2) to help with data transfer. Once the server and client have successfully shaken hands, they can freely send data to each other with less overhead.

WebSocket communications use the WS (port 80) or WSS (port 443) protocols over a single TCP socket. According to Can I Use, almost all browsers support WebSockets at the time of writing this article except Opera Mini.

The status quo

Historically, creating Web applications that required real-time data communication, such as games or chat applications, required the abuse of the HTTP protocol to establish two-way data traffic. Although there are many ways to implement real-time functionality, none is as efficient as WebSockets. HTTP polling, HTTP streaming, Comet, SSE — they all have their drawbacks.

HTTP polling

The first attempt to resolve the problem is to poll the server periodically. The HTTP long polling lifecycle is as follows:

  1. The client issues the request and waits for the response.
  2. The server delays the response until a change, update, or timeout occurs. The request remains “pending” until something comes back from the server to the client.
  3. When there are some changes or updates on the server side, it sends the response back to the client.
  4. The client sends a new long polling request to listen for the next set of changes.

There are many bugs in long polling — header overhead, latency, timeouts, caching, and so on.

HTTP streaming

This mechanism reduces the pain of network latency because the initial request remains open indefinitely. The request is never terminated, even after the server pushes the data. The first three step lifecycle methods in an HTTP flow are the same as HTTP polling.

However, when the response is sent back to the client, the request is never terminated, and the server keeps the connection open and sends new updates as changes occur.

Server sending event (SSE)

With SSE, the server pushes data to the client. Chat or game applications cannot rely solely on SSE. The perfect use case for SSE is a Facebook-like news Feed: every time new posts are posted, the server pushes them to the timeline. SSE is sent over traditional HTTP and has a limit on the number of connections open.

Not only are these methods inefficient, but the code to maintain them is tiresome to the developer.

WebSocket

WebSockets are designed to replace existing two-way communication technology. The existing methods described above are neither reliable nor efficient when full-duplex real-time communication is involved.

WebSockets are similar to SSE, but are also excellent at getting messages from the client back to the server. Since the data is provided over a single TCP socket connection, connection limitations are no longer an issue.


Actual combat tutorial

As mentioned in the introduction, the WebSocket protocol has only two agendas. Let’s see how WebSockets implement these agendas. To do this I will analyze a Node.js server and connect it to a client built using React.

Agenda 1: WebSocket establishes a handshake between the server and the client

Create a handshake at the server level

We can use a single port to provide HTTP and WebSocket services separately. The following code shows the creation of a simple HTTP server. Once created, we will bind the WebSocket server to the HTTP port:

const webSocketsServerPort = 8000;
const webSocketServer = require('websocket').server;
const http = require('http');
// Spinning the http server and the websocket server.
const server = http.createServer();
server.listen(webSocketsServerPort);
const wsServer = new webSocketServer({
  httpServer: server
});

After creating the WebSocket server, we need to accept a handshake when receiving a request from a client. I save all connected clients as objects in the code and use a unique user ID when receiving requests from the browser.

// I'm maintaining all active connections in this object
const clients = {};

// This code generates unique userid for everyuser.
const getUniqueID = () => {
  const s4 = () => Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
  return s4() + s4() + '-' + s4();
};

wsServer.on('request', function(request) {
  var userID = getUniqueID();
  console.log((new Date()) + ' Recieved a new connection from origin ' + request.origin + '.');
  // You can rewrite this part of the code to accept only the requests from allowed origin
  const connection = request.accept(null, request.origin);
  clients[userID] = connection;
  console.log('connected: ' + userID + ' in ' + Object.getOwnPropertyNames(clients))
});

So, what happens when you accept a connection?

When sending a regular HTTP request to establish a connection, in the request header, the client sends * sec-websocket-key *. The server encodes and hashes this value, and adds a predefined GUID. It responds to the value generated in * sec-websocket-accept * in the handshake sent by the server.

Once the request is accepted on the server (after the necessary validation), the handshake is complete, with a status code of 101. If you see anything in the browser other than the status code 101, it means that the WebSocket upgrade failed and normal HTTP semantics will be followed.

* sec-websocket-accept * header field indicates whether the server is willing to Accept the connection. In addition, if the response is missing the *Upgrade* header field, or if *Upgrade* is not equal to WebSocket, the WebSocket connection failed.

The successful server handshake looks like this:

HTTP GET WS :// 127.0.0.1:8000/101 Switching Protocols Connection: Upgrade sec-websocket-accept: HTTP GET WS :// 127.0.0.1:8000/101 Switching Protocols Connection: Upgrade sec-websocket-accept: Nn/XHq0wK1oO5RTtriEWwR4F7Zw= Upgrade: websocket

Create a handshake at the client level

On the client side, I use the same WebSocket package as on the server to establish a connection to the server (the WebSocket API in Web IDL is being standardized by the W3C). Once the server accepts the request, we will see the WebSocket Client Connected on the browser console.

Here is the initial scaffolding to create a connection to the server:

import React, { Component } from 'react'; import { w3cwebsocket as W3CWebSocket } from "websocket"; Const client = new w3cwebSocket ('ws:// 127.0.1:8000 '); class App extends Component { componentWillMount() { client.onopen = () => { console.log('WebSocket Client Connected'); }; client.onmessage = (message) => { console.log(message); }; } render() { return ( <div> Practical Intro To WebSockets. </div> ); } } export default App;

The client sends a header to establish the handshake:

HTTP GET ws: / / 127.0.0.1:8000/101 Switching separate Protocols Upgrade: websocket Connection: Upgrade the Sec - websocket - Key: vISxbQhM64Vzcr/CD7WHnw== Origin: http://localhost:3000 Sec-WebSocket-Version: 13

Now that the client and server are connected by shaking hands with each other, the WebSocket connection can transmit a message as it is received, thus implementing the second agenda of the WebSocket protocol.

Agenda 2: Real-time information transmission

I’ll write a basic real-time document editor that users can wire together and edit documents. I tracked two events:

  1. User activity: Every time a user joins or leaves, I broadcast a message to all connected clients.
  2. Content changes: Each time the content in the editor is modified, it is broadcast to all other connected clients.

This protocol allows us to send and receive messages in binary data or UTF-8 (note: UTF-8 is less expensive to transfer and convert).

Once we have a good understanding of the socket events onOpen, onClose, and onMessage, understanding and implementing WebSockets is very simple. The client-side and server-side terms are the same.

Send and receive messages on the client side

On the client side, when a new user joins or content changes, we use Client.Send to send a message to the server to provide the new information to the server.

/* When a user joins, I notify the server that a new user has joined to edit the document. */ logInUser = () => { const username = this.username.value; if (username.trim()) { const data = { username }; this.setState({ ... data }, () => { client.send(JSON.stringify({ ... data, type: "userevent" })); }); } } /* When content changes, we send the current content of the editor to the server. */ onEditorStateChange = (text) => { client.send(JSON.stringify({ type: "contentchange", username: this.state.username, content: text })); };

The events we track are user joining and content changes.

Receiving a message from the server is very simple:

componentWillMount() { client.onopen = () => { console.log('WebSocket Client Connected'); }; client.onmessage = (message) => { const dataFromServer = JSON.parse(message.data); const stateToChange = {}; if (dataFromServer.type === "userevent") { stateToChange.currentUsers = Object.values(dataFromServer.data.users); } else if (dataFromServer.type === "contentchange") { stateToChange.text = dataFromServer.data.editorContent || contentDefaultMessage; } stateToChange.userActivity = dataFromServer.data.userActivity; this.setState({ ... stateToChange }); }; }

Send and listen for messages on the server side

In the server, we simply capture the incoming message and broadcast it to all clients connected to WebSocket. This is one of the differences between the notorious socket.io and WebSocket: when we use WebSockets, we need to manually send messages to all clients. Socket.io is a mature library, so it handles itself.

const sendMessage = (json) => { // We are sending the current data to all connected clients Object.keys(clients).map((client) => { clients[client].sendUTF(json); }); } connection.on('message', function(message) { if (message.type === 'utf8') { const dataFromClient = JSON.parse(message.utf8Data); const json = { type: dataFromClient.type }; if (dataFromClient.type === typesDef.USER_EVENT) { users[userID] = dataFromClient; userActivity.push(`${dataFromClient.username} joined to edit the document`); json.data = { users, userActivity }; } else if (dataFromClient.type === typesDef.CONTENT_CHANGE) { editorContent = dataFromClient.content; json.data = { editorContent, userActivity }; } sendMessage(JSON.stringify(json)); }});

Broadcast messages to all connected clients.

What happens when the browser closes?

In this case, WebSocket calls the close event, which allows us to write logic that terminates the current user’s connection. In my code, when a user leaves a document, a message is broadcast to the remaining users:

connection.on('close', function(connection) {
    console.log((new Date()) + " Peer " + userID + " disconnected.");
    const json = { type: typesDef.USER_EVENT };
    userActivity.push(`${users[userID].username} left the document`);
    json.data = { users, userActivity };
    delete clients[userID];
    delete users[userID];
    sendMessage(JSON.stringify(json));
  });

The source code for this application is in Repo on GitHub.

conclusion

WebSockets are one of the most interesting and convenient ways to implement real-time functionality in an application. It gives us the flexibility to take full advantage of full-duplex communication. I highly recommend trying out WebSockets before trying out Socket.io and other available libraries.

Happy coding! 😊


This article first send WeChat messages public number: front-end pioneer

Welcome to scan the two-dimensional code to pay attention to the public number, every day to push you fresh front-end technology articles


Read on for the other great articles in this column:

  • 12 Amazing CSS Experiment Projects
  • 50 React Interview Questions You Must Know
  • What are the front-end interview questions at the world’s top companies
  • 11 of the best JavaScript dynamic effects libraries
  • CSS Flexbox Visualization Manual
  • React from a designer’s point of view
  • The holidays are boring? Write a little brain game in JavaScript!
  • How does CSS sticky positioning work
  • A step-by-step guide to implementing animations using HTML5 SVG
  • Programmer 30 years old before the monthly salary is less than 30K, which way to go
  • 14 of the best JavaScript data visualization libraries
  • 8 top VS Code extensions for the front end
  • A complete guide to Node.js multithreading
  • Convert HTML to PDF 4 solutions and implementation

  • More articles…