Представлення WebSocket згідно з AI. Створено за допомогою AI.
Декілька тижнів тому я зацікавився вивченням того, як працюють додатки в реальному часі, та їх зв'язок з WebSocket. Я чув про WebSocket раніше і знав, як вони працюють теоретично, але ніколи не використовував їх на практиці, тому захотів створити щось, що дасть мені цей досвід, сподіваючись, що я зможу чогось навчитися.
Створення додатку для чату в реальному часі — чудовий проєкт, щоб побудувати і вивчити принципи роботи. Це просто класика, яку я мав зробити. Хороший чат-додаток повинен мати три базові дії: приєднатися, надіслати повідомлення і вийти.
Але що таке WebSocket?
Лялька з шкарпетки на ім’я Fu. Джерело: https://commons.wikimedia.org/wiki/File:Sock-PuppetFu.jpg_
Гаразд, друзі, сідайте, і давайте згадуємо, що таке WebSocket і чому вони корисні. Це може викликати кілька запитань.
Що таке WebSocket?
Як вони відрізняються від звичайних HTTP запитів?
Коли вони необхідні?
Чи відрізняється робота з WebSocket від роботи з HTTP?
WebSocket — це сучасний протокол для комунікації, який розроблений для наших потреб у двосторонньому, реальному часі. На відміну від HTTP, що вимагає слідувати моделі запит-відповідь, де клієнт повинен ініціювати кожну взаємодію, у WebSocket сервер і клієнт можуть надсилати дані в будь-який час, встановлюючи постійне з'єднання з обох сторін.
Ось деякі ключові відмінності між HTTP і WebSocket:
Ключові відмінності між WebSocket і HTTP протоколами.
Отже, чи мають вони постійне з'єднання?
Так, мають. Як тільки з'єднання WebSocket встановлено, воно залишається відкритим, поки одна зі сторін (клієнт або сервер) явно не закриє його. Такий постійний стан усуває необхідність повторних рукостискань, зменшуючи латентність і використання ресурсів, а також підвищує ефективність.
Стандартна робота з WebSocket: подієва комунікація.
WebSocket за своєю природою працює за моделлю подієвої комунікації завдяки різним факторам. Основна причина в тому, що дані у WebSocket можуть надходити в непередбачуваний час. Наприклад:
- Сервер може надіслати повідомлення клієнту, коли з'являються нові дані.
- Клієнт може надіслати повідомлення, коли відбувається взаємодія користувача.
Отже, подієва модель дозволяє “слухати” ці події та відповідати на них без необхідності постійно перевіряти оновлення, як це відбувається в інших моделях, наприклад, в моделі опитування.
Популярні бібліотеки WebSocket API, як браузерний WebSocket API, обробляють події на кшталт “onopen”, “onmessage”, “onclose” та “onerror”. Це майже стандарт для бібліотек WebSocket, але не плутайте — це лише стандартизована абстракція, надана більшістю бібліотек WebSocket, і не є частиною протоколу.
Як ти реалізував серверну та клієнтську логіку?
По-перше, ви можете побачити весь код у цьому репозиторії. Я зробив його відкритим. Також ви можете спробувати це з реалізацією WebSocket-сервера, який я розгорнув на віртуальній машині Azure. Дотримуйтесь інструкцій у файлі README.md, але я все ж надам вам команду для тестування.
Не соромтеся зв’язатися через мій аккаунт X або через email, якщо хочете підключитися; я буду радий поспілкуватися!
# Щоб спробувати в режимі онлайн
bundle exec ruby ./client/app.rb ws://172.203.64.105:9292
Для реалізації як сервера, так і клієнта я використав мінімалістичну бібліотеку WebSocket, яка називається faye/websocket-ruby
.
It is easy to learn and get used to, and it is rack-compliant, so the server is easy to set up with Puma and Rackup.
Реалізація клієнта.
Для реалізації клієнта я розділив код на три файли: app.rb
, chat_cli.rb
та chat_client.rb
.
app.rb
: Я використовую цей файл як точку входу для запуску клієнтського додатку та отримання аргументів, які користувач передає при запуску програми.chat_cli.rb
: Тут я в основному ініціалізую клас/обгортку WebSocket Client та ініціалізую GUI та цикл, який він виконує.
У chat_client.rb
знаходиться основна частина реалізації клієнта WebSocket. Пам’ятайте, що необхідно використовувати подієву модель (event-driven model) для клієнта, щоб усе працювало. Рукостискання не завершується, поки і клієнт, і сервер не виконають зворотний виклик on :open
.
# frozen_string_literal: true
require 'faye/websocket'
require 'eventmachine'
require 'json'
class ChatClient
attr_reader :connected, :server_url, :name
def initialize(server_url, name)
@server_url = server_url
@name = name
@ws = nil
@connected = false
end
def connect
EM.run do
@ws = Faye::WebSocket::Client.new(server_url)
@ws.on :open do |_event|
puts 'Connected to the server!'
@connected = true
send_message('System', "#{name} has joined the chat.")
end
@ws.on :message do |event|
data = JSON.parse(event.data)
puts "[#{data['name']}] #{data['message']}"
print '> '
end
@ws.on :close do |event|
puts "Connection closed. Code: #{event.code} Reason: #{event.reason}"
@connected = false
@ws = nil
EM.stop
end
end
end
def send_message(name, message)
return unless @connected
data = { name: name, message: message }
@ws.send(data.to_json)
end
def disconnect
return unless @connected
send_message('System', "#{name} has left the chat.")
@ws.close
end
end
Логіка, якої я дотримувався, полягає в тому, що кожного разу, коли сервер транслює повідомлення, я його виводжу. Далі виводжу “>”, щоб користувач міг продовжити вводити своє повідомлення.
Реалізація сервера.
На серверній стороні я створив два важливі файли: config.ru
та app.rb
. Перший конфігурує та робить додаток Faye сумісним з Rack, тоді як другий містить всю серверну логіку Faye.
Спочатку я неправильно налаштував сервер, щоб він транслював лише повідомлення, яке отримав від підключеного клієнта. Я виправив це, створивши масив із усіма активними підключеннями та транслюючи повідомлення всім підключеним клієнтам.
# frozen_string_literal: true
require 'faye/websocket'
require 'json'
App = lambda do |env|
@clients ||= []
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env)
ws.on :open do |_event|
puts 'Client connected'
@clients << ws
end
ws.on :message do |event|
puts "Received message: #{event.data}"
@clients.each do |client|
next if client == ws
client.send(event.data)
end
end
ws.on :close do |event|
p [:close, event.code, event.reason]
@clients.each do |client|
next if client == ws
data = { name: 'System', message: 'Someone has disconnected' }
client.send data.to_json
end
@clients.delete ws
ws = nil
end
# Return async Rack response
ws.rack_response
else
# Normal HTTP request
[200, { 'Content-Type' => 'text/plain' }, ['Hello']]
end
end
Для реалізації на віртуальній машині Azure я використав Docker, і це було дуже корисно, але краще буде описати це в іншому блозі.
Let me know if you want to hear about it in the comments.
Які покращення я міг би додати до цього додатку?
Я думаю, що є багато чого, що можна покращити, але деякі речі, які я б хотів зробити в майбутньому:
- Додати підтримку кількох кімнат.
- Зберігати надіслані повідомлення (потрібно оцінити, чи краще використовувати SQL чи NoSQL базу даних).
- Додати SSL до серверного додатку.
- Оптимізувати роботу з багатьма клієнтами одночасно: при видаленні елемента з масиву, масив може бути повністю переприсвоєний. Використання зв'язного списку могло б бути корисним.
- Використовувати WebSocket для реального часу в іграх!
Висновок.
Створення цього чату з використанням WebSocket в реальному часі навчило мене багатьох корисних речей. Я покращив своє розуміння роботи WebSocket, зокрема, як вони функціонують і як їх реалізувати.
Також я краще зрозумів подієві моделі (event-driven models) та як з ними працювати. Я щиро вірю, що саме вирішення таких завдань — це шлях до того, щоб стати кращим розробником, і це справді задовольняє мою душу, коли я вивчаю речі, з якими не часто доводиться працювати. Якщо ви маєте інші цікаві ідеї для викликів, будь ласка, дайте знати!
Перекладено з: Building a Real-time chat app with WebSockets and Ruby