프로젝트 개발하는 과정에서 Channels에 관한 내용을 정리할려고 한다.
Channels를 사용하여 WebSocket, 채팅 프로토콜, IoT 프로토콜 등을 처리할 수 있다.
서버와 브라우저 간 연결을 유지한 상태로 데이터를 교환할 수 있다.
WebSocket을 호출하며, 이때 ws 라는 특수 프로토콜을 사용한다.
let socket = new WebSocket("ws://javascript.info");
wss://는 보안 이외에도 신뢰성 측면에서 ws 보다 더 신뢰성이 깊다.
ws://를 사용해 데이터를 전송하면 암호화되어 있지 않은 채로 전송된다.
반면 wss://는 TSL이라는 보안 계층을 통과해 전달되므로 송신자 측에서 데이터가 암호화되고, 복호화는 수신자 측에서 이뤄지게 된다.
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)을 호출해 소켓을 생성하면 즉시 연결이 시작된다.
커넥션이 연결되는 동안, 브라우저는 서버에 '웹 소캣을 지원하나요' 라고 물어본다.
'네'라고 응답을 하면, 서버 - 브라우저 간 통신은 웹 소캣 프로토콜을 사용한다.
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 헤더는 웹 소캣 통신에 매우 중요한 역할이다.
클라이언트 측에서 프로토콜을 바꾸고 싶다는 신호를 보냈다는 것을 의미
클라이언트 측에서 요청한 프로토콜은 'websocket' 이라는걸 의미
보안을 위해 브라우저에서 생성한 키,
웹소캣 프로토콜 버전이 명시된다.
101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: hsBlbuDTkk24srzEOTBUlZAlC2g=
여기서 Sec-WebSocket-Accept 값은 특별한 알고리즘을 사용해 만든 Sec-WebSocket-Key 입니다.
웹 소캣 통신은 Sec-WebSocket-Extensions, Protocol 헤더를 지원한다.
두 헤더는 웹소켓 프로토콜 기능을 확장할 때와 서브 프로토콜을 사용해 데이터를 전송할 때 사용한다.
deflate-frame 이 헤더는 브라우저에서 데이터 압축을 지원한다는 것을 의미
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]);
그럼 다른 한쪽에 구현된 close 이벤트 핸들러에선 다음과 같이 코드와 사유를 확인
// 닫기 주체
socket.close(1000, "Project Working");
// 다른 주체
socket.onclose = event => {
// event.code === 1000
// event.reason = "작업 완료"
};
가장 많이 사용하는 코드는 다음과 같다.
커넥션 상태를 알고 싶다면, socket.readyState 프로퍼티의 값을 확인하면 된다.
Channels를 알기 위해서 WebSocket에 대해 원리와 개념을 정리해 봤다.
다음은 Channels에 대해 정리를 해보겠다.
잘 읽었습니다. 좋은 정보 감사드립니다.