웹소켓
웹 소켓
웹 소켓을 사용하면 HTTP의 새로운 요청이나 중단 없이 데이터를 주고 받을 수 있다. 커넥션을 종료 시키지 않은 체 패킷 형태로 이루어지며 양방향 통신이 가능하다. 실시간으로 데이터를 교환하는 시스템이나 주식 트레이딩 시스템에 적합하다.
간단한 예시
1 | let socket = new WebSocket("ws://javascript.info"); |
ws
말고 wss://
라는 프로토콜도 사용하는데 이는 HTTP
와 HTTPS
의 관계와 유사하다.
항상
wss://
를 사용해라.
ws를 사용하면 데이터가 암호화 되지 않고 전달되는데 오래된 프락시 서버에 경우는 웹솟켓을 알지 못해서 이상한 헤더를 붙혀서 전달하는 일이 발생한다. wss를 사용하면 TLS라는 보안 계층을 통해서 데이터가 암호화 되어서 프락시에서 열어 볼 수 없고, 복호화는 받는 쪽에서 연결 하기 때문에 안전하게 연결할 수 있다.
다음 이벤트를 사용할 수 있다.
open
- 커넥션이 제대로 만들어졌을때 발생함.message
- 데이터를 수신하였을때 발생함.error
- 에러가 생겼을때 발생함.close
- 커넥션이 종료되었을때 발생함.
커넥션이 만들어진 상태에서 무엇을 보내고 싶다면 socket.send(data)
를 사용하면 된다.
1 | let socket = new WebSocket( |
이벤트는 open
-> message
-> close
순서로 발생한다.
웹소켓 핸드셰이크
new WebSocket(url)
를 호출해 소켓을 생성하면 즉시 연결이 시작된다.
브라우저는 서버에게 웹소켓을 지원하는지 묻고 지원한다는 응답이 오면 이제 HTTP가 아닌 WebSocket protocol로 통신한다.
요청 헤더 예시 :
1 | GET /chat |
Origin
- 클라이언트 오리진을 나타낸다. 서버는Origin
헤더를 보고 웹사이트와 소켓통신을 할지 결정하기 때문에 Origin 헤더는 웹소켓 통신에 중요한 역할을 한다.Connection: Upgrade
- 클라이언트 측에서 프로토콜을 바꾸고 싶다고 알려줌.Upgrade: websocket
- 클라이언트 측에서 요청한 프로토콜은websocket
이라는 것을 의미한다.Sec-WebSocket-Key
- 보안을 위해 브라우저에서 생성한 키를 나타낸다.Sec-WebSocket-Vertion
- 웹소켓 프로토콜 버전이 명시된다. 예시는 13버전
웹소켓 핸드셰이크는 모방이 불가능하다. (바닐라 자바스크립트로 헤더를 설정하는 건 기본적으로 막혀있다.)
응답 예시 :
1 | 101 Switching Protocols |
Sec-WebSocket-Accept
는 Sec-WebSocket-Key
와 밀접한 관계가 있다. 브라우저는 이 헤더를 보고 특정한 알고리즘을 사용해 자신이 설정한 key인지 확인한다.
Extensions와 Subprotocols 헤더
기능확장과 서브 프로토콜로 데이터를 전달할때 Sec-WebSocket-Extenstions
와 Sec-WebSocket-Protocol
헤더를 지원한다.
Sec-WebSocket-Extensions : deflate-frame
이 헤더는 데이터 압축을 지원한다는 것을 의미함. 이 헤더는 브라우저에 의해 자동 생성되는데, 그 값엔 데잍터 전송과 관련된 무언가나 웹소켓 프로토콜 기능 확장과 관련된 무언가가 여러개 나열된다.Sec-WebSocket-Protocol: soap, wamp
- 이렇게 설정되면 평범한 헤더가 아닌 SOAP, WAMP 프로토콜을 준수하는 데이터를 전송하겠다는 의미이다.
이 헤더는 new WebSocket
에 두번째 매겨변수에 값을 넣어서 설정할 수 있다.
1 | let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]); |
이때 서버는 지원 가능한 익스텐션과 프로토콜을 응답 헤더에 담아 클라이언트에 전달해야 한다.
예시 :
클라이언트 :
1 | GET /chat |
서버 :
1 | 101 Switching Protocols |
데이터 전송
웹소켓 통신은 프레임(frame)이라 불리는 데이터 조각을 사용해 이루어진다. 데이터 종류에 따라 다음과 같이 나뉜다.
- 텍스트 프레임 (text frame) - 텍스트 데이터가 담긴 프레임
- 이진 데이터 프레임 (binary data frame) - 이진 데이터가 담긴 프레임
- 핑 또는 퐁 프레임 (ping/pong frame) - 커넥션이 유지되고 있는지 확인할 때 사용하는 프레임으로 서버나 브라우저에서 자동 생성해서 보내는 프레임
- 이 외에도 커넥션 종료 프레임(connection close frame) 등 다양한 프레임이 있음.
브라우저에서 개발자는 텍스트나 이진 프레임만 다루게 된다. 이유는 socket.send(body) 에서 body 에 올수 있는 데이터가 string 과 blog, arraybuffer 같은 형태이기 때문이다.
받을 때는 텍스트 일경우 문자열로 들어온다. 이진데이터일 경우에는 선택할 수 있다.
이진 데이터를 받을 때는 socket.binaryType
프로퍼티를 사용하여 Blob
이나 ArrayBuffer
포맷 중 하나를 고룰 수 있다.
1 | socket.binaryType = "arraybuffer"; |
Rate limiting (속도 제한)
느린 데이터 환경에서 데이터를 전송하고 있다고 생각해 보자. 사용자는 계속해서 send 하겠지만 데이터는 버퍼링 되어 메모리에 저장되고 충분히 전달하기에 빠른 환경에 있을때 보내지게 될 것이다.
socket.bufferedAmount
프로퍼티는 보내고 있는 시점에서 남아있는 바이트나 버퍼를 저장하고 있다. 이 정보를 활용해서 버퍼에 데이터가 없을때 send 하는 로직을 구성할 수 있다.
1 | // every 100ms examine the socket and send more data |
Connection close
일반적으로 연결을 종료하고 싶을때 숫자 코드와 이유가 포함된 연결 종료 프레임을 보낸다.
1 | socket.close([code], [reason]); |
- code : 정해진 숫자 코드 (optional)
- reason : 이유를 설하는 문자열 (optional)
예시 :
1 | // closing party: |
많이 사용하는 코드는 다음과 같다.
1000
: default, normal closure1006
: 브라우저 구현에 의해 연결이 비정상적으로 (로컬로) 닫혔음을 의미하는 특수 코드
다른 코드는 다음과 같다.
1001
: 서버가 꺼지거나 브라우저에서 페이지를 떠났다.1009
: 메세지가 처리하기에 너무 크다.1011
: 서버에 알수 없는 에러
WebSocket Code 는 HTTP 코드와 비슷한듯 다르다. 특별히 1000 미만의 코드는 미리 예약되어 있으며 설정하려고 하면 에러를 발생시킨다.
1 | // in case connection is broken |
Connection state
socket.readyState
프로퍼티를 사용하여서 연결 상태를 얻을 수 있다.
0
: “CONNECTiNG” : 연결 중 아직 연결된 것은 아님1
: “OPEN”: communicating2
: “CLOSING” : the connection is closing3
: “CLOSED” : the connection is closed.
Chat example
웹소켓을 사용하는 체팅 예제를 살펴보자. 클라이언트에 집중해서 볼 것이지만, 서버도 간단하게 구현 가능하다.
메세지를 담을 form 이 필요하고, 메세지를 표시할 div 가 필요하다.
1 | <!-- message form --> |
자바스크립트를 통해서 해야 할것 :
- 연결
- socket.send(message)
- 수신 메세지 div에 표시
구현 :
1 | let socket = new WebSocket("wss://javascript.info/article/websocket/chat/ws") |