728x90
728x90

#2.6 Room Notifications

이제 사용자가 방을 떠날 때 이를 알리는 기능을 구현해보자.

여기서 disconnecting 과 disconnected 를 구별해야한다.

disconnecting 은 완전히 disconnected 되기 전의 상태이기 때문에 메세지를 보낼 수 있는 것이다.  

 

socket.rooms 를 하면 아래와 같이 Set 을 의미한다. Set 이기 때문에 forEach 즉, 반복이 가능하다. 

qay... 는 room.id 이며 myroom~ 은 roomName 이다. 

그리고 server.js 에 아래의 line 31~33 과 같이 disconnecting 이벤트에 대해 코드를 추가해준다.

import http from "http";
import SocketIO from "socket.io"
import WebSocket from "ws";
import express from "express";

const app = express();

//set the view
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public")); //user가 public 으로 이동하면 __dirname+/public 폴더를 보여주는 것. 
app.get("/", (req, res) => res.render("home"));//render 
app.get("/*", (req, res) => res.redirect("/"));

const handleListen = () => console.log('Listening on http://localhost:3000');
//app.listen(3000, handleListen);

//http의 서버
const httpServer = http.createServer(app); //app은 requestListener 의 경로. express application으로부터 서버 생성. 
const wsServer  = SocketIO(httpServer);

wsServer.on("connection", (socket) => {
    socket.onAny((event) => {   //socket 에 있는 모든 이벤트 모니터링 가능
        console.log(`Socket Event: ${event}`);
    });
    socket.on("enter_room", (roomName, done) => {
        socket.join(roomName);
        done();
        socket.to(roomName).emit("welcome");    //모든 사람에게 welcome 이벤트 Emit
    });
    socket.on("disconnecting", () => {
        socket.rooms.forEach(room => socket.to(room).emit("bye"));
    })
});

httpServer.listen(3000, handleListen);

 

app.js 에도 앞서 했던 welcome 과 동일한 방식으로 bye 도 추가해준다.

socket.on("bye", () => {
    addMessage("Someone Left!");  
});

 

방에 들어갔다가 나갔음을 알 수 있다. 

 

 

이제 메세지를 보내보자. 

room 안에 있는 message 를 from 에서 찾아서 -> addEventListener 를 추가한뒤 -> new_message event 를 보내면 된다. 

먼저 방에 들어왔음을 보여주는 showRoom 메서드 안에서 from 을 찾아보자. (line 32~33)

 

그 후 line 17~25 와 같이 handleMessageSubmit 을 작성한다. 이 메소드는 우리가 브라우저에서 입력한 메세지 내용을 backend로 전송해주는 역할을 한다.  전송 시 roomName도 함께 인자로 보내줘서 backend 가 어디 room 으로 보낼지 식별 가능하도록 한다. 

 

handleMessageSubmit 에서 input.value 를 초기화 시켜줄때 주의해야한다.

왜냐하면 emit 을 통해 backend 로 보내고 다시 메세지를 front 로 받는 과정에서 

input.value = "" 를 통해 초기화 되어버리기 때문에 브라우저에 출력이 되지 않게 되는 문제가 있기 때문이다. 

그렇기 때문에 value 라는 변수 선언을 통해 값을 저장해놓고 (line20)

line22 에서 그 저장된 값을 쓰도록 함으로써 line 24에 영향을 받지 않도록 한다. 

 

//app.js

const socket = io();

const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");
const room = document.getElementById("room");

room.hidden = true;
let roomName;

function addMessage(message){
    const ul = room.querySelector("ul");
    const li = document.createElement("li");
    li.innerText = message;
    ul.appendChild(li);
}

function handleMessageSubmit(event){
    event.preventDefault();
    const input = room.querySelector("input");
    const value = input.value;
    socket.emit("new_message", input.value, roomName, () => {     //백엔드에 입력한 메세지 전송 
        addMessage(`You: ${value}`);  //대화창에 메세지 출력 
    });
    input.value = "";
}

function showRoom(){
    welcome.hidden = true;
    room.hidden = false; 
    const h3 = room.querySelector("h3");
    h3.innerText = `Room ${roomName}`;
    const form = room.querySelector("form");
    form.addEventListener("submit", handleMessageSubmit);
}

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    socket.emit("enter_room", input.value, showRoom);
    roomName = input.value;
    input.value = "";
}
form.addEventListener("submit", handleRoomSubmit);

socket.on("welcome", () => {
    addMessage("Someone Joined!");  
}); 

socket.on("bye", () => {
    addMessage("Someone Left!");  
});

socket.on("new_message", (addMessage)); 
// = socket.on("new_message", (msg) => {addMessage});

front 에서 보낸 메세지를 이제 backend 에서 받아보자. 

(line 34~37 추가)

import http from "http";
import SocketIO from "socket.io"
import WebSocket from "ws";
import express from "express";

const app = express();

//set the view
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public")); //user가 public 으로 이동하면 __dirname+/public 폴더를 보여주는 것. 
app.get("/", (req, res) => res.render("home"));//render 
app.get("/*", (req, res) => res.redirect("/"));

const handleListen = () => console.log('Listening on http://localhost:3000');
//app.listen(3000, handleListen);

//http의 서버
const httpServer = http.createServer(app); //app은 requestListener 의 경로. express application으로부터 서버 생성. 
const wsServer  = SocketIO(httpServer);

wsServer.on("connection", (socket) => {
    socket.onAny((event) => {   //socket 에 있는 모든 이벤트 모니터링 가능
        console.log(`Socket Event: ${event}`);
    });
    socket.on("enter_room", (roomName, done) => {
        socket.join(roomName);
        done();
        socket.to(roomName).emit("welcome");    //모든 사람에게 welcome 이벤트 Emit
    });
    socket.on("disconnecting", () => {
        socket.rooms.forEach(room => socket.to(room).emit("bye"));
    })
    socket.on("new_message" , (msg, room, done) => {
        socket.to(room).emit("new_message", msg);
        done();
    })
});

httpServer.listen(3000, handleListen);

 

그 후 backend 에서 front 로 보내는 socket.to(room).emit("new_message", msg) 부분을 

front 에 출력하기 위해 아래와 같이 함수를 추가해준다. 

socket.on("new_message", (addMessage));

실행해보자. 

You 를 통해 어디서 보낸 메세지인지 식별이 가능해졌다.

728x90
728x90

#2.5 Room Message

방에 처음 들어갔을 때 그 방 모든 사람들에게 메세지를 전송해보자. 

line 29 와 같이 backend 쪽에 코드를 추가한다. 

//server.js

import http from "http";
import SocketIO from "socket.io"
import WebSocket from "ws";
import express from "express";

const app = express();

//set the view
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public")); //user가 public 으로 이동하면 __dirname+/public 폴더를 보여주는 것. 
app.get("/", (req, res) => res.render("home"));//render 
app.get("/*", (req, res) => res.redirect("/"));

const handleListen = () => console.log('Listening on http://localhost:3000');
//app.listen(3000, handleListen);

//http의 서버
const httpServer = http.createServer(app); //app은 requestListener 의 경로. express application으로부터 서버 생성. 
const wsServer  = SocketIO(httpServer);

wsServer.on("connection", (socket) => {
    socket.onAny((event) => {   //socket 에 있는 모든 이벤트 모니터링 가능
        console.log(`Socket Event: ${event}`);
    });
    socket.on("enter_room", (roomName, done) => {
        socket.join(roomName);
        done();
        socket.to(roomName).emit("welcome");    //모든 사람에게 welcome 이벤트 Emit
    });
});

참고) socket.to

io.on("connection", (socket) => {

  // to one room
  socket.to("others").emit("an event", { some: "data" });

  // to multiple rooms
  socket.to("room1").to("room2").emit("hello");

  // or with an array
  socket.to(["room1", "room2"]).emit("hello");

  // a private message to another socket
  socket.to(/* another socket id */).emit("hey");

  // WARNING: `socket.to(socket.id).emit()` will NOT work
  // Please use `io.to(socket.id).emit()` instead.
});

 

//server.js

backend 에 Line 29와 같이 작성한다.

해당 내용은 같은 roomName을 가진 room 에 있는 socket 들 중 나를 제외한 모든 socket 에 "welcome" 이벤트를 emit 하는 것이다.

backend 에서 보낸 "welcome" 이벤트를 front 에서 받아보자.

//app.js

line 33-35에 "welcome" 이벤트에 대한 액션을 취한다.

액션을 위해 10~15에 addMessage 메소드를 작성한다. 

"Someone Joined!" 라는 message 를 li 에 담고,

그 message 들을 append 함으로써 계속 아래로 달리게 한다. 

const socket = io();

const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");
const room = document.getElementById("room");

room.hidden = true;
let roomName;

function addMessage(message){
    const ul = room.querySelector("ul");
    const li = document.createElement("li");
    li.innerText = message;
    ul.appendChild(li);
}

function showRoom(){
    welcome.hidden = true;
    room.hidden = false;
    const h3 = room.querySelector("h3");
    h3.innerText = `Room ${roomName}`;
}

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    socket.emit("enter_room", input.value, showRoom);
    roomName = input.value;
    input.value = "";
}
form.addEventListener("submit", handleRoomSubmit);

socket.on("welcome", () => {
    addMessage("Someone Joined!");  
})

 

실행해보자.

왼쪽에서 My Room1 이라는 방을 생성한다. 왼쪽은 방에 들어가진 모습이다. 

이제 오른쪽에서 생성한 방인 My Room1 에 들어가보자.

들어가는 순간 왼쪽에 Someone Joined! 라는 메세지가 출력된다. (오른쪽은 방에 새로 들어간 본인이라 출력 안됨 )

 

 

크롬 시크릿 창을 두개 더 띄워서 테스트 해본 화면이다.

동일하게 본인을 제외하고 메세지가 출력됨을 확인할 수 있다. 

728x90
728x90

#2.4 Rooms

Room 이란? : 서로 소통 가능한 socket 들의 그룹

 

모든 websocket이 서로 대화할 필요는 없다. 그저 room 안에 몇개의 websocket 들끼리만 대화하면 된다.

꼭 Chat room 뿐 아니라 websocket 들은 그룹으로 묶일 필요가 있는 것이다. 

예를 들어 배달 앱이라고 하더라도, 배달 기사의 위치를 나한테 알려주기 위해서는 

배달기사 <-> 나 와의 socket 이 room 안에 따로 있어야하는 것이다. 

socketIO는 join 메소드를 통해 room 기능을 제공하고 있다.

 

참고)socket.id

https://socket.io/docs/v4/server-api/#socketid

 

Server API | Socket.IO

Server

socket.io

 

참고) socket. onAny : 임의의 이벤트에서든 실행됨으로써 모든 socket 에 대한 이벤트를 모니터링 할 수 있음 

https://socket.io/docs/v4/server-api/#socketonanycallback

 

Server API | Socket.IO

Server

socket.io

 

//server.js

line 23~30 을 수정해주었다. 

  • socket이 어느 방에 있는지 알기 -> socket.rooms
  • 방에 들어가기 -> socket.join(room1) , socket.join(room1, room2) -> 동시에 여러 방 접속 가능
  • socket 구분 : socket.id
import http from "http";
import SocketIO from "socket.io"
import WebSocket from "ws";
import express from "express";

const app = express();

//set the view
app.set("view engine", "pug");
app.set("views", __dirname + "/views");
app.use("/public", express.static(__dirname + "/public")); //user가 public 으로 이동하면 __dirname+/public 폴더를 보여주는 것. 
app.get("/", (req, res) => res.render("home"));//render 
app.get("/*", (req, res) => res.redirect("/"));

const handleListen = () => console.log('Listening on http://localhost:3000');
//app.listen(3000, handleListen);

//http의 서버
const httpServer = http.createServer(app); //app은 requestListener 의 경로. express application으로부터 서버 생성. 
const wsServer  = SocketIO(httpServer);

wsServer.on("connection", (socket) => {
    socket.onAny((event) => {   //socket 에 있는 모든 이벤트 모니터링 가능
        console.log(`Socket Event: ${event}`);
    });
    socket.on("enter_room", (roomName, done) => {
        console.log(socket.id);
        console.log(socket.rooms); // Set { <socket.id> 
        socket.join(roomName);
        console.log(socket.rooms); // Set { <socket.id>, "room1" }
        
        setTimeout(() => {
            done();
        }, 15000);
    });
});


//websocket 서버
//const wss = new WebSocket.Server({ httpServer });


// function handleConnection(socket) {
//     console.log(socket)
// }

// const sockets = [];

// wss.on("connection", (socket) => {
//     sockets.push(socket);
//     socket["nickname"] = "Anon";    //처음엔 익명으로 설정 
//     console.log("Connected to Browser ✅");
//     socket.on("close", () => {
//         console.log("Disconnected from the Browser ❌");
//     });
//     socket.on("message", (msg) => { 
//         const message = JSON.parse(msg);        //string -> Object
//         //console.log(message, message.toString('utf-8'));
//         switch(message.type){
//             case "new_message" :
//                 //sockets.forEach((aSocket) => aSocket.send(message.payload.toString('utf-8'))); //채팅 메세지만 출력하기 위해 이것만 브라우저에 보냄 
//                 sockets.forEach((aSocket) => aSocket.send(`${socket.nickname}: ${message.payload}`)); //누가 보냈는지, 뭘 보냈는지 
//             case "nickname" :
//                 //console.log(message.payload.toString('utf-8'));     
//                 socket["nickname"] = message.payload;
//         }
//     });
    
// });

httpServer.listen(3000, handleListen);

 

코드 수정 후 room name 을 브라우저에서 입력했더니 backend에서 아래와 같이 확인할 수 있었다.

qay~ 부분이 socket.id 이며,

Set(1) 은 모든 socket 은 처음에 private room 을 한개씩 가지고 있기 때문에 RoomName 이 설정되지 않은 상태의 room 이며

Set(2)는 사용자가 브라우저에 myroom~이라고 RoomName을 지정했을 때의 Room 이다.

 

 

 

 

참고)

  • 방 떠나기 : socket.leave()
  • 방 전체에 메세지 broadcasting : socket.to("room1")
io.on("connection", (socket) => {

  // to one room
  socket.to("others").emit("an event", { some: "data" });

  // to multiple rooms
  socket.to("room1").to("room2").emit("hello");

  // or with an array
  socket.to(["room1", "room2"]).emit("hello");

  // a private message to another socket
  socket.to(/* another socket id */).emit("hey");

  // WARNING: `socket.to(socket.id).emit()` will NOT work
  // Please use `io.to(socket.id).emit()` instead.
});

 

위 기능들을 활용해서, 방에 참가했을 때 방 안의 모든 사람들에게 참여했음을 알리는 메세지를 전송해보자. 

 

먼저 HTML 을 수정한다. (div room 추가)

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 Noom
        link(rel="stylesheet", href="https://unpkg.com/mvp.css")
    body 
        header
            h1 Noom 
        main 
            div#welcome
                form
                    input(placeholder="room name", required, type = "text")
                    button Enter Room 
            div#room
                ul
                form
                    input(placeholder="message", required, type = "text")
                    button Send 
        script(src="/socket.io/socket.io.js")    
        script(src="/public/js/app.js")

 

이제 frontend 를 수정한다.

위에서 생성한 div#room은 처음에는 보이지 않아야 한다. 처음에는 div#welcome 만 보이고, 

roomname 을 입력한 뒤에 방에 들어가면 메세지를 입력할 수 있어야 하기 때문이다. 

room Element를 가져오고, (line 5)

showRoom 메소드를 생성하여 front에서 showRoom이 실행되면 즉 roomName 이 입력되면

그 입력된 값을 back->front 로 넘겨줄 때 이 메소드를 실행하여 welcome 은 숨기고 room 은 보여지게 된다. (line 9~12)

 

 

//app.js

const socket = io();

const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");
const room = document.getElementById("room");

room.hidden = true;

function showRoom(){
    welcome.hidden = true;
    room.hidden = false;
}

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    socket.emit("enter_room", input.value, showRoom);
    input.value = "";
}
form.addEventListener("submit", handleRoomSubmit);

 

이제 참가한 room에 누가 참가했는지를 알려주자.

home.pug 에 h3 를 추가하자. 

    div#room
                h3
                ul
                form
                    input(placeholder="message", required, type = "text")
                    button Send

그 후 app.js 에 

roomName 변수를 생성하고 (line 8)

handleRoomSubmit 에 roomName 을 넣어주고 (line21)

showRoom에서 h3 변수를 생성한 뒤 html 의 h3 아이템을 받아와서 내용을 roomName으로 바꿔준다. (line 14-15)

const socket = io();

const welcome = document.getElementById("welcome");
const form = welcome.querySelector("form");
const room = document.getElementById("room");

room.hidden = true;
let roomName;

function showRoom(){
    welcome.hidden = true;
    room.hidden = false;
    const h3 = room.querySelector("h3");
    h3.innerText = `Room ${roomName}`;
}

function handleRoomSubmit(event){
    event.preventDefault();
    const input = form.querySelector("input");
    socket.emit("enter_room", input.value, showRoom);
    roomName = input.value;
    input.value = "";
}
form.addEventListener("submit", handleRoomSubmit);

실행 후 방 이름을 입력하면

해당 방에 들어왔다는 의미로 h3 헤드라인이 출력된다. 

다음 편에서는 방에 들어왔을 때 방의 모든 사람들에게 인사를 하는 기능을 추가해보자.

728x90

+ Recent posts