Atobaum

웹소켓에 대한 소고

개요

웹소켓은 먼저 HTTP로 handshake를 한다.

클라이언트는 먼저 HTTP 요청을 서버로 보내 프로토콜을 websocket으로 바꾸기를 요청한다:

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
 

이때 Sec-WebSocket-Key는 서버와 웹소켓 악수(handshake)를 정상적으로 하기 위한 값이다. 이 값은 클라이언트에서 랜덤으로 만든 16 byte 값을 base64로 인코딩 한 것이다.

서버는 요청을 검증하고 다음 응답을 보낸다.

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
 

서버가 보내는 Sec-WebSocket-Accept는 클라이언트의 Sec-WebSocket-Key에다가 258EAFA5-E914-47DA-95CA-C5AB0DC85B11를 연결(문자열로)하고 SHA-1 해시값을 base64로 인코딩한 값이다.

이 값은 클라이언트가 웹소켓을 지원하지 않는 서버에 연결한 것으로 착각하는 것을 막기 위한 값이다.

요청 예시

  • 요청 예시

Subprotocol

웹소켓은 subprotocol을 가질 수 있다. 클라이언트가 요청 시 Sec-WebSocket-Protocol 헤더이 지정하면(comma-seperated list) 서버는 그에 따라 적절하게 처리할 수 있다.

브라우저에서 사용해보자

연결하기

let ws = new WebSocket("ws://localhost:3000")
let ws = new WebSocket("ws://localhost:3000", "echo") // 프로토콜을 줄 수 있다.
let ws = new WebSocket("ws://localhost:3000", ["echo", "chat"]) // 여러개 줄 수도 있다.

WebSocket은 CONNECTING, OPEN, CLOSING, CLOSED의 네가지 상태를 가지고 있고 값은 WebSocket.<status>에 정의되어있다.

연결이 되면 OPEN 상태로 바뀌고 그때부터 메시지를 전송할 수 있다. ws.readystate로 현재 상태를 알 수 있다.

전송하기

ws.send("message")

보낼 수 있는 데이터는 String, Blob, ArrayBuffer로 JSON을 보내려면 stringify 해서 보내면 된다.

전송 받기

ws.onmessage = evt => {
  console.log(evt.data)
}

연결 끊기

ws.close()

핸들러

WebSocket은 onopen, onclose, onerror, onmessage 핸들러를 가질 수 있다.

Node로 서버 구현

많은 코드가 생략되었습니다.

var http = require("http")
var webSocketServer = require("websocket").server
 
var connections = []
var server = http.createServer()
var wsServer = new webSocketServer({
  httpServer: server,
})
 
wsServer.on("request", req => {
  // chatting 프로토콜을 받는다.
  var connection = req.accept("chatting")
 
  connections.push(connection)
  console.log(new Date() + " Connection accepted. Now " + connections.length)
 
  connection.on("message", message => {
    if (message.type === "utf8")
      // 브로드캐스팅
      connections.forEach(c => c.send(message.utf8Data))
  })
 
  connection.on("close", () => {
    connections = connections.filter(c => c != connection)
    console.log(
      new Date() + " Connection disconnected. Now " + connections.length
    )
  })
})
 
server.listen(3000, () => {
  console.log("Express start to lsten port 3000")
})

Example

Github에 간단한 예제를 올려놓았다.

Reference