본문으로 바로가기

 

⚡ 웹 소켓이 뭐야

 

Socket

 

  웹 소켓은 HTML5에서 새로 추가된 스펙으로, 실시간 양방향 데이터 전송을 말한다. 전기 코드를 꽂으면 빼기 전까지 계속 전기가 통하는 것처럼 데이터 전송이 이뤄진다. 보통 웹 서버가 HTTP 프로토콜을 이용해 통신하였다면 웹 소켓은 WS 프로토콜을 사용한다.

  HTTP 프로토콜이 stateless한 통신이다. 한번 req를 날리고 서버로 부터 res를 받으면 끝이다. 더 이상의 통신은 없다. WS는 이와 다르게 stateful한 통신이다. 한 번 연결이 되면 계속 그 관계가 유지된다.  

 

웹 소켓 이전의 비슷한 기술로, polling, long polling, streaming이 존재하였고, 지금도 사용되고 있지만 우선 http 프로토콜로 통신하기에 req, res 모두 header가 너무 크다는 공통적인 단점이 존재한다.

 

  Node 환경에서는 Socket.IO 모듈이 WS를 더 사용하기 쉽게 만들어준다. 마치 바닐라 node로 구현한 웹 서버를 이용하지 않고 express를 이용하는 것처럼.

socket.io,  한편, HTTP에 보안을 추가한 HTTPS 프로토콜이 존재하듯이 WS에도 WSS 프로토콜이 존재한다.

 

  WS와 비슷한 개념으로 Server Sent Events(SSE)가 존재한다. 서버에서 클라이언트 방향으로 데이터를 보내는 단방향 통신이다. 그렇다고 SSE가 WS에 의해 대체된 것은 아니다. SSE와 Web Socket은 경우에 따라 무엇이 적절한 지 취사 선택해야 할 문제다.

 

  여기서는 Socket.IO를 이용하지 않고 ws 모듈을 이용해 웹 소켓을 알아본 후 Socket.IO를 이용해보도록 하겠다. websocket과 socket.io 둘 다 활성화가 되어 있으며 원하는 것을 선택하면 된다. 초심자가 시자하기에는 Socket.IO가 편하다고 하다. 연습을 좀 하다가 websocket로 넘어가도 될 듯 싶다.

 

혹시나 websocket을 지원 안하는 브라우저가 있다면

 

거 아직도 websocket 지원 안하는 브라우저가 있겠냐 싶겠지만 MDN에선 아직도 '실험적'이라는 태그를 달고 있습니다.

developer.mozilla.org/ko/docs/Web/API/WebSocket

 

WebSocket - Web API | MDN

WebSocket WebSocket 객체는 서버와의 WebSocket 연결을 생성하고 관리할 수 있는 API 들을 제공합니다. 이는 데이터를 전송하거나 주고 받는 등의 API 들을 포함합니다. WebSocket 생성자는 하나의 필수

developer.mozilla.org

간단하게 아래처럼 체크해주면 되겠습니다. 없으면 못하는 거죠. 뭐

if ('WebSocket' in window) {  
    // do ya thing
}

 

 

websocket 동작 방법


1. 핸드 쉐이킹. 
우선 http(s)로 우선 통신한다. (ws 프로토콜이 아님) 반드시 Get 메서드로 요청해야 함.
헤더를 살펴보면 Upgrade: websocket 이 존재하는 것 알 수 있는데, Upgrade는 다른 프토토콜로 업그레이드하기 위한 규칙이다.
Upgrade라는 헤더 필드가 명시되었을 경우 송신자는 반드시 Upgrade 옵션을 지정한 Connection 헤더 필드도 전송해야 한다.
Sec-WebgSocket-Key도 전송되는데, 소켓 통신을 하기 위한 클라이언트/서버의 신원 인증 도구이다.

서버측은 101 switching protocols로 응답하여 핸드 쉐이킹을 완료한다.

2. 
ws(80), wss(443) 프로토콜로 웹 소켓 통신을 시작한다. 즉, http 통신과 동일한 포트를 공유한다.
message : 여러 frame이 모여서 구성하는 하나의 논리적 메세지 단위
frame : communication에서 가장 작은 단위의 데이터, 작은 헤더 + payload로 구성됨. (http 헤더가 너무 큰 것에 대한 해결)
프레임의 헤더 구조...는 생략하자..

ws 통신에 사용되는 데이터는 utf8 인코딩이다.

 

 

⚡ ws 모듈로 핑퐁 서버 구현

 

npm i ws

 

./app.js (서버측 코드)

 

webSocket 함수는 ./socket/index.js에서 export한 함수입니다.

서버 자체를 웹소켓 메서드에 통째로 넣으면 됩니다.

const webSocket = require("./socket");

// listen
const server = app.listen(process.env.PORT, () => {
  return console.log(`success on : http://localhost:${process.env.PORT}`);
});

// ws는 http와 동일 포트를 사용함. 웹 서버를 그대로 받아 사용하면 됨
webSocket(server);

 

./socket/index.js (서버측 코드)

 

  연결이 되면 connection 이벤트가 발생합니다. 연결된 후 다음 이벤트가 실행되면 적절한 행동을 하게끔 코딩합니다. 현재는 비워두었습니다.

  * req 객체를 통해 접속한 클라이언트의 정보를 얻을 수 있습니다.

  * 인터벌을 준다면 ws 연결 종료시 clearinterval을 해야 메모리 누수가 없습니다.

  * ws.readyState에는 ws.CONNECTING, ws.OPEN, ws.CLOSING, ws.CLOSE이 존재합니다.

const WebSocket = require("ws");

module.exports = (server) => {
  const wss = new WebSocket.Server({ server });

  // 이벤트가 발생하면 지정한 콜백을 실행합니다.
  // res는 없습니다. 계속 통신하기 때문입니다.
  // req 객체를 통해 접속자의 정보를 얻을 수 있습니다.

  wss.on("connection", (ws, req) => {
    // x-forwarded-for는 프록시를 거치기 전의 ip
    // req.connection.remoteAddress는 최종 ip입니다.
    // 따라서 x-forwarded-for를 먼저 사용하되 없으면 req.connection.remoteAddress를 받아옵시다
    const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
    console.log(`${ip} 클라이언트 접속 완료`);

    ws.on("message", (message) => {
      console.log(message);
    });
    ws.on("error", (error) => {
      console.log(error);
    });
    ws.on("close", () => {
      console.log(`${ip} 클라이언트 접속 해제`);
      clearInterval(ws.interval);
    });

    // 3초마다 서버에서 클라이언트로 메세지를 보냅니다
    // ws 종료시 clearInterval을 해줘야 메모리 누수가 없습니다.
    const interval = setInterval(() => {
      // ws 상태가 수립된 후 (ws.OPEN) 메세지를 보내야 오류가 없습니다.
      // ws.CONNECTING(연결 중), ws.CLOSING(연결), ws.CLOSING(종료 중), ws.OPEN(종료)
      if (ws.readyState === ws.OPEN) {
        ws.send("서버에서 클라이언트로 메세지를 보냅니다!");
      }
    }, 3000);
    ws.interval = interval;
  });
};

 

./views/index.pug (클라이언트 코드)

 

클라이언트 측에서는 별다른 패키지 설치 없이, Web API로 제공되는 Websocket을 이용합니다.

https://developer.mozilla.org/en-US/docs/Web/API/WebSocket

 

extends layout

block content
  div F12를 눌러 console 탭과 network 탭을 확인하세요.
  script.
    const ws = new WebSocket('ws://localhost:3051')
    // 백단에서 연결이 되면 'connection' 이벤트
    // 프론트단에서는 onopen 이벤트가 발생합니다
    ws.onopen = () => console.log("서버와 웹소켓 연결 성공")
    // 서버로부터 메세지가 오면 onmessage 이벤트가 발생합니다.
    ws.onmessage = (event) => {
      // 서버에서 온 메세지는 event 객체에 담겨 있습니다.
      console.log(event.data)
      // 클라이언트도 서버 쪽으로 메세지를 보낼 수 있습니다
      ws.send("클라이언트도 서버 쪽으로 메세지를 보낼 수 있습니다")
    }
    

 

  클라이언트 쪽에서 서버에 메세지를 보낼 수 있고, 서버도 클라이언트 쪽으로 메세지를 보낼 수 있는 양방향 데이터 전송을 구현할 수 있습니다.

  서버가 보낸 메세지는 onmessage 함수의 event 객체에 들어있으며 클라이언트가 보낸 메세지는 message 이벤트의 콜백에서 부를 수 있습니다. 보내는 방식은 동일하게 ws.send()를 통해 보낼 수 있습니다.

 

 

 

  크롬의 개발자 도구에서 Network를 통해 message를 확인할 수도 있습니다.

아래로 향하는 화살표가 서버에서 클라이언트로 보내는 메세지, 

위로 향하는 화살표가 클라이언트에서 서버로 보내는 메세지

 

 

 

darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체