WebSocket 기본

Gon Kim·2022년 11월 13일
0

1. 배경

배경부터 보자. CS 공부를 할때마다 느끼는건데, 내 전공인 기계공학과 많이 다르다. 기계공학의 학사 수준에서는 거시적인 수준에서의 진리가 존재하고, 그 진리에 가장 가까운, 이를 가장 잘 표현하는 수식을 찾아 이를 응용하는 것을 주로 다룬다. 하지만 CS의 경우 학사 수준에서부터 필요에 의해 만들어지고 체계화된 지식들을 배우는 것 같다. 내 생각일 뿐이긴 하다. 💬

어쨋든 ㅎㅎ 배경을 봐보자!

http

어찌됐든 web socket은 web에서 특정한 통신을 하기 위한 것이니, web에서 통신이 어떻게 이루어지는지부터 보도록 하자. 웹에서는 http를 기반으로 통신이 이루어지며, http는 다음과 같은 특징이 있다.

  • http는 stateless이다. 현재의 요청/응답은 이전까지 발생했던 모든 요청/응답과 무관하다.
  • 클라이언트가 요청을 보내면 서버는 이에 응답하는 방식으로 소통하도록 설계된 프로토콜이다.
  • 클라이언트가 action을 명시한다.(method)
  • method과 모든 기타 정보들은 header를 통해 보내진다.

요청과 응답으로 데이터 교환이 이루어지는 http는 기본적으로 html 문서를 주고 받는 용도로 쓰였다는 것은 이제는 내 2살짜리 조카도 아는 사실일 것이다. web에서 그냥 html 문서를 뿌리는 것 이상의 무언가를 할 필요가 있었다. 다양한 포맷의 데이터를 주고 받으면서도, 페이지를 리프레쉬 하지 않고, 뒷단에서 무언가 처리할 필요가 있었다. ajax가 등장한다.

ajax

첫 요청 이후에도 비동기적으로 요청을 보내고 응답을 받을 수 있도록 하는 것이다. 자세한건 mdn 문서 참고하자.

요청을 비동기적으로 보내며, 응답이 오더라도 브라우저는 화면 전환을 하지 않는다. 응답으로 온 데이터를 뒷단에서 가지고 놀 수 있다.

이런 멋진걸 할 수 있다. ajax를 잘 모른다면, 한번 실행시켜 보자. 일반적으로 브라우저에서 특정 url에 요청을 보내면, 응답이 오고 화면이 전환되지만, 이 친구는 화면은 그대로지만 무언가 요청 응답이 일어나고, 데이터가 화면에 추가되는 것을 볼 수 있다. server를 돌리려면 http 패키지를 깔아야한다.

//server.js
const http = require("http");

const randomStrings = ["random", "string", "hey", "oh"];

const server = http.createServer((req, res) => {
  req
    .on("data", (chunk) => {
      console.log(chunk);
    })
    .on("end", () => {
      res.writeHead(200, defaultHeader);
      const randomNum = Math.floor(Math.random() * randomStrings.length);
      res.end(randomStrings[randomNum]);
    });
});

server.listen(3000, () => {
  console.log(`http server listen on 3000`);
});

const defaultHeader = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
  "Access-Control-Allow-Headers": "Content-Type, Accept",
  "Access-Control-Max-Age": 10,
  "Content-type": "application/json",
};
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <button>get random number</button>
    <ul></ul>
  </body>
  <script src="index.js"></script>
</html>
// index.js
const button = document.querySelector("button");
const list = document.querySelector("ul");
const onClickButton = (e) => {
  fetch("http://localhost:3000", {
    method: "GET",
    headers: { "Content-Type": "application/json" },
  })
    .then((res) => res.text())
    .then((text) => {
      const li = document.createElement("li");
      li.innerText = text;
      list.appendChild(li);
    });
};
button.addEventListener("click", onClickButton);

아주 요긴하다. 하지만 여러 이유로, 이것만으로도 만족이 안되는 상황이 왔다.

ajax는 여전히 요청을 보내야만 응답이 오는 방식이다.

뭔가 느낌이 이상하지 않나? 게임이나, 메신저를 생각해보면 굳이 내가 뭘하고 있지 않아도 응답이 오는 것 같은데, 어떻게 된 것일까?

서버가 보낼 데이터가 생기면, 바로 보내주도록 할 필요가 있었고, web socket이 등장했다. 실시간성을 보장해주는 친구이다.

물론 ajax로도 실시간성을 따라할 방법이 있지만, 단점이 존재한다. 추후에 알아본다.

Web socket!

오늘의 주인공이다. 양방향 통신이 가능하고, 포트와 ip로 정의된다는 점에서 socket이라는 이름을 따온 것이지 싶다. socket도 매우 매우 잘 아는건 아니라서 곧 따로 정리하려한다.

어쨋든, web socket은 실시간으로 데이터를 주고 받고 싶은 상황에서 http보다 더 나은 선택이 될 수 있는 프로토콜이다. http를 대체하는, 그런 개념이 아니다. 목적서이 다른 느낌

다음과 같은 특징이 있다.

  • full duplex , bi directional 커뮤니케이션 채널이다.
    • 양방향이라는 뜻이다. 요청과 응답을 구분하지 않는다.
  • http의 upgrade 버전!
    • http와 같은 tcp 커넥션을 이용하지만, ws, wss 프로토콜을 사용한다
    • 클라이언트가 web socket 사용과 관련된 헤더를 요청에 포함시켜 서버에 보내면, 프로토콜은 web socket으로 업그레이드 되고, 같은 tcp 커넥션 위에서 데이터를 계속 보내고 받을 수 있게 된다. tcp의 경우 미리 커넥션을 생성해야하지 않는가? 만들어놓은 것 위에서 데이터를 계속 보내고 받을 수 있게 된다는 것이다.
    • 양방향 통신을 위해 고안되었기에, http를 썼다면 발생할 overhead가 사라진다.
  • 사용하기 쉽고, standardize되어있다. html5 웹 표준 기술이다.

2. 그럼 이전에는?

polling

  • ajax를 몇초 주기로 보내게 하기

long polling

  • 서버에 request 보냄. 이때 response를 보낼때까지 connection을 닫지 않게 하기.
  • 그렇게 res가 오면 또 같은 request를 보내기 반복
  • 장단점이 있음

server sent event

  • EventSource API의 활용
  • 양방향(bi directional)이라고 하기에 애매함
  • 얘는 client는 가끔 연결되어있고, 주로 서버가 그냥 일방적으로 쏴주는 느낌이 더 강함
  • event loop이나 asynchronous 서버가 필요하다는 단점이 있다.
  • binary message는 주고 받을 수 없다.
  • 이미 존재하는 기술들과 공존 가능
  • rest api와 Oauth와 잘 작동하다.
  • web socket은 얘들과 뭐 잘 안된다.
  • 따라서 trade off이다.
  • 브라우저 서포트가 web socket보다 안된다. 아이러니하게

3. 과정

기본적으로 처음 handshake가 이루어진 후, 쌍방향 통신이 가능해진다. 자세한 과정은 다루지 않고, handshake 과정에서 클라이언트의 요청, 서버의 응답 정도를 확인해보자.

클라이언트 측 요청이다.

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • GET 방식으로, HTTP/1.1 이상으로 요청해야한다
  • 이 외에 User-Agent, Referer, Cookie와 같은 헤더도 보낼 수 있다.

서버 측 응답이다.

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를 concat 한다.(매직 스트링이라고 하네?)
    • SHA-1로 해싱하고 base64로 인코딩한다.
  • 서버가 해당 응답을 성공적으로 보내면, 데이터 프레임 교환이 가능해진다!

4. Socket.io

기본적으로 통신이다보니, 통신이 끊겼을 때, 혹은 해당 브라우저가 websocket을 지원하지 않을 때 등등 예외적인 상황에 대처해야한다. 이러한 것들을 쉽게 할 수 있도록 해준 라이브러리가 socket.io이다.

공식문서를 보면 정말 잘 나와있다. 다시 읽기 귀찮으니 대략적으로 정리해봤다.

Socket.io

기본 설명

  • low-latency, bidirectional, event-based communication 지원
  • Websocket 프로토콜을 기반
  • http long-polling으로의 fallback, automatic reconnection등을 지원
  • ws 라이브러리를 사용할 때는 받은 데이터에서 type, content 등을 확인해 하나 하나 이에 대응하는 로직을 따로 짜야한다. 하지만 socket.io는 이벤트 기반으로 동작하도록 추상화 해두었다.
  • 편리한 기능을 제공하기 위해 packet에 metadata를 추가했다. plain websocket 서버/클라이언트는 socket.io 서버/클라이언트와 호환되지 않는다.
  • TCP connection을 열어두기 때문에 모바일 환경에서 적합하지 않다. 배터리 많이 빨아먹는다고. 이럴때는 fcm 쓰라고 한다.

WebSocket 대비 추가 기능

http long-polling fallback

  • websocket connection이 되지 않을 때 http long-polling 방식으로 fallback
  • 요즘은 97%이상의 브라우저가 지원한다고 하는데 아직misconfigured proxy에 의해 connection을 만들지 못한다는 이슈가 가끔 들어온다고

Automatic reconnection

  • 서버/클라이언트 사이 connection이 불안정하거나 끊길 수 있다.
  • 그래서 특정 주기로 connection status확인한다고 한다
  • 클라이언트가 연결을 끊으면, exponential back-off delay로 reconnect한다고 한다. 뭐 서버 안정을 위해 그러는거 같다.

Packet buffering

  • 클라이언트 연결이 끊겨도 packet이 buffer에 쌓여 보관된다! 이 후 다시 연결됐을 때 보내진다고 한다.
  • 유용하긴 하지만 connection이 다시 성립됐을 때 event가 몰아서 터질 수도 있다. 해결 방안은 다음을 참고

Broadcasting

  • 여러 클라이언트들에게 동시에 브로드캐스팅 가능!!

Mutliplexing

  • namespace라는걸 제공한다. 하나의 connection에서 로직을 쪼갤 수 있게 해준다.
  • connection 생성을 listen하는 로직 앞에 붙여주면 된다. 다음을 참고

Client usage with bunlders

  • 번들링할 때 추가해줘야하는 패키지들 설명. 다음을 참고

How it works

https://socket.io/docs/v4/how-it-works/

진짜 친절하다 이런거도 써주고

Cors 설정

https://socket.io/docs/v3/handling-cors/

5. 기타

브라우저

지원 안되는 브라우저가 있을 수 있다.

근데 내가 지금 확인할 부분은 아니다.

intended use

websocket은 http의 대체제인게 아니다.

  • ws은 upgrade이다.
  • http의 장점이 많다.
    • automatic caching
    • rest, oauth와 잘 작동
    • 서버 load balacning하는게 websocket으로는 힘들다.
  • low latency, real-time일 때 좋다는 것이다.

websocket clients

  • 웹 브라우저일 필요가 없다.
  • socketio 쓰면 좋다.
    • websocket을 쉽게 쓸 수 있도록 하는 기능을 제공하는 라이브러리
    • auto reconnection, fallback을 제공한다.
    • 서버랑 클라이언트 handshake가 제대로 안되면 fallback을 쓴다. long polling으로 전환하는 것
    • 오래된 브라우저도 고려한다면 좋은 선택이다.
    • disconnection, connection도 알아서 해준다.
    • namespace 정의 기능도 있다. 클라이언트의 그룹을 만드는 기능
    • native websocket과 달리 custom event도 가능

TCP socket vs Web socket

동기화 소켓 vs 비동기화 소켓, 사용시 각각의 장단점

Differences between TCP sockets and web sockets, one more time

굉장히 흥미로운 글을 발견했다. 물론 이게 100% 맞는 것인지는 모르겠다. 이런게 있다는 것을 인지해두자

참고

https://www.youtube.com/watch?v=8ARodQ4Wlf4

https://www.youtube.com/watch?v=MPQHvwPxDUw

stomp

  • 웹소켓은 데이터만 전달할 뿐 해석은 애플리케이션에 맞김
  • 그래서 sub protocol을 씀
  • 얘는 채팅 통신을 위한 형식을 정의함

https://velog.io/@rhdmstj17/소켓과-웹소켓-한-번에-정리-2

내가 쓰려던 말이 여기 거의 다 있더라

https://www.peterkimzz.com/websocket-vs-socket-io/

websocket과 socket.io의 차이

https://ko.wikipedia.org/wiki/웹소켓

위키

https://dalkomit.tistory.com/57

socket.io와 ajax중 어떤걸 사용해야할까

웹소켓 서버 작성하기 - Web API | MDN

profile
응애

0개의 댓글