노마드 코더의 줌 클린코딩을 공부한 내용입니다.
WebSocket을 이용하여 채팅을 구현한다.
express 서버를 구현한 상태에서 진행하며, express 서버 구현은 전 게시글에서 볼 수 있다.
[NodeJS] express 서버 구축
노마드 코더의 줌 클린코딩을 공부한 내용입니다. 채팅 서버를 만들기 위한 express 서버를 구축한다. 폴더 구조 📁 chat ┣ 📁 src ┃ ┣ 📁 public ┃ ┃ ┗ 📁 js ┃ ┃ ┃ ┗ 📄 app.js ┃ ┗ 📁 views
hello-x5957.tistory.com
폴더 구조
📁 chat
┣ 📁 src
┃ ┣ 📁 public
┃ ┃ ┗ 📁 js
┃ ┃ ┃ ┗ 📄 app.js
┃ ┗ 📁 views
┃ ┃ ┗ 🐶 home.pug
┃ ┗ 📄 server.js
WebSocket 기본 구현
server.js
에 Http 및 WebSocket 서버를 구현한다.
express application으로부터 http 서버를 생성하고, 그 위에 Websocket 서버를 생성한다.
서버는 모두 동일포트에서 동작하게 된다.
const app = express();
const PORT = 3000;
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
server.listen(PORT, handleListen);
Websocket 이벤트 구현
여기서는 총 3가지의 이벤트를 사용한다.
- open : 소켓이 연결되면 발생
- close : 소켓 연결이 종료되면 발생
- message : 소켓에서 메시지를 받으면 발생
먼저 소켓이 연결되면 "Connected to Browser ✅" 를 콘솔에 출력한다.
wss.on("connection", (socket) => {
console.log("Connected to Browser ✅");
...
});
소켓 연결이 종료되면 "Disconnected from the Browser ❌" 를 콘솔에 출력한다.
socket.on("close", () => console.log("Disconnected from the Browser ❌"))
소켓으로부터 메시지를 받으면 받은 메시지를 브라우저에 다시 전달해준다.
즉, 에코서버의 동작을 하도록 한다.
socket.on("message", (msg) => {
socket.send(msg);
})
이때 메시지가 Buffer로 출력된다면 인코딩을 해줘야한다.
msg = msg.toString('utf8');
app.js
에서 브라우저 접속 시 WebSocket 서버로의 연결을 수행하도록 구현한다.
소켓을 생성한다.
const socket = new WebSocket(`ws://${window.location.host}`);
그 후 open, close, message 이벤트를 구현한다.
소켓의 연결이 열리면 "Connected to Server ✅" 를 콘솔에 출력한다.
웹에서 콘솔은 F12를 눌러 개발자모드에서 확인할 수 있다.
socket.addEventListener("open", () => {
console.log("Connected to Server ✅");
});
서버가 오프라인 상태가 되어 소켓이 종료되면 "Disconnected to Server ❌" 를 콘솔에 출력한다.
socket.addEventListener("close", () => {
console.log("Disconnected to Server ❌");
});
서버로부터 메시지를 받으면 "New message: 메시지내용" 을 콘솔에 출력한다.
socket.addEventListener("message", (message) => {
console.log("New message: ", message.data);
});
또한 서버로 메시지를 전송한다.
socket.send("hello!");
여기까지하면
1. 브라우저와 서버가 연결
2. 브라우저 -> 서버로 "hello!" 전송
3. 서버는 받은 메시지를 브라우저로 재전송
4. 브라우저는 받은 메시지를 출력 : "New message: hello!"
위의 기능을 구현하게 된다.
이제는 본격적으로 채팅을 구현해보자.
채팅 구현
프론트엔드 : 화면
home.pug
에 채팅을 위한 Form을 작성한다.
닉네임 설정 시 사용하기 위한 Form을 "nick", 채팅은 "message"로 하였다.
main
form#nick
input(type="text", placeholder="choose a nickname", required)
button Save
ul
form#message
input(type="text", placeholder="write a msg", required)
button Send
프론트엔드 : 기능
app.js
에 버튼을 누르면 닉네임 및 메시지를 전송하도록 구현한다.
먼저 서버로 메시지 전송 시 String 형태로 보내주기 위해, JSON -> String 로 변환하는 함수를 작성한다.
function makeMessage(type, payload){
const msg = {type, payload}
return JSON.stringify(msg);
}
채팅을 보낼 때의 기능을 구현하기 위한 함수를 작성한다.
입력한 메시지를 String 형태로 변경하여 타입 및 채팅내용을 서버로 전송한다.
그 다음 내가 보낸 메시지는 닉네임이 아닌 "You"로 표시하여 출력한 뒤, 입력한 채팅을 지워준다.
function handelSubmit(event){
event.preventDefault();
const input = messageForm.querySelector("input");
socket.send(makeMessage("new_message", input.value));
const li = document.createElement("li");
li.innerText = `You : ${input.value}`;
messageList.append(li);
input.value = "";
}
닉네임 설정 시 기능을 구현하기 위한 함수를 작성한다.
입력한 닉네임을 String 형태로 변환하여 타입 및 닉네임을 서버로 전송한다.
그 다음 입력한 닉네임은 지워준다.
function handelNickSubmit(event){
event.preventDefault();
const input = nickForm.querySelector("input");
socket.send(makeMessage("nickname", input.value));
input.value = "";
}
Form 의 버튼을 누르면 각 함수를 수행하도록 지정한다.
messageForm.addEventListener("submit", handelSubmit);
nickForm.addEventListener("submit", handelNickSubmit);
서버로부터 메시지를 받을때의 기능을 구현한다.
li 태그를 생성하여 웹에 표시해준다.
socket.addEventListener("message", (message) => {
const li = document.createElement("li");
li.innerText = message.data;
messageList.append(li);
});
백엔드
마지막으로 server.js
에 서버의 동작을 구현한다.
먼저 서버에 연결된 소켓을 기억하기 위해 sockets 를 생성한다.
const sockets = [];
소켓이 연결됬을 때의 동작을 connection 이벤트 내에 구현한다.
wss.on("connection", (socket) => {
...
});
소켓이 연결되면 sockets에 연결된 소켓을 저장한다.
sockets.push(socket);
연결시 닉네임을 "Anon", 즉 익명으로 설정한다.
닉네임을 설정하지 않은 경우에는 "Anon"으로 표시된다.
socket["nickname"] = "Anon";
소켓으로부터 받은 메시지를 JSON 형태로 변환한다.
socket.on("message", (msg) => {
const message = JSON.parse(msg);
...
받은 메시지의 타입을 이용하여 채팅, 닉네임 설정 중 어느 동작을 수행해야 하는지 구분한다.
채팅의 경우, 받은 메시지 및 보낸 소켓의 닉네임을 전송한 소켓을 제외한 연결된 모든 소켓에 전송해준다.
닉네임 설정의 경우, 소켓의 닉네임을 변경하여 저장한다.
...
switch (message.type) {
case "new_message":
sockets.forEach(aSocket => {
if(aSocket != socket)
aSocket.send(`${socket.nickname}: ${message.payload}`)
});
break;
case "nickname":
socket["nickname"] = message.payload;
break;
}
})
결과
이로써 WebSocket 을 이용한 간단한 채팅을 구현할 수 있다.
WebSocket 채팅 구현 결과