본문 바로가기

Node.js

Node.js(24) - HTTP & WebSocket 정리, WebSocket 사용 방법(ws, socket.io)

728x90
반응형

1) Node.js

   1-1) WebSocket, HTTP

   1-2) 사용 방법

      1-2-1) ws

      1-2-2) socket.io

 

 

 

 

 

 

 

 

1) Node.js

1-1) WebSocket, HTTP

WebSocket은 HTTP(기존의 웹 페이지를 구상하는 방법)를 보완하고자, 즉 다양한 기능을 만들고자 등장함!

 

HTTP의 단점

  • 연결되어 있지 않음(한 번의 요청에 대해 한 번의 응답만 줄 수 있는 구조라는 의미이다!)
  • 양방향 통신이 불가능함

WebSocket은 HTTP와 달리 기본적으로 양방향 통신이 가능하며, 연결되어 있다!

WebSocket은 채팅, 실시간 예약 프로그램, 실시간 주식 차트 등과 같이

실시간으로 데이터가 왔다갔다 하는 것을 구현할 수 있다!

 

TCP 통신의 연결은 논리적인 연결이다!
이와 마찬가지로 WebSocket 또한 논리적 연결이 필요하다!

 

Client가 Server 측에 HTTP 요청을 보내면 Server 측에서 Client 측에 응답을 준다!
-->  여기서 HTTP 요청을 보낸다는 것은 HTTP를 사용한다는 뜻이며, 이는 OSI 7계층 상 7계층에 해당한다!

 

네트워크
직역하면 관계 혹은 연결이라는 의미로 네트워크는 컴퓨터가 최소 2대 필요하다!
우리 입장에선 컴퓨터가 한 대이기에 프로세스가 2개 필요하다!

TCP 코드의 특징은 "연결"이라는 개념이 있는데 이때 연결은 물리적인 연결이 아니라

논리적인 연결(TCP는 서로 데이터만 주고 받는데 이를 연결이라고 한다!!)이다!
TCP의 논리적인 연결 방식이 "3-way Handshaking"이다!

 


WebSocket 또한 일종의 프로토콜이다!

  • http://  HTTP라는 의미를 나타내는 프로토콜
  • ws://  WebSocket이라는 의미를 나타내는 프로토콜

 

 

 

 

1-2) 사용 방법

1-2-1) ws

프로토콜 형태
request mesaage
GET /HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-key: asdjkl...

 

 

response message
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Accept: qwertyy...

 


Node.js - express
Socket.io : Javascript로 만든 라이브러리
ws : Node.js의 외장 라이브러리(npm install을 통해 설치한 뒤 사용 가능함)

 

express 서버와 WebSocket 서버는 각각 따로 만들 수 있다!


"port"는 프로세스의 식별자이기도 하다!

 

Web Socket과 HTTP는 동일한 port를 같이 쓸 수 있다!
--> http://localhost:3000
--> ws://localhost:3000

 

 

back 파일
- app.js
- socket.js
  많이 쓰는 event : connection, message, error, close
- server.js

$ npm install cors mysql2 sequelize express ws

 

app.listen(Typescript 형태)
(method) Application<Record<string, any>>.listen(port: number, callback?: (() => void) | undefined):

Server<typeof IncomingMessage, typeof ServerResponse> (+5 overloads)

 


client(front)

$ npm install express nunjucks



GET /
index.html

broadcast: 본인을 제외한 모두에게 데이터를 던져주는 것을 의미함

 

 

 

back

server.js

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

const http = app.listen(3000, () => {
  console.log("server start");
});

webSocket(http);

 

 

app.js

const express = require("express");
const cors = require("cors");
const app = express();

app.use(cors());
app.use(express.json());

app.get("/", (req, res, next) => {
  res.send("hello~~~");
});

module.exports = app;

 

 

socket.js

const WebSocket = require("ws");

module.exports = (http) => {
  // console.log(http);
  let sockets = [];
  const server = new WebSocket.Server({
    server: http, // websocket 서버에 연결할 HTTP 지정
    // port: 8545, // websocket에 사용할 port 지정
  });

  // "connection" event가 발동되었을 때 해당 callback 함수가 실행됨!
  server.on("connection", (socket, request) => {
    console.log(request.url);
    // console.log(request);
    // console.log(request.headers);

    // const ip = request.connection.remoteAddress
    // 바로 위의 코드와 동일한 코드이다!
    const {
      connection: { remoteAddress: ip },
    } = request;

    console.log(`client 접속 ${ip}`);
    // readyState: 현재 connection이 어떤 상태인지를 숫자(0, 1, 2, 3)로 나타냄
    // 0(connection을 맺는 중에 해당), 1(connection이 완료된 상태), 2(close를 하는 중에 해당), 3(close가 완료된 상태)
    // 여기서 socket.OPEN은 "1"에 해당함!
    // console.log(socket.readyState, socket.OPEN);
    if (socket.readyState === socket.OPEN) {
      sockets.push(socket);
      console.log(sockets.length);
      socket.send(`${ip} 님 환영합니다 - back`);
    }

    // client 측에서 message를 보낼 때마다 해당 callback 함수가 계속 실행되는 코드
    socket.on("message", (msg) => {
      console.log(`${ip} 에서 받은 메시지 ${msg}`);
      // 만일 해당 채팅 내용들(client 측에서 보낸 메시지들)을 DB에 계속 저장하고 싶다면 여기에 DB Insert 문을 추가하면 됨!

      for (const client of sockets) {
        // broadcast: 본인을 제외한 모두에게 실행하는 조건
        if (client === socket) continue;
        client.send(`${msg}`);
      }
    });

    socket.on("close", () => {
      sockets = sockets.filter((v) => v !== socket);
      console.log(`${ip} 님이 접속을 끊음!`);
    });
  });
};

 

 

 

front

server.js

const express = require("express");
const nunjucks = require("nunjucks");
const app = express();

app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
});

app.get("/", (req, res) => {
  res.render("index.html");
});

app.listen(3005, () => {
  console.log("front server start");
});

 

 

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" />
    <title>Document</title>
    <style>
      .right {
        text-align: right;
      }
    </style>
  </head>
  <body>
    <form id="frm">
      <input type="text" name="message" />
      <button type="submit">전송</button>
    </form>

    <ul id="chat"></ul>
    <script type="text/javascript">
      const socket = new WebSocket("ws://localhost:3000");
      const chat = document.querySelector("#chat");

      // server 측으로부터 message를 받으면 해당 callback 함수가 실행되는 코드
      socket.addEventListener("message", async (event) => {
        console.log(event);
        console.log(`서버에서 받은 메시지 : ${event.data}`);

        const li = document.createElement("li");
        // console.log(await event.data.text());
        li.innerHTML = event.data;
        chat.append(li);
      });

      // client 측에서 server 측에 데이터를 보내는 코드
      const frm = document.querySelector("#frm");
      frm.addEventListener("submit", (e) => {
        e.preventDefault();

        const { message } = e.target;
        socket.send(message.value);

        const li = document.createElement("li");
        li.classList.add("right");
        li.innerHTML = message.value;
        chat.append(li);

        e.target.reset();
        message.focus();
      });
    </script>
  </body>
</html>

 

 

 

 

1-2-2) socket.io

"socket.io"는 외장 라이브러리이다!

$ npm install socket.io

 

<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>

 

 

 

back

server.js

const app = require("./app");
const socketIO = require("./ws");

const http = app.listen(3000, () => {
  console.log("server start");
});

socketIO(http, app);

 

 

app.js

const express = require("express");
const cors = require("cors");
const app = express();

app.use(cors());
app.use(express.json());

app.get("/", (req, res, next) => {
  res.send("hello~~~");
});

module.exports = app;

 

 

ws.js

const SocketIO = require("socket.io");

module.exports = (server, app) => {
  const io = SocketIO(server, {
    path: "/socket.io",
  });

  io.on("connection", (socket) => {
    // 이벤트 명을 자유롭게 만들고 사용할 수 있음!
    socket.on("data", (data) => {
      // console.log(`data: `, data);
      const json = { userid: "hsb7722", data };
      socket.broadcast.emit("reply", JSON.stringify(json));
    });

    socket.on("hello", (data) => {
      console.log(data);
    });
  });
};

 

 

 

front

server.js

const express = require("express");
const nunjucks = require("nunjucks");
const app = express();

app.set("view engine", "html");
nunjucks.configure("views", {
  express: app,
});

app.get("/", (req, res) => {
  res.render("index.html");
});

app.get("/io", (req, res) => {
  res.render("io.html");
});

app.listen(3005, () => {
  console.log("front server start");
});

 

 

views/io.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>
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
  </head>
  <body>
    <form id="frm">
      <input type="text" name="message" />
      <button type="submit">전송</button>
    </form>

    <ul id="chat"></ul>
    <script type="text/javascript">
      const socket = io.connect("http://localhost:3000", {
        path: "/socket.io",
        transports: ["websocket"],
      });

      socket.on("reply", (data) => {
        console.log(data);
        const json = JSON.parse(data);
        const li = document.createElement("li");
        li.innerHTML = json.data + " " + json.userid;
        chat.append(li);
      });

      const frm = document.querySelector("#frm");
      frm.addEventListener("submit", (e) => {
        e.preventDefault();

        const { message } = e.target;

        // emit: 첫번째 인자값은 "발동시킬 event 명", 두번째 인자값은 "내용"이다!
        socket.emit("data", message.value);

        const li = document.createElement("li");
        li.innerHTML = message.value;
        chat.append(li);

        e.target.reset();
        message.focus();
      });
    </script>
  </body>
</html>