[포스코x코딩온] 웹개발자 풀스택 부트캠프8기

[포스코x코딩온]Socket.IO를 활용한 채팅 만들기 완성

항상 발전하는 개발자 2023. 8. 31. 00:56
728x90

Socket.IO를 활용한 채팅 만들기 완성

1. 채팅방 입장 시 입장 문구 띄우기

이러한 결과를 만들기 위한 코드


클라이언트

  • 입장 전에 채팅방이랑 사용자 이름을 서버로 전송합니다.
<form id="room">
        <input type="text" id="roomName" placeholder="채팅방 만들기" />
        <input type="text" id="userName" placeholder="사용자 이름 입력" />
        <button>만들기</button>
</form>
      
 <script>
       ///폼 이벤트
      roomForm.addEventListener("submit", (e) => {
        e.preventDefault();
        const roomName = roomForm.querySelector("#roomName");
        const userName = roomForm.querySelector("#userName");
        if (roomName.value === "" || userName.value === "") {
          alert("방이름과 닉네임 적어주세요");
          return;
        }
        socket.emit("create", roomName.value, userName.value, () => {
          const main = document.querySelector("#main");
          const body = document.querySelector("#body");
          main.hidden = true;
          body.hidden = false;
          myName = userName.value;
        });
      });
 </script>

서버

  • 받아온 채팅방으로 join을 하고 socket.room이랑 socket.user에 데이터 저장하고 입장문구를 보내줍니다.
  socket.on("create", (roomName, userName, cb) => {
    //join(방이름) 해당 방이름으로 없다면 생성. 존재하면 입장
    //socket.rooms에 socket.id값과 방이름 확인가능
    socket.join(roomName);
    //socket은 객체이며 원하는 값을 할당할 수 있음
    socket.room = roomName;
    socket.user = userName;

    socket.to(roomName).emit("notice", `${socket.user}님이 입장하셨습니다`);

클라이언트

  • 입장문구를 받아와서 화면에 띄워줍니다.
      //입장 메세지 이벤트
      socket.on("notice", (message) => {
        const div = document.createElement("div");
        const p = document.createElement("p");
        p.textContent = message;
        div.appendChild(p);
        notice.appendChild(div);
      });

2. 채팅하기

흰색과 노란색은 전체에게 보내는 메시지이고 빨간색은 귓속말로 특정 대상자에게 보낸다.

이번 코드는 코드를 보면서 이해하면 되기래 코드만 올리겠습니다. CSS도 같이 있습니다.


클라이언트

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <script src="/socket.io/socket.io.js"></script>
        <title>Document</title>
        <style>
            #body {
                width: 100%;
                height: 70vh;
                position: relative;
                background: #75e3c4;
            }
            #chat {
                position: absolute;
                bottom: 0px;
                width: 100%;
                display: flex;
                justify-content: space-between;
            }
            #chat input {
                width: 80%;
            }
            .my-chat {
                display: flex;
                justify-content: end;
                padding: 2px 0px;
            }
            .my-chat p {
                margin: 0;
                padding: 10px;
                background: yellow;
                margin-right: 10px;
                border-radius: 10px;
            }
            .other-chat {
                display: flex;
                justify-content: start;
                padding: 2px 0px;
            }
            .other-chat p {
                margin: 0;
                padding: 10px;
                background: white;
                margin-left: 10px;
                border-radius: 10px;
            }
            #notice {
                display: flex;
                flex-direction: column;
                text-align: center;
            }
            #notice p {
                margin: 0;
            }
            .secret-chat p {
                background: pink;
            }
        </style>
    </head>
    <body>
        <div id="main">
            <form id="room">
                <input type="text" id="roomName" placeholder="채팅방 만들기" />
                <input type="text" id="userName" placeholder="사용자 이름 입력" />
                <button>만들기</button>
            </form>
            <ul id="lists"></ul>
        </div>
        <div id="body" hidden>
            <div id="msg">
                <div id="notice"></div>
                <!-- <div class="my-chat">
                    <p>채팅테스트(나)</p>
                </div>
                <div class="other-chat">
                    <p>채팅테스트(상대방)</p>
                </div> -->
            </div>
            <form id="chat">
                <select id="userList"></select>
                <input type="text" id="message" placeholder="메세지 입력" />
                <button>입력</button>
            </form>
        </div>

        <script>
            const socket = io();
            const roomForm = document.querySelector('#room');
            const chatForm = document.querySelector('#chat');
            const msg = document.querySelector('#msg');
            const notice = document.querySelector('#notice');

            let myName;

            //룸리스트
            socket.on('roomList', (roomLists) => {
                console.log(roomLists);
                const lists = document.querySelector('#lists');
                lists.textContent = '';
                roomLists.forEach((roomList) => {
                    const li = document.createElement('li');
                    li.textContent = `${roomList} 와(과) 닉네임 입력 후 입장`;
                    lists.appendChild(li);
                });
            });

            //사용자 리스트
            socket.on('userList', (userLists) => {
                console.log(userLists);
                const lists = document.querySelector('#userList');
                lists.textContent = '';
                let options = `<option value="all">전체</option>`;
                for (let i of userLists) {
                    options += `<option value="${i.key}">${i.name}</option>`;
                }
                lists.innerHTML = options;
            });
            //메세지 띄우기
            socket.on('newMessage', (message, nick, bool) => {
                console.log(message, nick);
                const div = document.createElement('div'); //밖div
                const p = document.createElement('p'); //안쪽p
                console.log('닉', myName);
                if (myName === nick) {
                    div.classList.add('my-chat');
                } else {
                    div.classList.add('other-chat');
                }
                if (bool) {
                    div.classList.add('secret-chat');
                }
                //채팅텍스트
                p.textContent = `${nick} : ${message}`;
                div.appendChild(p);
                msg.appendChild(div);
            });
            //입장 메세지 이벤트
            socket.on('notice', (message) => {
                const div = document.createElement('div');
                const p = document.createElement('p');
                p.textContent = message;
                div.appendChild(p);
                notice.appendChild(div);
            });

            ///폼 이벤트
            roomForm.addEventListener('submit', (e) => {
                e.preventDefault();
                const roomName = roomForm.querySelector('#roomName');
                const userName = roomForm.querySelector('#userName');
                if (roomName.value === '' || userName.value === '') {
                    alert('방이름과 닉네임 적어주세요');
                    return;
                }
                socket.emit('create', roomName.value, userName.value, () => {
                    const main = document.querySelector('#main');
                    const body = document.querySelector('#body');
                    main.hidden = true;
                    body.hidden = false;
                    myName = userName.value;
                });
            });
            //메세지 보내기
            chatForm.addEventListener('submit', (e) => {
                e.preventDefault();
                const user = document.querySelector('#userList');
                const message = document.querySelector('#message');
                console.log(user.value);
                const msg = {
                    nick: myName,
                    user: user.value,
                    message: message.value,
                };
                socket.emit('sendMessage', msg);
                message.value = '';
            });
        </script>
    </body>
</html>

서버

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

const PORT = 8000;
const app = express();

const server = http.createServer(app);
const io = SocketIO(server);

app.set("view engine", "ejs");

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

app.get("/:room", (req, res) => {
  const room = req.params.room;
});

function getUsersInRoom(room) {
  const users = [];
  const clients = io.sockets.adapter.rooms.get(room);
  //console.log(clients);
  if (clients) {
    clients.forEach((socketId) => {
      const userSocket = io.sockets.sockets.get(socketId);
      //사용자에게 메세지를 보내기 위해서 객체형태로 변경
      //key: 소켓아이디, value:이름
      const info = { name: userSocket.user, key: socketId };
      users.push(info);
    });
  }
  return users;
}
const roomList = [];

io.on("connection", (socket) => {
  //socket!//
  //socket은 접속한 웹페이지, io는 접속해있는 모든 웹페이지
  //웹 페이지가 접속이되면 고유한 id값이 생성됨. socket.id로 확인가능
  //console.log(io.sockets);
  //채팅방 목록 보내기
  socket.emit("roomList", roomList);
  //채팅방 만들기 생성
  socket.on("create", (roomName, userName, cb) => {
    //join(방이름) 해당 방이름으로 없다면 생성. 존재하면 입장
    //socket.rooms에 socket.id값과 방이름 확인가능
    socket.join(roomName);
    //socket은 객체이며 원하는 값을 할당할 수 있음
    socket.room = roomName;
    socket.user = userName;

    socket.to(roomName).emit("notice", `${socket.user}님이 입장하셨습니다`);

    //채팅방 목록 갱신
    if (!roomList.includes(roomName)) {
      roomList.push(roomName);
      //갱신된 목록은 전체가 봐야함
      io.emit("roomList", roomList);
    }
    const usersInRoom = getUsersInRoom(roomName);
    io.to(roomName).emit("userList", usersInRoom);
    cb();
  });

  socket.on("sendMessage", (message) => {
    if (message.user === "all") {
      io.to(socket.room).emit(
        "newMessage",
        message.message,
        message.nick,
        false
      );
    } else {
      io.to(message.user).emit(
        "newMessage",
        message.message,
        message.nick,
        true
      );
      //자기자신한테 메세지 띄우기
      socket.emit("newMessage", message.message, message.nick, true);
    }
  });

  socket.on("disconnect", () => {
    if (socket.room) {
      socket.leave(socket.room);
    }
  });
});

server.listen(8000, () => {
  console.log(`http://localhost:${PORT}`);
});

이번 Socket.IO를 통해 채팅을 만들면서 느낀점이 소켓통신을 통해 실시간으로 서비스하는 무언가를 만들 수 있겠다는 점과 통신을 위해 하나씩 하나씩 보면서 하면 코드를 금방 작성한다는 점이다.

또, 이번에 사용한 Socket.IO 함수 말고도 더 많은 함수들이 http://socket.io/공식문서에 많이 있으니 자주 참조하면 되겠다는 것을 알게 되었다. 


내일부터 프로젝트가 시작되는데 이제까지 배운 부분을 잘 사용하여 좋은 프로젝트를 만들어야겠다.

728x90