WebSocket이 뭐야?!!

hyunho4562·2023년 8월 18일
1
post-thumbnail

파이썬 프레임워크인 장고를 배우고 실시간 채팅 프로젝트를 만드는 중이다.

프로젝트 개발하는 과정에서 Channels에 관한 내용을 정리할려고 한다.


Channels는 언제 사용하는가? 🤔🤔

Channels은 WebSocket과 Redis를 사용하여 통신을 한다.

Channels를 사용하여 WebSocket, 채팅 프로토콜, IoT 프로토콜 등을 처리할 수 있다.


Web Socket

서버와 브라우저 간 연결을 유지한 상태로 데이터를 교환할 수 있다.

WebSocket을 호출하며, 이때 ws 라는 특수 프로토콜을 사용한다.

let socket = new WebSocket("ws://javascript.info");

ws 라는 프로토콜을 사용하지 말고 wss://을 사용하자!

  1. wss://는 보안 이외에도 신뢰성 측면에서 ws 보다 더 신뢰성이 깊다.

  2. ws://를 사용해 데이터를 전송하면 암호화되어 있지 않은 채로 전송된다.

  3. 반면 wss://는 TSL이라는 보안 계층을 통과해 전달되므로 송신자 측에서 데이터가 암호화되고, 복호화는 수신자 측에서 이뤄지게 된다.


소켓 관련 이벤트

  • open - 커넥션이 제대로 만들어졌을 때
  • message - 데이터를 수신하였을 때
  • error - 에러가 생겼을 때
  • close - 커넥션이 종료되었을 때
  • socket.send (data) - 커넥션이 만들어진 상태에서 데이터를 보낼 때

예시 코드 👀👀

let socket = new Socket("wss://javascript.info/article/websocket/demo/hello");

socket.onopen = function(e) {
  alert("[open] 커넥션이 만들어졌습니다.");
  alert("데이터를 서버에 전송해봅시다");
  socket.send("My name is Hyun");
};

socket.onmessage = function(event) {
  alert(`[message] 서버로부터 전송받은 데이터: ${event.data}`);
};

socket.onclose = function(event) {
  if (event.wasClean) {
    alert(`[close] 커넥션이 정상적으로 종료되었습니다(code=${event.code} reason=${event.reason})`);
  } else {
    alert('[close] 커넥션이 죽었습니다.');
  }
};

socket.onerror = function(error) {
 	alert(`[error]`); 
}

웹소캣 핸드셰이크

new WebSocket(url)을 호출해 소켓을 생성하면 즉시 연결이 시작된다.

커넥션이 연결되는 동안, 브라우저는 서버에 '웹 소캣을 지원하나요' 라고 물어본다.

'네'라고 응답을 하면, 서버 - 브라우저 간 통신은 웹 소캣 프로토콜을 사용한다.

new WebSocket("wss://javascript.info/chat") 요청을 전송했을 때

GET /chat
Host: javascript.info
Origin: https://javascript.info
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13

Origin

서버는 Origin 헤더를 보고 어떤 웹사이트와 소켓통신을 할 지 결정하기 때문에 Origin 헤더는 웹 소캣 통신에 매우 중요한 역할이다.


Connection: Upgrade

클라이언트 측에서 프로토콜을 바꾸고 싶다는 신호를 보냈다는 것을 의미


Upgrade: WebSocket

클라이언트 측에서 요청한 프로토콜은 'websocket' 이라는걸 의미


Sec-WebSocket-Key

보안을 위해 브라우저에서 생성한 키,


Sec-WebSocket-Version

웹소캣 프로토콜 버전이 명시된다.


서버는 클라이언트 측에서 보낸 웹소켓 통신 요청을 최초로 받고 이에 동의하면, 상태 코드 101이 담긴 응답을 클라이언트에 전송한다.
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=

여기서 Sec-WebSocket-Accept 값은 특별한 알고리즘을 사용해 만든 Sec-WebSocket-Key 입니다.


Extensions와 Subprotocols 헤더

웹 소캣 통신은 Sec-WebSocket-Extensions, Protocol 헤더를 지원한다.
두 헤더는 웹소켓 프로토콜 기능을 확장할 때와 서브 프로토콜을 사용해 데이터를 전송할 때 사용한다.

각 헤더에 대한 예시를 살펴보자.

  • Sec-WebSocket-Extensions

deflate-frame 이 헤더는 브라우저에서 데이터 압축을 지원한다는 것을 의미


  • Set-WebSocket-Protocol

soap, wamp 이렇게 헤더가 설정되면 평범한 데이터가 아닌 SOAP나 WAMP 프로토콜을 준수하는 데이터를 전송하겠다는 것을 의미


사용법

WebSocket의 두 번째 매개변수에 값을 넣어서 설정할 수 있다.

서브 프로토콜로 SOAP나 WAMP를 사용하고 싶다고 가정해보자.
let socket = new WebSocket("wss://javascript.info/chat", ["soap", "wamp"]);

서버는 지원 가능한 익스텐션과 프로토콜을 응답 헤더에 담아 클라이언트로 전달.

요청 헤더

GET /chat
Host: javascript.info
Upgrade: websocket
Connection: Upgrade
Origin: https://javascript.info
Sec-WebSocket-Key: Iv8io/9s+lYFgZWcXczP8Q==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap, wamp

서버 응답

101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
Sec-WebSocket-Extensions: deflate-frame
Sec-WebSocket-Protocol: soap

이 경우, 우리는 서버에선 'deflate-frame'이라는 익스텐션과 요청 프로토콜 중 SOAP라는 서브 프로토콜만 지원한다.


데이터 전송 ✍️

웹 소켓 통신은 '프레임'이라 불리는 데이터 조각을 사용해 이뤄진다.

프레임은 서버와 클라이언트 양측 모두에서 보낼 수 있다.

프레임 내 담긴 데이터 종류

  • 텍스트 프레임 - 텍스트 데이터가 담긴 프레임
  • 이진 데이터 프레임 - 이진 데이터가 담긴 프레임
  • 핑 퐁 프레임 - 커넥션이 유지되고 있는지 확인할 때 사용하는 프레임
  • 이 외에도 커넥션 종료 프레임 등 다양한 프레임이 존재함.

보통 브라우저 환경에서 개발자는 텍스트나 이진 데이터 프레임을 사용함.


왜?

WebSocket.send() 메서드는 텍스트나 이진 데이터 프레임만 다루게 된다.

socket.send(body) 를 호출할 때, body엔 문자열이나 Blob, ArrayBuffer 등의 이진 데이터만 들어감.

한편, 데이터를 받을 때 텍스트 데이터는 항상 문자열 형태로 온다.

이진 데이터를 받을 때엔 Blob이나 ArrayBuffer 포맷 둘 중 하나를 고를 수 있다.


socket.binaryType 프로퍼티를 사용하면, 기본값은 "blob"이다.

socket.binaryType = "arraybuffer";
socket.onmessage = (event) => {
 	// event.data는 (텍스트: 문자열) (이진 데이터: arraybuffer) 
}

전송 제한

데이터 전송량이 상당한 앱을 개발하고 있다고 가정해보자.
그런데 우리 앱의 사용자는 네트워트가 속도가 느린 곳에서 앱을 사용해본다고 가정하자.


앱 쪽에서 socket.send(data)를 계속해서 호출할 순 있다.
이렇게 하면 데이터가 메모리에 계속 쌓일 것이고, 네트워크 속도가 현저히 느려서 송신이 안될 것이다.


socket.bufferedAmount 프로퍼티는 송신 대기 중인 현재 시점에서 얼마나 많은 바이트가 메모리에 쌓여 있는지 정보를 담고 있다.


따라서, socket.bufferAmount 프로퍼티 값을 확인하면 소켓을 전송에 사용할 수 있는지 아닌지를 판단할 수 있다.

// 100ms마다 소켓을 확인해 쌓여있는 바이트가 없는 경우에만
// 데이터를 추가 전송한다.
setInterval(() => {
  if (socket.bufferedAmount == 0) {
  	socket.send(moreData()); 
  }
}, 100);

커넥션 닫기

연결 주체 중 한쪽에서 커넥션 닫기를 원하는 경우엔 보통 숫자로 된 코드와 문자로 된 사유가 담긴 '커넥션 종료 프레임'을 전송하게 된다.


socket.close([code], [reason]);
  • code - 커넥션을 닫을 때 사용하는 코드
  • reason - 커넥션 닫기 사유를 설명하는 문자열

그럼 다른 한쪽에 구현된 close 이벤트 핸들러에선 다음과 같이 코드와 사유를 확인

// 닫기 주체
socket.close(1000, "Project Working");

// 다른 주체
socket.onclose = event => {
	// event.code === 1000
  	// event.reason = "작업 완료"
};

가장 많이 사용하는 코드는 다음과 같다.

  • 1000 - 기본 값으로 정상 종료를 의미함.
  • 1006 - 커넥션이 유실되었음을 의미함.

커넥션 상태

커넥션 상태를 알고 싶다면, socket.readyState 프로퍼티의 값을 확인하면 된다.

  • 0 - "CONNECTING": 연결 중
  • 1 - "OPEN": 연결이 성립되고 통신 중
  • 2 - "CLOSING": 커넥션 종료 중
  • 3 - "CLOSED": 커넥션 종료됨

Channels를 알기 위해서 WebSocket에 대해 원리와 개념을 정리해 봤다.
다음은 Channels에 대해 정리를 해보겠다.

profile
유연한 자세

1개의 댓글

comment-user-thumbnail
2023년 8월 18일

잘 읽었습니다. 좋은 정보 감사드립니다.

답글 달기