0. 들어가며
앞서 세팅편을 보지 않으셨다면 먼저 보고 오시는 것을 추천합니다.
1. 서버 설정하기
npm i ws
Node.js에서 webSocket의 핵심 패키지인 ws이다.
express는 기본적으로 http를 지원하기 때문에 ws는 지원하지 않는다.
그래서 서버에 ws 기능을 추가로 설치할 예정이다.
import express from "express";
import http from "http";
const app = express();
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (req, res) => res.render("home"));
app.get("/*", (req, res) => res.redirect("/"));
const server = http.createServer(app)
Node.js에 내장되어 있는 http 패키지를 사용해서 서버를 실행할 것이다.
http.createServer(app)
createServer를 사용해서 Express Application으로부터 서버를 만들었다.
...
import WebSocket from "ws";
const app = express();
...
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
http.createServer를 사용해서 http 서버를 만들고 ws 패키지의 WebSocket을 이용해서 WebSocket 서버를 만들었다.
new WebSocket.Server({ server });
Server()의 매개변수로 서버를 넘겨줘서 http 서버와 webSocket 서버 둘 다 실행이 가능해졌다.
만약 http 서버가 필요 없다면 매개변수로 넘겨주지 않아도 상관없다.
import express from "express";
import http from "http";
import WebSocket from "ws";
const app = express();
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (req, res) => res.render("home"));
app.get("/*", (req, res) => res.redirect("/"));
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
server.listen(3000, () => console.log("Listening on http://localhost:3000")); // -- *
최종으로 listion()을 통해서 http 서버와 webSocket 서버 모두 실행하였다.
2. 메시지 주고받기
import express from "express";
import http from "http";
import WebSocket from "ws";
const app = express();
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (req, res) => res.render("home"));
app.get("/*", (req, res) => res.redirect("/"));
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on("connection", (socket) => console.log(socket)); // -- *
server.listen(3000, () => console.log("Listening on http://localhost:3000"));
webSocket은 on 함수를 통해서 여러 가지 이벤트가 발생했을 때 추가적인 작업이 가능하다.
JavaScript에서 element.addEventListener("click", () =>...)와 비슷한 역할이라고 생각하면 된다.
즉, 위 코드는 connection 이벤트가 발생하면 socket을 콘솔 로그에 띄운다는 뜻이다.
// src/public/js/app.js
const socket = new WebSocket(`ws://${window.location.host}`);
FrontEnd는 브라우저가 이미 WebSocket 클라이언트에 대한 패키지를 가지고 있다.
그래서 서버의 webSocket과 연결하고 싶다면 자바스크립트를 통해서 바로 연결이 가능하다.
한 가지 다른 점이 흔히 서버와 통신할 때는 주소가 "http://~~~.~~" 같이 앞에 http가 붙는데, webSocket은
" ws://주소 "를 통해서 webSocket 서버와 연결할 수 있다.
import express from "express";
import http from "http";
import WebSocket from "ws";
const app = express();
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public"));
app.get("/", (req, res) => res.render("home"));
app.get("/*", (req, res) => res.redirect("/"));
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on("connection", (socket) => {
socket.send("hello!!"); // -- *
});
server.listen(3000, () => console.log("Listening on http://localhost:3000"));
전달받은 socket을 이용하면 FrontEnd에게 메시지를 보낼 수 있다.
const socket = new WebSocket(`ws://${window.location.host}`);
socket.addEventListener("open", () => {
console.log("Conneted to Server");
});
socket.addEventListener("message", (message) => {
console.log("message : ", message);
});
socket.addEventListener("close", () => {
console.log("Disconneted to Server");
});
FrontEnd에서는 socket에게 addEventListener를 통해 webSocket 연결 시
필요한 이벤트 ( open : 연결 완료, message : 서버로부터 메시지가 올 경우, close : 연결 종료 ) 때 적절한 작업을
수행할 수 있다.
// ...
wss.on("connection", (socket) => {
// console.log(socket);
socket.send("hello!!");
socket.on("close", () => console.log("DIsconnected from the Browser"));
});
// ...
서버에서도 마찬가지로 " close " 이벤트가 있어서 연결이 종료되면 추가 작업이 가능하다.
// ...
wss.on("connection", (socket) => {
// console.log(socket);
socket.send("hello!!");
socket.on("close", () => console.log("Disconnected from the Browser"));
socket.on("message", (message) => {
console.log(message.toString("utf8"));
});
});
// ...
서버에서도 마찬가지로 " message " 이벤트를 받아서 FrontEnd로부터 메시지를 받을 수 있다.
const socket = new WebSocket(`ws://${window.location.host}`);
socket.addEventListener("open", () => {
console.log("Conneted to Server");
});
socket.addEventListener("message", (message) => {
console.log("message : ", message);
});
socket.addEventListener("close", () => {
console.log("Disconneted to Server");
});
setTimeout(() => {
socket.send("hello from the browser!");
}, 10000);
FrontEnd도 마찬가지로 " send " 이벤트를 통해서 서버에게 메시지를 보낼 수 있다.
3. 다수의 FrontEnd와 통신하기
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 Zoom Clone
link(rel="stylesheet", href="https://unpkg.com/mvp.css")
body
header
h1 Zoom
main
ul
form
input(type="text", name="write a msg", required)
button Send
script(src="/public/js/app.js")
pug에서 form 안에 텍스트를 입력할 수 있는 input과 button을 만들어주고, ul은 메시지 리스트를 나타낼 것이다.
const ul = document.querySelector("ul");
const form = document.querySelector("form");
const socket = new WebSocket(`ws://${window.location.host}`);
// ...
function handleSubmit(event) {
event.preventDefault();
const input = form.querySelector("input");
socket.send(input.value);
input.value = "";
}
form.addEventListener("submit", handleSubmit);
form을 querySelector로 가져온 뒤 submit 이벤트를 통해서 서버로 작성한 내용을 전달한다.
wss.on("connection", (socket) => {
socket.send("hello!!");
socket.on("close", () => console.log("Disconnected from the Browser"));
socket.on("message", (message) => {
socket.send(message.toString()); // -- *
});
});
서버에서는 다시 전달 발은 메시지를 FrontEnd로 넘겨준다.
하지만 아직까지는 FrontEnd와 서버 간의 1: 1 메시지만 가능하다. 우리는 많은 FrontEnd끼리의 통신을 목표로 만들고
있다.
const sockets = [];
wss.on("connection", (socket) => {
// console.log(socket);
sockets.push(socket);
socket.send("hello!!");
socket.on("close", () => console.log("Disconnected from the Browser"));
socket.on("message", (message) => {
sockets.forEach((client) => {
client.send(message.toString());
});
});
});
wss.on("connetion" () => {})
connection 이벤트는 FrontEnd와 서버가 webSocket 통신이 성공될 때마다 호출된다.
그렇기 때문에 임의의 데이터베이스 sockets 배열을 만들어 안에 socket을 저장한다면 서버와 통신하는 모든 socket을
저장할 수 있고, message가 오면 모든 sokect에게 전달해서 다중 통신이 가능하게 만들었다.
// ...
socket.addEventListener("message", (message) => {
const li = document.createElement("li");
li.innerHTML = message.data;
ul.append(li);
});
// ...
message가 오면 li에 전달받은 메시지를 넣어 화면에 나오게 만들었다.
4. 닉네임 나오게 하기
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 Zoom Clone
link(rel="stylesheet", href="https://unpkg.com/mvp.css")
body
header
h1 Zoom
main
form#nick
input(type="text", name="choose a nickname", required)
button Save
ul
form#messge
input(type="text", name="write a msg", required)
button Send
script(src="/public/js/app.js")
메시지는 완벽하게 전달이 되지만, 익명으로 메시지가 나오기 때문에 FrontEnd별 닉네임을 정하는 기능을
추가하려고 한다.
먼저, 새롭게 form 태그를 하나 만들고 안에 닉네임용 input과 button을 만들어준다.
const ul = document.querySelector("ul");
const nickForm = document.querySelector("#nick");
const meesageForm = document.querySelector("#message");
// ...
function handleSubmit(event) {
event.preventDefault();
const input = meesageForm.querySelector("input");
socket.send(input.value);
input.value = "";
}
function handleNickSumit(event) {
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send(input.value);
input.value = "";
}
meesageForm.addEventListener("submit", handleSubmit);
nickForm.addEventListener("submit", handleNickSumit);
form이 하나 추가돼서 querySelector를 수정해주고 각각 나눠서 sumit 함수를 만들어주었다.
// ...
function makeMessage(type, payload) {
const msg = { type, payload };
return JSON.stringify(msg);
}
function handleSubmit(event) {
event.preventDefault();
const input = meesageForm.querySelector("input");
socket.send(makeMessage("new_message", input.value));
input.value = "";
}
function handleNickSumit(event) {
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send(makeMessage("nickname", input.value));
input.value = "";
}
meesageForm.addEventListener("submit", handleSubmit);
nickForm.addEventListener("submit", handleNickSumit);
현재 필요한 것은 하나의 webSocket에서 2개의 유형으로 나뉘어서 처리가 돼야 한다.
가장 좋은 방법은 JSON을 서버에게 보내주는 건데, webSocket으로 서버에게 메시지를 보낼 때는 String 자료형으로만 보낼 수 있다.
그래서 JSON.strignify를 사용해 JSON 데이터를 String으로 변환해서 서버에 보내고 서버에서 다시
JSON.parse로 JSON 데이터로 바꿔서 처리하는 방식을 사용할 것이다.
wss.on("connection", (socket) => {
socket["nickname"] = "Anon";
sockets.push(socket);
socket.send("hello!!");
socket.on("close", () => console.log("Disconnected from the Browser"));
socket.on("message", (message) => {
const data = JSON.parse(message.toString());
switch (data.type) {
case "new_message":
sockets.forEach((client) => {
client.send(`${socket.nickname}: ${data.payload}`);
});
break;
case "nickname":
socket["nickname"] = data.payload;
break;
}
});
});
서버에서 message를 JSON.parse로 다시 JSON 데이터로 바꿔서 각 타입마다 처리하게 작업했다.
socket ["nickname"]은 socket도 객체이기 때문에 위와 같이 선언이 가능하며, 그럴 경우 최초 연결 시 닉네임이
지정되지 않기 때문에 초기화를 시켜야 했다.
그래서 처음 연결 시 socket ["nickname"]을 Anon으로 초기화시켰다.
5. 깃허브
https://github.com/SeoJaeWan/zoom-clone
'Node.js > 실험실' 카테고리의 다른 글
[Node.js] Express ORM 세팅해보기 (0) | 2022.10.09 |
---|---|
[Node.js] Express에 Webpack 구현하기 (1) | 2022.09.28 |
[Node.js] Express set "views" (2) | 2022.09.26 |
[Node.js] Zoom 클론코딩 - 채팅방편 (1) | 2022.06.21 |
[Node.js] Zoom 클론코딩 - 세팅편 (0) | 2022.06.07 |