Ruby - WebSockets
Trước đây để thực hiện giao tiếp giữa client và server, người ta có sử dụng một số công nghệ như là: sử dụng HTTP truyền thống, sau đó là sự ra đời của Ajax polling, phổ biến nhất phải kể đến Ajax long-polling...Nhưng nhận thấy một điểm khi sử dụng những công nghệ kể trên đều có một số nhược điểm ...
Trước đây để thực hiện giao tiếp giữa client và server, người ta có sử dụng một số công nghệ như là: sử dụng HTTP truyền thống, sau đó là sự ra đời của Ajax polling, phổ biến nhất phải kể đến Ajax long-polling...Nhưng nhận thấy một điểm khi sử dụng những công nghệ kể trên đều có một số nhược điểm như là khi sử dụng HTTP thì chứa qúa nhiều dữ liệu không cần thiết trong phần header nếu truyền tải một lượng dữ liệu lớn thì thời gian chuyển giao dữ liệu sẽ tăng lên(latency rất lớn). Còn đối với Ajax long-polling thì client gửi yêu cầu tới server và server sẽ trả về cho client khi có dữ liệu mới, sau đó client ngay lập tức gủi một yêu cầu khác tới server, có nghĩa là nếu thời gian chuyển giao(latency) càng ngắn thì client sẽ gửi nhiều yêu cầu hơn trong đó có những yêu cầu sẽ không trả về bất cứ dữ liệu nào dẫn đến tình trạng băng thông bị hao tốn và xử lý tài nguyên vô ích. Chính vì những lý do đó người ta nghĩ ra một công nghệ là WebSocket để khắc phục được những nhược điểm đó.
WebSocket: là một công nghệ hỗ trợ giao tiếp full-duplex(tức là cùng một lúc có thể gửi và nhận dữ liệu đồng thời). Được thiết kết cho các ứng dụng web.
sử dụng kết nối TCP
là một tập của nhiều chuẩn: WebSocket API(W3C), WebSocket protocol(RFC 6455) và phần mở rộng của nó được định nghĩa bởi HyBi Working Group(IETF).
WebSocket URL sử dụng ws và wss lần lượt cho các kết nối WebSocket không bảo mật và kết nối WebSocket bảo mật.
được sử dụng trong các ứng dụng tài chính, games, mạng xã hội(chat), quản lý, thiết bị nhúng,...
hỗ trợ trong nhiều trình duyệt. Có thể xem cụ thể ở đây: http://caniuse.com/#feat=websockets
Ưu điểm: hỗ trợ giao tiếp full-duplex mạnh mẽ, latency thấp và dễ xử lý lỗi. API dễ sử dụng. tạo các kết nối bền bỉ đến máy chủ mà vẫn mở cho đến khi các ứng dụng khách hàng được đóng lại. (Không cần mở nhiều kết nối nên giảm thiểu băng thông).
Nhược điểm: do dữ kết nối mở trên server trong suốt thời gian người dùng giao tiếp với trang nên sẽ làm tăng yêu cầu trên server.
trong Ruby có một số thư viện hỗ trợ như là:
sinatra-websocket tubesock webmachine-ruby websocket-rails em-websocket faye-websocket ActionCable (Rails 5)
Trong bài viết này sẽ viết code một ứng dụng chat đơn giản sử dụng gem em-websocket để hiểu rõ hơn về WebSocket trong Ruby
Đầu tiên ta cần cài một số gem hỗ trợ
Viết code bên phía server (WebSocket Server):gem "thin" (https://github.com/macournoyer/thin) gem "sinatra" (https://github.com/sinatra/sinatra) gem "em-websocket" (https://github.com/igrigorik/em-websocket)
Đầu tiên tạo ra một Websocket-server lắng nghe ở localhost:3001
#app.rb EM::WebSocket.start(host: '0.0.0.0', port: '3001') do |ws| //to do end
Có 3 sự kiện Websocket quan trọng trong app này là: onopen, onmessage và onclosed
onopen : Nó được triệu gọi khi có kết nối mới trên server. Ở đây chúng ta sẽ lưu trữ client đã được kết nối trong một mảng @clients.
#app.rb ws.onopen do |handshake| @clients << ws ws.send "Connected to #{handshake.path}." end
onmessage : Nó được triệu gọi khi có WebSocket message được tiếp nhận bởi server. Ở đây chúng ta cần gửi message tới mỗi client được kết nối.
#app.rb ws.onmessage do |msg| puts "Received Message: #{msg}" @clients.each do |socket| socket.send msg end end
onclose : Nó được triệu gọi khi client đóng kết nối. Ở đây chúng ta cần xóa client trong mảng lưu trữ client của chúng ta.
#app.rb ws.onclose do ws.send "Closed." @clients.delete ws end
Toàn bộ code được viết ở phía server:
require 'thin' require 'em-websocket' require 'sinatra/base' EM.run do class App < Sinatra::Base get '/' do erb :index end end @clients = [] EM::WebSocket.start(host: '0.0.0.0', port: '3001') do |ws| ws.onopen do |handshake| @clients << ws ws.send "Connected to #{handshake.path}." end ws.onclose do ws.send "Closed." @clients.delete ws end ws.onmessage do |msg| puts "Received Message: #{msg}" @clients.each do |socket| socket.send msg end end end App.run! port: 3000 endViết code bên phía client (WebSocket Client)
Sử dụng javascript. Đầu tiên khởi tạo một WebSocket.
host = "ws://localhost:3001" socket = new WebSocket(host);
onopen: Sau khi kết nối được thiết lập thì sự kiện open sẽ được gọi trên đối tượng WebSocket.
socket.onopen = function() { addMessage("Socket Status: " + socket.readyState + " (open)"); }
onclose: đóng kết nối.
socket.onclose = function() { addMessage("Socket Status: " + socket.readyState + " (closed)"); }
onmessage: tiếp nhận messages.
socket.onmessage = function(msg) { addMessage("Received: " + msg.data); }
Gửi messages: Để gửi một message qua kết nối WebSocket, chúng ta sử dụng phương thức send() trên đối tượng WebSocket và truyền vào dữ liệu muốn gửisocket.send(text)
Toàn bộ code phía client:
#app.js var socket; function addMessage(msg) { $("#chat-log").append("<p>" + msg + "</p>"); } function connect() { try { socket = new WebSocket("ws://localhost:3001"); addMessage("Socket State: " + socket.readyState); socket.onopen = function() { addMessage("Socket Status: " + socket.readyState + " (open)"); } socket.onclose = function() { addMessage("Socket Status: " + socket.readyState + " (closed)"); } socket.onmessage = function(msg) { addMessage("Received: " + msg.data); } } catch(exception) { addMessage("Error: " + exception); } } function send() { var text = $("#message").val(); if (text == ') { addMessage("Please Enter a Message"); return; } try { socket.send(text); addMessage("Sent: " + text) } catch(exception) { addMessage("Failed To Send") } $("#message").val('); } $(function() { connect(); }); $('#message').keypress(function(event) { if (event.keyCode == '13') { send(); } }); $("#disconnect").click(function() { socket.close() });
Tạo một view index.erb
<!doctype html> <html> <head> </head> <body> <div class="container"> <h1>WebSockets Chat App</h1> <div id="chat-log"></div> <div id="form"> <input type="text" id="message"> <button id="disconnect">Disconnect</button> </div> </div> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"> </script> <script type="text/javascript" src="app.js"></script> </body> </html>
Chạy lệnhruby app.rb hoặc bundle exec ruby app.rb