Hướng dẫn websocket trong javascript

GIỚI THIỆU VỀ WEBSOCKETS

Trước khi đi tìm hiểu về Websockets chúng ta cùng ngẫm qua Ajax Polling và Ajax Long Polling.

Ajax Polling và Ajax Long Polling là hai phương thức cho client <-> server giao tiếp với nhau, chúng sẽ lý giải lý do tồn tại Websockets và giúp các bạn lựa chọn sử dụng tuỳ trường hợp cụ thể khi áp dụng thực tế.

AJAX POLLING

Cơ chế của phương thức này như sau : Client sẽ gửi các request liên tục tới server trong mỗi khoảng thời gian xác định với mục đích cập nhật lại dữ liệu phía người dùng. Server sẽ bị bắt buộc trả về dữ liệu mong muốn ứng với mỗi nhịp request gửi đến, và cũng không quan tâm tới việc liệu có những thay đổi nào, nhiều hay ít hoặc không hề thay đổi. Tất cả dữ liệu yêu cầu được gói lại trả về Client.

AJAX LONG POLLING

Phương thức này cũng tương tự như AJAX POLLING là Client vẫn sẽ gửi request đều đặn tới Server . Tuy nhiên, thay vì trả về ttoafn bộ dữ liệu ứng với mỗi request đến thì khi này , dữ liệu thay đổi được kiểm tra và sẽ chỉ response lại Client những thay đổi (nếu có) . Điều này có nghĩa Server hoàn toàn có thể bỏ qua việc phản hồi lại những request không cần thiết khi dữ liệu người dùng vẫn chính xác ở thời điểm đó.

Với cả 2 phương thức này, chúng đều sử dụng giao thức HTTP. Vì thế mà mỗi gói tin truyền đi bao gồm rất nhiều thông tin header làm tốc độ truyền tải không được tối ưu. Thêm nữa là quá trình xảy ra tuần tự, không hỗ trợ việc giao tiếp song song Client <-> Server.

Những vấn đề trên là lý do cho Websockets ra đời, mục đích là tạo ra khả năng tạo ra môi trường tiệm cận realtime tốt hơn cho giao tiếp Client <-> Server, hỗ trợ giao tiếp song song cho cả Client và Server cùng lúc.

Websockets

Đặc điểm :

Websockets thực hiện thông qua giao thức TCP, nó có thể giảm kích thước header của bản tin request tớí vài trăm lần so với bản tin HTTP. Cũng vì thế mà tốc độ tuỳ trường hợp có thể tăng lên một vài lần, độ trễ thấp và dễ xử lý lỗi. Các API được cung cấp cũng rất đơn giản , gọn gàng và dễ sử dụng. Tuy nhiên nhược điểm là không hỗ trợ được toàn bộ các trình duyệt

  • Cấu trúc : hỗ trợ chuẩn giao thức mới ws:// và wss:// (chuẩn bảo mật).
  • Kiểu dữ liệu : dữ liệu cơ sở là String. Ngoài ra còn có buffer array, blobs.

Cách sử dụng (trong javascript) :

  • Kiểm tra xem môi trường ứng dụng có hỗ trợ Websocket hay không : Phương thức webSocketSupported trả về kiểu boolean để xác định điều này :
function webSocketSupported() {
  return "WebSocket" in window;
}
if ('WebSocket' in window){
alert('Có hỗ trợ đấy nhé');
} else {
alert('Chúng tôi không biết đây là cái gì, tôi không hỗ trợ !');
}
  • Tạo một object theo kiểu Websockets để kết nối kèm theo đường dẫn Server của bạn :
var  connection = new Websocket('ws://yourServer');

hoặc 

var  connection = new Websocket('wss://yourServer'); với dịch vụ bảo mật.
  • Mở một kết nối tới Server đó :
connection.onopen = function(){
alert('Kết nối thành công');
}
  • Bắt lỗi xảy ra :
connection.onerror = function(error){
console.log('Lỗi' + JSON.stringify(error));
alert('Đã xảy ra lỗi');
}
  • Gửi request đến Server kèm theo dữ liệu :
var data = {page : 'Viblo',
                  message : 'Hãy nhận lấy thông báo của tôi'
                }
connection.send(JSON.stringify(data));
  • Nhận message từ Server gửi về :
connection.onmessage = function(e) {
console.log('data response', e.data);
}
  • Đóng kết nối Websockets tới Server :
connection.onclose = function() {
alert('Kết nối đã đóng lại');
}

hoặc đơn giản hơn là :

connection.close();

Theo 1 cách khác, chúng ta cũng có thể sử dụng addEventListener để lắng nghe các event :

connection.addEventListener('eventName', function(event) {
console.log(event);
});
// eventName : open, close, send, message...
  • Kiểm tra kết nối: Object Websocket có một thuộc tính trạng thái readyState phản ánh trạng thái kết nối, chúng ta có thể tận dụng nó kết hợp cùng các hằng số của Websocket:
switch (connection.readyState) {
case WebSocket.CONNECTING:
  alert('Đang thực hiện kết nối');
  break;
case WebSocket.OPEN:
  alert('Kết nối đang mở');
  break;
case WebSocket.CLOSING:
  alert('Đang đóng kết nối');
  break;
case WebSocket.CLOSED:
  alert('Kết nối đã đóng');
  break;
}

Tài liệu tham khảo : https://viblo.asia/p/hieu-hon-ve-websocket-znVGL2r0RZOe

1. Websocket là gì?

WebSoket là công nghệ hỗ trợ giao tiếp hai chiều giữa client và server bằng cách sử dụng một TCP socket để tạo một kết nối hiệu quả và ít tốn kém. Mặc dù được thiết kế để chuyên sử dụng cho các ứng dụng web, lập trình viên vẫn có thể đưa chúng vào bất kì loại ứng dụng nào.

  • Ưu điểm

WebSockets cung cấp khả năng giao tiếp hai chiều mạnh mẽ, có độ trễ thấp và dễ xử lý lỗi. Không cần phải có nhiều kết nối như phương pháp Comet long-polling và cũng không có những nhược điểm như Comet streaming. API cũng rất dễ sử dụng trực tiếp mà không cần bất kỳ các tầng bổ sung nào, so với Comet, thường đòi hỏi một thư viện tốt để xử lý kết nối lại, thời gian chờ timeout, các Ajax request (yêu cầu Ajax), các tin báo nhận và các dạng truyền tải tùy chọn khác nhau (Ajax long-polling và jsonp polling).

  • Nhược điểm

Những nhược điểm của WebSockets gồm có:

Không có phạm vi yêu cầu nào. Do WebSocket là một TCP socket chứ không phải là HTTP request, nên không dễ sử dụng các dịch vụ có phạm vi-yêu cầu, như SessionInViewFilter của Hibernate. Hibernate là một framework kinh điển cung cấp một bộ lọc xung quanh một HTTP request. Khi bắt đầu một request, nó sẽ thiết lập một contest (chứa các transaction và liên kết JDBC) được ràng buộc với luồng request. Khi request đó kết thúc, bộ lọc hủy bỏ contest này.

2. Node.js là gì?

Node.js là một hệ thống phần được thiết kế để viết các ứng dụng internet có khả năng mở rộng, đặc biệt là máy chủ web. Chương trình được viết bằng JavaScript, sử dụng kỹ thật điều khiển theo sự kiện, nhập/xuất không đồng bộ để tối thiểu tổng chi phí và tối đại khả năng mở rộng. Node.js bao gồm có V8 JavaScript engine của Google,libUV, và vài thư viện khác.

Lợi thế của Node.js để lập trình web-socket:

  • Thứ nhất: javascript là ngôn ngữ lập trình hướng sự kiện, mà trong lập trình thời gian thực, cách tiếp cận bằng lập trình sự kiện là cách tiếp cận khôn ngoan nhất.
  • Thứ hai: Node.js chạy non-blocking việc hệ thống không phải tạm ngừng để xử lý xong một request sẽ giúp cho server trả lời client gần như ngay tức thì.
  • Thứ ba: lập trình socket yêu cầu bạn phải xây dựng được mô hình lắng nghe – trả lời từ cả 2 bên. Nói khác đi, vai trò của client và server phải tương đương nhau, mà client thì chạy bằng javascript, nên nếu server cũng chạy bằng javascript nữa, thì việc lập trình sẽ dễ dàng và thân thiện hơn.

Giao thức bắt tay của WebSocket:

Hướng dẫn websocket trong javascript

3. Cài đặt công cụ

Bạn cài đặt NodeJs theo thứ tự các dòng lệnh sau:

sudo apt-get update
sudo apt-get install python-software-properties python g++ make
sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs

Tiếp đó, bạn cài đặt thêm node-uuid (sẽ giới thiệu kỹ hơn về UUID ở phần sauu) theo lệnh:

npm install node-uuid

4. WebSocket Events

WebSocket hỗ trợ bốn sự kiện, chúng đều đã có sẵn trong JavaScript API và được xác định theo W3C: • open • message • error • close

Với JavaScript, you listen for these events to fire either with the handler on , or the addEventListener() method. Your code will provide a callback that will execute every time that event gets fired.

4.1 Event: Open

Khi WebSocket server phản hồi với yêu cầu kết nối, và quy trình bắt tay hoàn thành, sự kết nối giữa client và server được thiết lập. Khi đó, server đã sẵn sàng để gửi và nhận messages từ ứng dụng client.

// WebSocket connection established
ws.onopen = function(e) {
    console.log("Connection established");
    ws.send(JSON.stringify(stock_request));
};

Ví dụ trên thể hiện việc client hiển thị trạng thái kết nối thành công ra màn hình, kết nối được thiết lập, sẵn sàng cho việc trao đổi thông tin hai chiều. Sau đó client đã gửi một thông điệp đầu tiên lên server.

4.2 Event: Message

Sau khi kết nối thành công với websocket server, client sẽ có thể gửi và nhận thông điệp từ server (sử dụng các Websocket methods sẽ được đề cập phía sau bài viết này). Các WebSocket API sẽ chuẩn bị các thông điệp đầy đủ để được xử lý trong onmessage.

4.3 Event: Error

Khi có lỗi xảy ra với bất kỳ lý do gì. Giả sử rằng sự kết nối WebSocket bị đóng. Khi đó event close xảy ra ngay sau khi một trong các lỗi xuất hiện. Sau đây là một ví dụ về cách xử lý trong trường hợp xảy ra lỗi :

ws.onerror = function(e) {
    console.log("WebSocket failure, error", e);
    handleErrors(e);
};

4.4 Event: Close

Sự kiên close được gọi khi kết nối WebSocket đóng, và các sự kiện onerror sẽ được thực thi. Sau khi sự kết nối được đóng, sự giao tiếp giữa server và client cũng sẽ không đưuọc tiếp tục. Ví dụ sau sẽ trả về mảng giá trị 0 khi kết nối đã được đóng.

ws.onclose = function(e) {
    console.log(e.reason + " " + e.code);
    for(var symbol in stocks) {
        if(stocks.hasOwnProperty(symbol)) {
        	stocks[symbol] = 0;
    	}
    }
}
ws.close(1000, 'WebSocket connection closed')

5. WebSocket Methods

Các phương thức của WebSocket khá đơn giản, chỉ gồm hai phương thức: send()close().

5.1 Phương thức: Send

Khi sự kết nối được thiết lập, bạn sẽ sẵn sàng để nhận (gửi) thông điệp từ (đến) WebSocket server.

var ws = new WebSocket("ws://localhost:8181");
ws.onopen = function(e) {
	ws.send(JSON.stringify(stock_request));
}

5.2 Phương thức: Close

Sự kết nối WebSocket được đóng thông qua phương thức close(). Sau khi phương thức close() được gọi, sẽ không còn bất cứ dữ liệu nào được trao đổi. Ví dụ:

// Close WebSocket connection
ws.close();
// Close the WebSocket connection with reason.
ws.close(1000, "Goodbye, World!");

6. Xây dựng ứng dụng chat

6.1 Server

Trong phần này chúng ta sẽ xây dựng môt ứng dụng chat hoàn chỉnh với chức năng đơn giản hoàn chỉnh. Trước tiên, chúng ta khởi tạo một WebSocket server với cổng 8181.

var WebSocketServer = require('ws').Server,
	wss = new WebSocketServer({port: 8181});

Giao thức websocket thông thưởng không hỗ trợ các tính năng mặc định, chúng ta phải tự tạo ra chúng. Sau này khi làm việc với Socket.IO thì sẽ có nhiều API để thuận tiện hơn trong khi xây dựng ứng dụng.

Tiếp đó, chúng ta cần nhập vào một module Node để có thể sinh ra một UUID (UUID dung để xác định từng client đã kết nối với server và đưa chúng vào một một sưu tập).

var uuid = require("node-uuid");
var clients = [];
wss.on('connection', function(ws) {
    var client_uuid = uuid.v4();
    clients.push({"id": client_uuid, "ws": ws});
    console.log('client [%s] connected', client_uuid);

Khi server nhận được thông điệp từ client, nó sẽ duyệt lần lượt qua từng client trong bộ sưu tập đã tạo bên trên, và gửi lại một đối tượng JSON chứa thông điệp đã nhận được từ client đã gửi đi. Trên giao diện của các client sẽ chỉ cập nhật lại các thông điệp khi nó đã nhận được từ server. Từ đây, tất cả các client đã kết nối thành công tới server đều có thể nhận được thông điệp trên và trả về cho người dùng:

ws.on('message', function(message) {
    for(var i=0; i<clients.length; i++) {
        var clientSocket = clients[i].ws;
        if(clientSocket.readyState === WebSocket.OPEN) {
            console.log('client [%s]: %s', clients[i].id, message);
            clientSocket.send(JSON.stringify({
                "id": client_uuid,
                "message": message
            }));
        }
    }
});

Cuối cùng server cần xử lý sự kiện đóng kết nối:

ws.on('close', function() {
    for(var i=0; i<clients.length; i++) {
        if(clients[i].id == client_uuid) {
            console.log('client [%s] disconnected', client_uuid);
            clients.splice(i, 1);
        }
    }
});

6.2 WebSocket Client

Client nhận thông điệp từ server ở dạng một đối tượng JSON. Sử dụng hàm dựng sẵn để phân tích thông điệp đó để thể hiện lên giao diện người dùng.

ws.onmessage = function(e) {
    var data = JSON.parse(e.data);
    var messages = document.getElementById('messages');
    var message = document.createElement("li");
    message.innerHTML = data.message;
    messages.appendChild(message);
}

Sự kiện và thông báo

Chúng ta có thể gửi thông báo tới tất cả các client đã kết nối, xử lý trạng thái kết nối:

wss.on('connection', function(ws) {
    ...
    wsSend("message", client_uuid, nickname, message);
    ...
});

Việc gửi thông điệp tới tất cả các client về trạng thái kết nối, ngắt kết nối, hay bất kỳ thông báo nào cũng dựa trên cơ chế tương tự khi server gửi đi thông điệp nhận được từ một client tới tất cả các client đã kết nối.

Sau đây là phần code khá hoàn thiện cho một ứng dụng chat nho nhỏ giúp các bạn tham khảo dễ dàng hơn:

Phía Server

var WebSocket = require('ws');
var WebSocketServer = WebSocket.Server,
    wss = new WebSocketServer({port: 8181});
var uuid = require('node-uuid');

var clients = [];

var clientIndex = 1;

wss.on('connection', function(ws) {
  var client_uuid = uuid.v4();
  var nickname = "AnonymousUser" + clientIndex;
  clientIndex+=1;
  clients.push({"id": client_uuid, "ws": ws, "nickname": nickname});
  console.log('client [%s] connected', client_uuid);
  ws.on('message', function(message) {
    if(message.indexOf('/nick') == 0) {
      var nickname_array = message.split(' ')
      if(nickname_array.length >= 2) {
        var old_nickname = nickname;
        nickname = nickname_array[1];
        for(var i=0; i<clients.length; i++) {
          var clientSocket = clients[i].ws;
          var nickname_message = "Client " + old_nickname + " changed to " + nickname;
          clientSocket.send(JSON.stringify({
            "id": client_uuid,
            "nickname": nickname,
            "message": nickname_message
          }));
        }
      }
    } else {
      for(var i=0; i<clients.length; i++) {
          var clientSocket = clients[i].ws;
          if(clientSocket.readyState === WebSocket.OPEN) {
              console.log('client [%s]: %s', clients[i].id, message);
              clientSocket.send(JSON.stringify({
                  "id": client_uuid,
                  "nickname": nickname,
                  "message": message
              }));
          }
      }
    }
  });

  ws.on('close', function() {
    for(var i=0; i<clients.length; i++) {
        if(clients[i].id == client_uuid) {
            console.log('client [%s] disconnected', client_uuid);
            clients.splice(i, 1);
        }
    }
  });
});

Phía Client

<!DOCTYPE html>
<html lang="en">
<head>
<title>Bi-directional WebSocket Chat Demo</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
        var ws = new WebSocket("ws://localhost:8181");
        var nickname = "";
        ws.onopen = function(e) {
          console.log('Connection to server opened');
        }
        function appendLog(nickname, message) {
          var messages = document.getElementById('messages');
          var messageElem = document.createElement("li");
          var preface_label;
          if(nickname==='*') {
              preface_label = "*";
          } else {
              preface_label = "" + nickname + "";
          }
          var message_text = "

" + preface_label + " " + message + "

"
; messageElem.innerHTML = message_text; messages.appendChild(messageElem); } ws.onmessage = function(e) { var data = JSON.parse(e.data); nickname = data.nickname; appendLog(data.nickname, data.message); console.log("ID: [%s] = %s", data.id, data.message); } ws.onclose = function(e) { appendLog("*", "Connection closed"); console.log("Connection closed"); } function sendMessage() { var messageField = document.getElementById('message'); if(ws.readyState === WebSocket.OPEN) { ws.send(messageField.value); } messageField.value = ''; messageField.focus(); } function disconnect() { ws.close(); } </script> </head> <body lang="en"> <div class="vertical-center"> <div class="container"> <ul id="messages" class="list-unstyled"> </ul> <hr /> <form role="form" id="chat_form" onsubmit="sendMessage(); return false;"> <div class="form-group"> <input class="form-control" type="text" name="message" id="message" placeholder="Type text to echo in here" value="" autofocus/> </div> <button type="button" id="send" class="btn btn-primary" onclick="sendMessage();">Send Message</button> </form> </div> </div> </body> </html>

Bạn cũng có thể tải code về tại: GitHub

Chúng ta sẽ tiếp tục với WebSocket trong các bài viết sau. Hẹn gặp lại các bạn!