很早以前就對 WebSocket 協議很是感興趣,今天有空時看了一下 RFC6455, 發現實際上是一個很簡單的協議。因而嘗試着實現了一個客戶端。這裏摘取一些關鍵部分的代碼。web
WebSocket 和普通的 tcp 鏈接很相似,能夠雙向發送消息(區別於 http的request-response模式)。 服務器
首先第一步是創建 tcp 鏈接,而後發送 http 協議升級消息:websocket
defp upgrade_msg(uri, nonce) do """ GET / HTTP/1.1\r Host: #{uri.authority}\r Upgrade: websocket\r Connection: Upgrade\r Sec-WebSocket-Key: #{nonce}\r Sec-WebSocket-Version: 13\r\n """ end
以後服務器會返回一些內容,咱們校驗事後websocket鏈接就算創建成功了:socket
defp handle_tcp(:handshake, data, %{challenge: challenge}) do {:ok, {:http_response, _, 101, "Switching Protocols"}, rest} = :erlang.decode_packet(:http_bin, data, []) |> IO.inspect() case validate_headers(rest, fn {:Connection, up} -> String.downcase(up) == "upgrade" {:Upgrade, ws} -> String.downcase(ws) == "websocket" {"Sec-WebSocket-Accept", ch} -> ch == challenge _ -> true end) do :ok -> IO.inspect("goto data_framing") {:data_framing, ""} {:error, wrong_header} -> IO.inspect("falied because: #{inspect(wrong_header)}") {:failed, :close} end end
鏈接創建以後就能夠以特定的格式收發消息了,消息的最小單位是 frame,它的結構是這樣的:tcp
def encode(%{opcode: op, mask: mask} = meta) do op = enop(op) payload = if mask, do: meta.masked_payload, else: meta.payload mask_key = if mask, do: meta.mask_key, else: <<>> mask = if mask, do: 1, else: 0 <<1::size(1), 0::size(3), op::size(4), mask::size(1), encode_payload_length(byte_size(payload))::bitstring, mask_key::bytes, payload::bytes>> end
客戶端發送給服務器的內容須要 mask,而服務端發給客戶端的則不須要。rest
opcode 經常使用的有 text,binary,ping,pong,close. 分別表明不一樣的消息類型。code