RubyでEchoサーバー - @ledsun blog の経験を踏まえまして、改めて RubyでシンプルなWebSocketサーバーをゼロからつくってみたに取りくみます。 TCPサーバーの部分の理解が進みます。 HTTPで使ったTCPコネクションをそのままつかって、送受信データをWebSocketフレームに変えたことがわかります。 よしこれならできそう!
require 'socket' require 'digest/sha1' server = TCPServer.new 'localhost', 2345 loop do # ここら辺はecho_server.rbと同じ。純粋なTCP通信 socket = server.accept # HTTPリクエストを読み込む http_request = "" while (line = socket.gets) && (line != "\r\n") http_request += line end puts http_request # WebSocketリクエストかどうかを判定する unless match = http_request.match(/^Sec-WebSocket-Key: (\S+)/) puts "Not a websocket request" socket.close next end ws_key = match[1] puts "ws_key: #{ws_key}" response_key = Digest::SHA1.base64digest(ws_key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11') puts "response_key: #{response_key}" handshake_response = <<~EOS HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: #{response_key} \r\n EOS # ソケットそのものはHTTP通信で使われているものと同じ socket.write handshake_response puts 'Handshake response sent' # ここからはWebSocket通信 first_byte = socket.getbyte fin = first_byte & 0b10000000 opcode = first_byte & 0b00001111 raise 'fin bit is not set' unless fin raise 'opcode is not a text' unless opcode == 0x1 second_byte = socket.getbyte is_masked = second_byte & 0b10000000 payload_size = second_byte & 0b01111111 raise 'mask bit is not set' unless is_masked raise 'payload size > 125 is not supported' unless payload_size <= 125 puts "Payload size: #{payload_size}" mask = 4.times.map { socket.getbyte } puts "Mask: #{mask}" data = payload_size.times.map.with_index { socket.getbyte ^ mask[_2 % 4] } puts "Data: #{data.pack('C*')}" # クライアントにデータを返す response_message = "Loud and clear!" response = [0b10000001, response_message.size, response_message ].pack("CCA#{response_message.size}") puts "Response: #{response.unpack('C*')}" socket.write response socket.close end
というわけで
動きませんでした。 なんで・・・。
コネクション周りはわかりました。 ハンドシェイクの必要性がよくわかりません。 この辺は RFC 6455 - The WebSocket Protocol を読むと良さそうです。
20241103 追記
動かない原因はハンドシェイクのレスポンスでした。
handshake_response = <<~EOS HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: #{response_key} \r\n EOS
最後に\r\n
というゴミがついています。
これを消せば動きます。
ゴミがあったためクライアントはハンドシェイクレスポンスの終わりを上手く検知できていなかったようです。