Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

การสร้างเซิร์ฟเวอร์ websockets อย่างง่ายตั้งแต่เริ่มต้นใน Ruby

Websockets ได้รับความนิยมมากขึ้นทุกวัน เราได้ยินมาว่าพวกเขาคือ "อนาคต" เราได้ยินมาว่าใช้งานได้ง่ายกว่าที่เคยด้วย ActionCable ใน Rails 5 แต่จริงๆ แล้ว websockets คืออะไรกันแน่? ทำงานอย่างไร

ในโพสต์นี้ เราจะตอบคำถามเหล่านี้โดยการสร้างเซิร์ฟเวอร์ WebSocket แบบง่ายๆ ตั้งแต่เริ่มต้นใน Ruby เมื่อเสร็จแล้ว เราจะสามารถสื่อสารแบบสองทิศทางระหว่างเบราว์เซอร์และเซิร์ฟเวอร์ของเราได้

รหัสในโพสต์นี้มีขึ้นเพื่อเป็นแบบฝึกหัดการเรียนรู้ หากคุณต้องการใช้งาน websockets ในแอปที่ใช้งานจริง ให้ลองดู websocket-ruby gem ที่ยอดเยี่ยม คุณอาจดูที่ WebSocket Spec.

คุณจึงไม่เคยได้ยินเกี่ยวกับ websockets มาก่อน

เว็บซ็อกเก็ตถูกคิดค้นขึ้นเพื่อแก้ปัญหาบางอย่างในการเชื่อมต่อ HTTP ปกติ เมื่อคุณขอเว็บเพจโดยใช้การเชื่อมต่อ HTTP ปกติ เซิร์ฟเวอร์จะส่งเนื้อหาให้คุณแล้วปิดการเชื่อมต่อ หากคุณต้องการขอหน้าอื่น คุณต้องทำการเชื่อมต่อใหม่ ปกติแล้ววิธีนี้ใช้ได้ดี แต่ไม่ใช่วิธีที่ดีที่สุดสำหรับกรณีการใช้งานบางกรณี:

  • สำหรับบางแอปพลิเคชัน เช่น แชท จำเป็นต้องอัปเดตส่วนหน้าทันทีที่มีข้อความใหม่เข้ามา หากคุณมีเพียงคำขอ HTTP ปกติ แสดงว่าคุณต้องสำรวจเซิร์ฟเวอร์อย่างต่อเนื่องเพื่อดูว่ามีหรือไม่ เนื้อหาใหม่
  • หากแอปพลิเคชันส่วนหน้าของคุณต้องการส่งคำขอจำนวนเล็กน้อยไปยังเซิร์ฟเวอร์ ค่าใช้จ่ายในการสร้างการเชื่อมต่อใหม่สำหรับแต่ละคำขออาจกลายเป็นปัญหาด้านประสิทธิภาพ นี่เป็นปัญหาน้อยกว่าใน HTTP2

เมื่อใช้ซ็อกเก็ตเว็บ คุณจะทำการเชื่อมต่อกับเซิร์ฟเวอร์หนึ่งรายการ จากนั้นเปิดค้างไว้และใช้สำหรับการสื่อสารแบบสองทิศทาง

ฝั่งไคลเอ็นต์

โดยปกติเว็บซ็อกเก็ตจะใช้สำหรับการสื่อสารระหว่างเบราว์เซอร์และเว็บเซิร์ฟเวอร์ ฝั่งเบราว์เซอร์มีการใช้งานใน JavaScript ในตัวอย่างด้านล่าง ฉันได้เขียน JavaScript แบบง่ายๆ เพื่อเปิดซ็อกเก็ตเว็บไปยังเซิร์ฟเวอร์ในเครื่องของฉันและส่งข้อความไปที่มัน

<!doctype html>
<html lang="en">
<head>
  <title>Websocket Client</title>
</head>
<body>
  <script>
    var exampleSocket = new WebSocket("ws://localhost:2345");
    exampleSocket.onopen = function (event) {
      exampleSocket.send("Can you hear me?");
    };
    exampleSocket.onmessage = function (event) {
      console.log(event.data);
    }
  </script>
</body>
</html>

หากฉันเปิดเซิร์ฟเวอร์สแตติกขนาดเล็กและเปิดไฟล์นี้ในเว็บเบราว์เซอร์ ฉันจะได้รับข้อผิดพลาด สมเหตุสมผลแล้วเพราะยังไม่มีเซิร์ฟเวอร์ เรายังต้องสร้าง :-)

การสร้างเซิร์ฟเวอร์ websockets อย่างง่ายตั้งแต่เริ่มต้นใน Ruby

การเริ่มต้นเซิร์ฟเวอร์

เว็บซ็อกเก็ตเริ่มต้นชีวิตตามคำขอ HTTP ปกติ พวกมันมีวงจรชีวิตที่แปลก:

  1. เบราว์เซอร์ส่งคำขอ HTTP ปกติ โดยมีส่วนหัวพิเศษที่ระบุว่า "โปรดสร้าง websocket ให้ฉัน"
  2. เซิร์ฟเวอร์ตอบกลับด้วยการตอบสนอง HTTP บางอย่าง แต่ไม่ได้ปิดการเชื่อมต่อ
  3. เบราว์เซอร์และเซิร์ฟเวอร์ใช้โปรโตคอล websocket พิเศษเพื่อแลกเปลี่ยนเฟรมข้อมูลผ่านการเชื่อมต่อแบบเปิด

ดังนั้นขั้นตอนแรกสำหรับเราคือการสร้างเว็บเซิร์ฟเวอร์ ในโค้ดด้านล่าง ฉันกำลังสร้างเว็บเซิร์ฟเวอร์ที่ง่ายที่สุด มันไม่ได้ให้บริการอะไรจริงๆ เพียงแค่รอคำขอแล้วพิมพ์ไปที่ STDERR

require 'socket'

server = TCPServer.new('localhost', 2345)

loop do

  # Wait for a connection
  socket = server.accept
  STDERR.puts "Incoming Request"

  # Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
  http_request = ""
  while (line = socket.gets) && (line != "\r\n")
    http_request += line
  end
  STDERR.puts http_request
  socket.close
end

ถ้าฉันเปิดเซิร์ฟเวอร์ และรีเฟรชหน้าทดสอบ websocket ฉันจะได้รับสิ่งนี้:

$ ruby server1.rb
Incoming Request
GET / HTTP/1.1
Host: localhost:2345
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: cG8zEwcrcLnEftn2qohdKQ==

หากคุณจะสังเกตเห็น คำขอ HTTP นี้มีส่วนหัวจำนวนมากที่เกี่ยวข้องกับซ็อกเก็ตเว็บ นี่เป็นขั้นตอนแรกในการจับมือกันของ websocket

การจับมือกัน

คำขอเว็บซ็อกเก็ตทั้งหมดเริ่มต้นด้วยการจับมือกัน ทั้งนี้เพื่อให้แน่ใจว่าทั้งไคลเอ็นต์และเซิร์ฟเวอร์ต่างเข้าใจว่าซ็อกเก็ตเว็บกำลังจะเกิดขึ้น และทั้งคู่ก็เห็นด้วยกับเวอร์ชันโปรโตคอล มันทำงานดังนี้:

ไคลเอนต์ส่งคำขอ HTTP แบบนี้

GET / HTTP/1.1
Host: localhost:2345
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: E4i4gDQc1XTIQcQxvf+ODA==
Sec-WebSocket-Version: 13

ส่วนที่สำคัญที่สุดของคำขอนี้คือ Sec-WebSocket-Key . ไคลเอ็นต์คาดหวังให้เซิร์ฟเวอร์ส่งคืนเวอร์ชันที่แก้ไขของค่านี้เพื่อเป็นหลักฐานในการโจมตี XSS และแคชพร็อกซี

เซิร์ฟเวอร์ตอบสนอง

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: d9WHst60HtB4IvjOVevrexl0oLA=

การตอบสนองของเซิร์ฟเวอร์เป็นแบบสำเร็จรูป ยกเว้น Sec-WebSocket-Accept หัวข้อ. ส่วนหัวนี้ถูกสร้างขึ้นดังนี้:

# Take the value provided by the client, append a magic
# string to it. Generate the SHA1 hash, then base64 encode it.
Digest::SHA1.base64digest([sec_websocket_accept, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)

ดวงตาของคุณไม่ได้โกหกคุณ มีค่าคงที่เวทย์มนตร์ที่เกี่ยวข้อง

การจับมือกัน

มาอัปเดตเซิร์ฟเวอร์ของเราเพื่อจับมือกันให้เสร็จ ก่อนอื่น เราจะดึงโทเค็นความปลอดภัยออกจากส่วนหัวของคำขอ:

# Grab the security key from the headers.
# If one isn't present, close the connection.
if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
  websocket_key = matches[1]
  STDERR.puts "Websocket handshake detected with key: #{ websocket_key }"
else
  STDERR.puts "Aborting non-websocket connection"
  socket.close
  next
end

ตอนนี้ เราใช้คีย์ความปลอดภัยเพื่อสร้างการตอบกลับที่ถูกต้อง:

response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)
STDERR.puts "Responding to handshake with key: #{ response_key }"

socket.write <<-eos
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: #{ response_key }

eos

STDERR.puts "Handshake completed."

เมื่อฉันรีเฟรชหน้าทดสอบ websocket ฉันพบว่าไม่มีข้อผิดพลาดในการเชื่อมต่ออีกต่อไป การเชื่อมต่อถูกสร้างขึ้น!

การสร้างเซิร์ฟเวอร์ websockets อย่างง่ายตั้งแต่เริ่มต้นใน Ruby

นี่คือผลลัพธ์จากเซิร์ฟเวอร์ที่แสดงคีย์ความปลอดภัยและคีย์ตอบกลับ:

$ ruby server2.rb
Incoming Request
Websocket handshake detected with key: Fh06+WnoTQQiVnX5saeYMg==
Responding to handshake with key: nJg1c2upAHixOmXz7kV2bJ2g/YQ=
Handshake completed.

โปรโตคอลเฟรมเว็บซ็อกเก็ต

เมื่อสร้างการเชื่อมต่อ WebSocket แล้ว HTTP จะไม่ถูกใช้อีกต่อไป ข้อมูลจะถูกแลกเปลี่ยนผ่านโปรโตคอล WebSocket แทน

เฟรมเป็นหน่วยพื้นฐานของโปรโตคอล WebSocket

โปรโตคอล WebSocket เป็นแบบอิงเฟรม แต่สิ่งนี้หมายความว่าอย่างไร

เมื่อใดก็ตามที่คุณขอให้เว็บเบราว์เซอร์ส่งข้อมูลผ่าน WebSocket หรือขอให้เซิร์ฟเวอร์ตอบสนอง ข้อมูลจะถูกแบ่งออกเป็นชุดๆ ในแต่ละส่วนจะถูกห่อด้วยข้อมูลเมตาเพื่อสร้างเฟรม

นี่คือลักษณะโครงสร้างเฟรม ตัวเลขด้านบนเป็นบิต และฟิลด์บางฟิลด์ เช่น ความยาวเพย์โหลดเพิ่มเติม อาจไม่ปรากฏตลอดเวลา:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

สิ่งแรกที่อาจกระโดดออกมาที่คุณคือนี่คือโปรโตคอลไบนารี เราจะต้องจัดการเล็กน้อย แต่ไม่ต้องกังวล มันจะไม่ยากขนาดนั้น ตัวเลขด้านบนเป็นบิต และบางสาขาก็อาจไม่ปรากฏให้เห็นเสมอไป ตัวอย่างเช่น ความยาวเพย์โหลดที่ขยายจะปรากฏหากเพย์โหลดต่ำกว่า 127 ไบต์

กำลังรับข้อมูล

ตอนนี้การจับมือของพวกเขาเสร็จสิ้นแล้ว เราสามารถเริ่มแยกวิเคราะห์เฟรมไบนารีได้ เพื่อให้ง่ายขึ้น เราจะดูที่เฟรมที่เข้ามาทีละไบต์ หลังจากนั้น เราจะนำทุกอย่างมารวมกันเพื่อให้คุณได้ใช้งานจริง

ไบต์ 1:FIN และ Opcode

จากตารางด้านบน คุณจะเห็นว่าไบต์แรก (แปดบิตแรก) มีข้อมูลสองสามส่วน:

  • FIN:1 บิต หากเป็นเท็จ ข้อความจะถูกแบ่งออกเป็นหลายเฟรม
  • opcode:4 บิต บอกเราว่าเพย์โหลดเป็นข้อความ ไบนารี หรือนี่เป็นเพียง "ปิง" เพื่อให้การเชื่อมต่อคงอยู่
  • RSV:3 บิต สิ่งเหล่านี้ไม่ได้ใช้ในข้อมูลจำเพาะ WebSockets ปัจจุบัน

ในการรับไบต์แรก เราจะใช้ IO#getbyte กระบวนการ. และในการดึงข้อมูล เราจะใช้บิตมาสก์ง่ายๆ หากคุณไม่คุ้นเคยกับตัวดำเนินการระดับบิต ให้อ่านบทความอื่นของฉัน Bitwise hacks in Ruby

first_byte = socket.getbyte
fin = first_byte & 0b10000000
opcode = first_byte & 0b00001111

# Our server will only support single-frame, text messages.
# Raise an exception if the client tries to send anything else.
raise "We don't support continuations" unless fin
raise "We only support opcode 1" unless opcode == 1

ไบต์ 2:MASK และความยาวของเพย์โหลด

ไบต์ที่สองของเฟรมมีข้อมูลเพิ่มเติมเกี่ยวกับเพย์โหลด

  • มาสก์:1 บิต แฟล็กบูลีนระบุว่าเพย์โหลดถูกปิดบังหรือไม่ หากเป็นจริง จะต้อง "เปิดโปง" เพย์โหลดก่อนใช้งาน สิ่งนี้ควรเป็นจริงเสมอสำหรับเฟรมที่มาจากลูกค้าของเรา สเปกบอกไว้
  • ความยาวของเพย์โหลด:7 บิต หากเพย์โหลดของเราน้อยกว่า 126 ไบต์ ความยาวจะถูกเก็บไว้ที่นี่ หากค่านี้มากกว่า 126 แสดงว่าจะมีจำนวนไบต์ตามมามากขึ้นเพื่อให้เรามีความยาว

นี่คือวิธีที่เราจัดการกับไบต์ที่สอง:

second_byte = socket.getbyte
is_masked = second_byte & 0b10000000
payload_size = second_byte & 0b01111111

raise "All frames sent to a server should be masked according to the websocket spec" unless is_masked
raise "We only support payloads < 126 bytes in length" unless payload_size < 126

STDERR.puts "Payload size: #{ payload_size } bytes"

ไบต์ 3-7:คีย์การกำบัง

เราคาดว่าเพย์โหลดของเฟรมที่เข้ามาทั้งหมดจะถูกปิดบัง ในการเปิดโปงเนื้อหา เราจะต้อง XOR กับคีย์การกำบัง

คีย์กำบังนี้ประกอบขึ้นเป็นสี่ไบต์ถัดไป เราไม่ต้องประมวลผลเลย เราแค่อ่านไบต์ลงในอาร์เรย์

mask = 4.times.map { socket.getbyte }
STDERR.puts "Got mask: #{ mask.inspect }"

โปรดบอกฉันถ้าคุณรู้วิธีที่ดีกว่าในการอ่าน 4 ไบต์ในอาร์เรย์ times.map ค่อนข้างแปลก แต่ก็เป็นวิธีที่รัดกุมที่สุดที่ฉันคิดได้ ฉันชื่อ @StarrHorne บนทวิตเตอร์

ไบต์ 8 ขึ้นไป:เพย์โหลด

เอาล่ะ เราเสร็จสิ้นข้อมูลเมตาแล้ว ตอนนี้สามารถดึงข้อมูลเพย์โหลดที่แท้จริงได้

data = payload_size.times.map { socket.getbyte }
STDERR.puts "Got masked data: #{ data.inspect }"

โปรดจำไว้ว่าเพย์โหลดนี้ถูกปิดบัง ดังนั้นถ้าพิมพ์ออกมาก็จะดูเหมือนขยะ ในการเปิดโปง เราเพียงแค่ XOR แต่ละไบต์ด้วยไบต์ที่สอดคล้องกันของมาสก์ เนื่องจากมาสก์มีความยาวเพียง 4 ไบต์ เราจึงทำซ้ำเพื่อให้ตรงกับความยาวของเพย์โหลด:

unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
STDERR.puts "Unmasked the data: #{ unmasked_data.inspect }"

ตอนนี้เรามีอาร์เรย์ของไบต์ เราต้องแปลงเป็นสตริงยูนิโค้ด ข้อความทั้งหมดใน Websockets เป็นยูนิโค้ด

STDERR.puts "Converted to a string: #{ unmasked_data.pack('C*').force_encoding('utf-8').inspect }"

รวมทุกอย่างเข้าด้วยกัน

เมื่อคุณรวมรหัสทั้งหมดนี้เข้าด้วยกัน คุณจะได้สคริปต์ที่มีลักษณะดังนี้:

require 'socket' # Provides TCPServer and TCPSocket classes
require 'digest/sha1'

server = TCPServer.new('localhost', 2345)

loop do

  # Wait for a connection
  socket = server.accept
  STDERR.puts "Incoming Request"

  # Read the HTTP request. We know it's finished when we see a line with nothing but \r\n
  http_request = ""
  while (line = socket.gets) && (line != "\r\n")
    http_request += line
  end

  # Grab the security key from the headers. If one isn't present, close the connection.
  if matches = http_request.match(/^Sec-WebSocket-Key: (\S+)/)
    websocket_key = matches[1]
    STDERR.puts "Websocket handshake detected with key: #{ websocket_key }"
  else
    STDERR.puts "Aborting non-websocket connection"
    socket.close
    next
  end


  response_key = Digest::SHA1.base64digest([websocket_key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"].join)
  STDERR.puts "Responding to handshake with key: #{ response_key }"

  socket.write <<-eos
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: #{ response_key }

  eos

  STDERR.puts "Handshake completed. Starting to parse the websocket frame."

  first_byte = socket.getbyte
  fin = first_byte & 0b10000000
  opcode = first_byte & 0b00001111

  raise "We don't support continuations" unless fin
  raise "We only support opcode 1" unless opcode == 1

  second_byte = socket.getbyte
  is_masked = second_byte & 0b10000000
  payload_size = second_byte & 0b01111111

  raise "All incoming frames should be masked according to the websocket spec" unless is_masked
  raise "We only support payloads < 126 bytes in length" unless payload_size < 126

  STDERR.puts "Payload size: #{ payload_size } bytes"

  mask = 4.times.map { socket.getbyte }
  STDERR.puts "Got mask: #{ mask.inspect }"

  data = payload_size.times.map { socket.getbyte }
  STDERR.puts "Got masked data: #{ data.inspect }"

  unmasked_data = data.each_with_index.map { |byte, i| byte ^ mask[i % 4] }
  STDERR.puts "Unmasked the data: #{ unmasked_data.inspect }"

  STDERR.puts "Converted to a string: #{ unmasked_data.pack('C*').force_encoding('utf-8').inspect }"

  socket.close
end

เมื่อฉันรีเฟรชหน้าเว็บผู้ทดสอบ WebSocket และส่งคำขอไปยังเซิร์ฟเวอร์ของฉัน นี่คือผลลัพธ์ที่ฉันเห็น:

$ ruby websocket_server.rb
Incoming Request
Websocket handshake detected with key: E4i4gDQc1XTIQcQxvf+ODA==
Responding to handshake with key: d9WHst60HtB4IvjOVevrexl0oLA=
Handshake completed. Starting to parse the websocket frame.
Payload size: 16 bytes
Got mask: [80, 191, 161, 254]
Got masked data: [19, 222, 207, 222, 41, 208, 212, 222, 56, 218, 192, 140, 112, 210, 196, 193]
Unmasked the data: [67, 97, 110, 32, 121, 111, 117, 32, 104, 101, 97, 114, 32, 109, 101, 63]
Converted to a string: "Can you hear me?"

การส่งข้อมูลกลับไปยังไคลเอนต์

ดังนั้นเราจึงส่งข้อความทดสอบจากลูกค้าของเราไปยังเซิร์ฟเวอร์ของเล่น WebSocket ของเราได้สำเร็จ ตอนนี้คงเป็นการดีที่จะส่งข้อความกลับจากเซิร์ฟเวอร์ไปยังไคลเอนต์

สิ่งนี้เกี่ยวข้องน้อยกว่าเล็กน้อย เนื่องจากเราไม่ต้องจัดการกับสิ่งปิดบังใดๆ เฟรมที่ส่งจากเซิร์ฟเวอร์ไปยังไคลเอนต์จะถูกเปิดออกเสมอ

เช่นเดียวกับที่เราใช้เฟรมครั้งละหนึ่งไบต์ เราจะสร้างมันครั้งละหนึ่งไบต์

ไบต์ 1:FIN และ opcode

เพย์โหลดของเราจะพอดีกับเฟรมเดียว และมันจะเป็นข้อความ นั่นหมายความว่า FIN จะเท่ากับ 1 และ opcode ก็เท่ากับหนึ่งด้วย เมื่อฉันรวมกลุ่มที่ใช้รูปแบบบิตเดียวกันกับที่เราเคยใช้มาก่อน ฉันได้ตัวเลข:

output = [0b10000001]

ไบต์ 2:MASKED และความยาวของเพย์โหลด

เนื่องจากเฟรมนี้เปลี่ยนจากเซิร์ฟเวอร์ไปยังไคลเอ็นต์ MASKED จะเท่ากับศูนย์ นั่นหมายความว่าเราสามารถเพิกเฉยได้ ความยาวเพย์โหลดเป็นเพียงความยาวของสตริง

output = [0b10000001, response.size]

ไบต์ 3 ขึ้นไป:เพย์โหลด

เพย์โหลดไม่ได้ถูกปิดบัง มันเป็นแค่สตริง

response = "Loud and clear!"
STDERR.puts "Sending response: #{ response.inspect }"

output = [0b10000001, response.size, response]

ระเบิดทิ้ง!

ณ จุดนี้ เรามีอาร์เรย์ที่มีข้อมูลที่เราต้องการส่ง เราจำเป็นต้องแปลงค่านี้เป็นสตริงไบต์ที่เราสามารถส่งผ่านสายได้ ในการทำเช่นนี้ เราจะใช้ Array#pack . อเนกประสงค์ วิธีการ

socket.write output.pack("CCA#{ response.size }")

สตริงแปลก ๆ "CCA#{ response.size }" บอก Array#pack ที่อาร์เรย์มีอินต์ 8 บิตที่ไม่ได้ลงชื่อสองตัว ตามด้วยสตริงอักขระที่มีขนาดที่ระบุ

หากฉันเปิดโปรแกรมตรวจสอบเครือข่ายใน Chrome ฉันจะเห็นว่าข้อความส่งเสียงดังและชัดเจน

การสร้างเซิร์ฟเวอร์ websockets อย่างง่ายตั้งแต่เริ่มต้นใน Ruby

เครดิตเสริม

แค่นั้นแหละ! ฉันหวังว่าคุณจะได้เรียนรู้บางอย่างเกี่ยวกับ WebSockets มีหลายสิ่งหลายอย่างที่เซิร์ฟเวอร์ขาดหายไป หากคุณต้องการออกกำลังกายต่อไป คุณอาจพิจารณา:

  • รองรับเพย์โหลดแบบหลายเฟรม
  • รองรับเพย์โหลดไบนารี
  • รองรับปิง / ปิงปอง
  • รองรับน้ำหนักบรรทุกนาน
  • ปิดการจับมือ