2022.02.14 TIL

웹 소켓(Web Socket)

메시지를 교환하기 위한 통신 방법 중 하나. ws라는 프로토콜을 사용한다.

웹 소켓의 특징 2가지

1. 양방향 통신(Full-Duplex)

  • 양방향 통신이랑 데이터 송,수신을 동시에 처리할 수 있는 통신 방법.

2. 실시간 네트워킹(Real-Time Networking)

  • 웹 환경에서 채팅, 주식, 비디오 데이터 등의 데이터들은 연속된 데이터를 화면에 빠르게 보여주는 등의 실시간 처리가 필요할 경우 사용 가능하다.

웹 소켓 동작 과정

  1. Handshaking - 연결 확립
  2. Socket open - 실시간 데이터 교환
  3. Socket close - 통신 종료

보안 고려 사항

  1. Non browser clients
    웹 브라우저 클라이언트를 사용할 경우, header의 origin 정보를 확인함으로써 악성 javscript 를 걸러낼 수 있다. 이는, 웹 브라우저에서 기존 사용하던 동일 근원 정책을 통해 악성 리소슬부터 보호할 수 있다는 것.
    웹 브라우저가 아닌 환경에서는 동인 근원 정책을 활용할 수 없으므로, 사용에 유의해야 함
  2. Origin Consideration
    웹 브라우저와 마찬가지로 웹 서버에서도 origin을 확인하는 로직이 추가되어야 한다. 알수 없는 출처로부터 요청이 들어온다면, HTTP 403 Forbidden 상태 응답을 줄 것.
  3. WebScoket Client Authentication
    웹 소켓 프로토콜은 handshaking 단계에서 서버가 클라이언트를 인증하는 특별한 방법을 제시하지 않는다. 따라서 클라이언트 인증이 필요한 경우, HTTP 인증이나 TLS 인증 등을 사용해야 한다.

Node 서버 웹 소켓 예제

npm i ws cookie-parser dotenv express express-session morgan nunjucks

// app.js
const express = require("express");
const path = require("path");
const morgan = require("morgan");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const nunjucks = require("nunjucks");
const dotenv = require("dotenv");

dotenv.config();
const webSocket = require("./socket");
const indexRouter = require("./routes");

const app = express();
app.set("port", process.env.PORT || 4000);
app.set("view engine", "html");

nunjucks.configure("view", {
  express: app,
  watch: true,
});

app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
  session({
    resave: false,
    saveUninitialized: false,
    secret: process.env.COOKIE_SECRET,
    cookie: {
      httpOnly: true,
      secure: false,
    },
  })
);

app.use("/", indexRouter);

app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

const server = app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

webSocket(server);
// socket.js
const WebSocket = require("ws");
module.exports = (server) => {
  const wss = new WebSocket.Server({ server });
  // 웹 소켓 연결 시 실행
  wss.on("connection", (ws, req) => {
    // 클라이언트의 IP를 알아내는 방법
    const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
    console.log("새로운 클라이언트 접속", ip);

    // 클라이언트로부터 메시지 수신 시
    ws.on("message", (message) => {
      // 그냥 받으니 버퍼로 옴
      console.log(message.toString());
    });
    // 에러 발생 시
    ws.on("error", (err) => {
      console.log(err);
    });
    //연결 종료 시
    ws.on("close", () => {
      console.log("클라이너트 접속 해제", ip);
      // setInterval 안 지우면 메모리 누수 발생
      clearInterval(ws.interval);
    });
    // 3초마다 클라이언트로 메시지 전송
    ws.interval = setInterval(() => {
      /* 
        웹 소켓에는 4가지 상태가 있다.
        CONNECTION(연결 중), OPEN(열림), CLOSIN(닫는 중), CLOSED(닫힘)
      */
      if (ws.readyState === ws.OPEN)
        ws.send("서버에서 클라이언트로 메시지를 보냅니다.");
    }, 3000);
  });
};
// views/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" />
    <script defer>
      const webSocket = new WebSocket("ws://localhost:4000");
      webSocket.onopen = () => {
        console.log("서버와 웹 소켓 연결 성공");
      };
      webSocket.onmessage = (e) => {
        console.log(e);
        webSocket.send("클라이언트에서 서버로 답장을 보냅니다.");
      };
    </script>
    <title>GIF 채팅방</title>
  </head>
  <body>
    <div class="">F12를 눌러 console 탭과 network 탭을 확인하세요.</div>
  </body>
</html>

client-side Socket.IO 사용시 404

<script src="/socket.io/socket.io.js"></script>
<script>
  const socket = io.connect("http://localhost:4000", {
    path: "/socket.io",
  });
  socket.on("news", (data) => {
    console.log(data);
    socket.emit("reply", "Hello Node.JS");
  });
</script>

테스트 당시 index.html 을 Live Server로 켜 놓고 브라우저 콘솔을 키니 아래와 같dㅣ /socket.io/socket.io.js 파일이 없다는 오류가 떴다.

이거 때문에 1시간을 고민하고 있었는데, Url 포트 번호가 내가 설정한 것과 다르다는 것을 뒤늦게 깨달았다....;;;
브라우저에서 localhost:port 로 접속해야 하는 것을 Live Server로 테스트 하니 이 모양이 나지 멍청아

참고자료

https://caileb.tistory.com/185

좋은 웹페이지 즐겨찾기