[elixir! #0066] 打工人的摸鱼之做 —— websocket client 最小实现

很早以前就对 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

相关文章
相关标签/搜索