웹소켓 / 채팅방 구현

이재영·2023년 5월 24일
0

Node.js

목록 보기
10/12
post-thumbnail

결과물

동작설명

채팅방 2개가 있고, 로그인 하면 기본으로 0번방으로 접속됨
채팅은 해당 방에 있는 유저한테만 보이고, 접속한 사용자의 목록중에서
id를 클릭해서 내용 입력 후, 귓속말 버튼을 누르면 해당 유저에게 귓속말 가능

전체 코드

main.ejs

<!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="/socket.io/socket.io.js"></script>
</head>
<body>
<div style="display: flex;">
    <div class ="content">
        <div>귀여운 동물 카톡</div>
        <div id = "login">
            <p>로그인</p>
            <input type="text" id="username">
            <button id="loginBtn">접속</button>
        </div>
        <div id="main">
            <select name="" id="rooms">
                <option value="0번방">0번방</option>
                <option value="1번방">1번방</option>
            </select>
            <div id="send">
                <input type="text" id="msg">
                <button id="sendBtn">보내기</button>
            </div>
            <div id ="send2">
                <input type="text" id="msg2">
                <button id="sendBtn2">귓속말</button>
            </div>
            <ul id="messages">
            </ul>
        </div>
        <div id="userAll">
            <span>접속한 사용자</span>
            <ul id="list">
            </ul>
        </div>
    </div>
</div>
</body>
<script>
    window.onload = ()=>{
        loginBtn.onclick = ()=>{

            login.style.display = "none";
            main.style.display = "block";

            const name = username.value;
            let room = rooms.options[rooms.selectedIndex].value;
            const socket = io.connect();

            socket.emit("list",room,name);
            socket.emit("joinRoom",room,name);

            rooms.onchange = function(e){
                //이벤트가 일어난 태그
                let el = e.target;
                console.log("--------el",el.selectedIndex);
                // 해당 유저가 room에서 떠남
                console.log(room); // 현재방
                socket.emit("leaveRoom",room,name);

                room = rooms.options[el.selectedIndex].value;
                console.log(room); // 바꾼방

                socket.emit("joinRoom",room,name);
            }

            socket.on("list",(room,room1)=>{
                
                list.innerHTML ="";
                console.log(room1);

                room1.forEach((el)=>{
                    console.log(el);
                })
                
                for (let i = 0; i < room1.length; i++) {
                    
                    list.innerHTML +=
                    `<button class="bbtn">${room1[i].name}</button><br>`;
                }

                 let bbtn = document.querySelectorAll(".bbtn");
                 console.log(bbtn);

                 bbtn.forEach((el,index)=>{

                    el.onclick = ()=>{
                        console.log("클릭됨");
                        console.log(index);
                        msg2.value =room1[index].name;
                    }
                 })
            })

            socket.on("joinRoom",(room,name)=>{
                messages.innerHTML += `
                
                <li class="join_text">
                    ${name}님이 ${room}에 들어왔음.
                </li>
                `;
            })
            socket.on("leaveRoom",(room,name)=>{
                messages.innerHTML += `
                    <li class = "leave_text">
                    
                        ${name}님이 ${room}에서 나갔음.
                    
                    </li>
                `
            })

            socket.on("chat",(name,msg)=>{

                const lii = document.createElement("li");
                const textt = `${name} : ${msg}`;
                lii.append(textt);
                messages.appendChild(lii);
           
            })

            sendBtn.onclick= ()=>{
            console.log("눌림");
                socket.emit("chat",room,name,msg.value);
                msg.value ="";
            }
            
            sendBtn2.onclick =()=>{
                socket.emit("chat2",msg2.value,name,msg.value);
                msg.value = "";
            }
        }
    }
</script>
</html>

app.js

const express = require("express");
const path = require("path");
const socketio = require("socket.io");
const app = express();
app.set("views",path.join(__dirname,"page"));
app.set("view engine","ejs");

const server = app.listen(8070,()=>{
    console.log("server on");
})  

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

let userId=[];
let room1=[];

const io = socketio(server);

// 유저가 접속하면
io.on("connection",(socket)=>{

    socket.on("joinRoom",(room,name)=>{
        //방에 유저가 접속하면
        //join 메서드로 방에 입장 시킨다
        //방의 개념
        socket.join(room);
        //현재 방에 있는 클라이언트에게 이벤트 푸쉬
        io.to(room).emit("joinRoom",room,name);
    })

    socket.on("leaveRoom",(room,name)=>{
        //유저가 방에서 나가면
        //유저가 방에서 제외되게 해주고
        socket.leave(room);
        //어느방에서 누가 나갔는지 해당 방에 있는 유저들에게 이벤트 푸쉬
        io.to(room).emit("leaveRoom",room,name);
    })
    
    // 유저 접속시 배열에 유저의 아이디 추가
    userId.push(socket.id);

    const socketId = socket.id;

    // 방번호랑 , 로그인할 때 이름을 받고
    socket.on("list",(room,name)=>{
        
        room1.push({name,socketId,room});
        socket.join(room);
        // 그 룸에 다가 그사람 이름 표시해야하는데
        io.to(room).emit("list",room,room1);
    })

    socket.on("disconnect",()=>{
        
        userId = userId.filter((value)=> {
            
            return value!=socket.id
        });
       
        room1 = room1.filter((value)=> value.socketId!=socket.id)
        
        io.emit("list","",room1);
    })

    socket.on("chat",(room,name,msg)=>{
        io.to(room).emit("chat",name,msg);
    })

    socket.on("chat2",(id,name,msg)=>{

        for (let index = 0; index < room1.length; index++) {
            
            if(id==room1[index].name){

                id=room1[index].socketId;
            }
        }
        io.to(id).emit("chat",name,"귓속말"+msg);
    })

})

핵심 코드 동작

이름 입력 후 접속 버튼을 눌렀을 때

// 🚩 main.ejs
loginBtn.onclick = ()=>{
	const name = username.value;
  // select 태그는 따로 설정하지않으면 기본 value는 첫번째 옵션으로 들어간다.
   	let room = rooms.options[rooms.selectedIndex].value;
 
  // 서버에 연결시도
  const socket = io.connect();

  // 서버의 socket.on 에 emit
  socket.emit("list",room,name);
  socket.emit("joinRoom",room,name);
}

// 🚩 app.js
// 매개변수 room : 몇번방 인지, name : 입력한 이름
socket.on("list",(room,name)=>{
  // room1 빈배열에 객체정보를 푸쉬
  room1.push({name,socketId,room});
  // join() 메서드를 사용해 소켓을 특정 방에 조인
  socket.join(room);
  // to() 메서드를 사용해 클라이언트 쪽 socket.on에 emit
  io.to(room).emit("list",room,room1);
}
// 동작원리는 같다.
  socket.on("joinRoom",(room,name)=>{
  socket.join(room);
  io.to(room).emit("joinRoom",room,name);
})
// emit 을 했으니 다시 main.ejs 의 socket.on으로 
// 🚩 main.ejs           
socket.on("list",(room,room1)=>{
                
  list.innerHTML ="";
  for (let i = 0; i < room1.length; i++) {
    // 옆에 접속한 유저의 목록을 띄움.
    list.innerHTML +=
      `<button class="bbtn">${room1[i].name}</button><br>`;}
  // 접속한 유저의 목록을 띄울 때 button 태그를 달아
  // 유저 이름 클릭시 귓속말 input에 클릭한 유저 이름 들어가게끔 설정.
  let bbtn = document.querySelectorAll(".bbtn");
  bbtn.forEach((el,index)=>{

    el.onclick = ()=>{
      msg2.value = room1[index].name;
    }})})

// 유저가 방에 입장했을때 들어왔다는 text를 띄움.
socket.on("joinRoom",(room,name)=>{
  messages.innerHTML += `

<li class="join_text">
${name}님이 ${room}에 들어왔음.
</li>
`;})

채팅 입력후 보내기 버튼을 눌렀을 때

//🚩 main.ejs
sendBtn.onclick= ()=>{
  // socket.on("chat")으로 방번호, 이름, 메세지 내용을 emit
	socket.emit("chat",room,name,msg.value);
    msg.value ="";
}
//🚩 app.js
socket.on("chat",(room,name,msg)=>{
  // room 번호에 join 된 유저한테만 emit
        io.to(room).emit("chat",name,msg);
    })

//🚩 main.ejs
socket.on("chat",(name,msg)=>{

	const chatlist = document.createElement("li");
	const chatcontent = `${name} : ${msg}`;
    chatlist.append(chatcontent);
    messages.appendChild(chatlist);
})

채팅 입력후 귓속말 버튼을 눌렀을 때

// 🚩 main.ejs
whisperBtn.onclick =()=>{
  // socket.on("chat2") 에 클릭한 유저이름, 내 이름, 메시지  내용을 emit
	socket.emit("chat2",msg2.value,name,msg.value);
    msg.value = "";
}

//🚩 app.js
socket.on("chat2",(id,name,msg)=>{
// io.to로 특정 유저에게 emit 하려면
//해당 유저의 소켓ID를 사용해야 하기때문에 소켓ID를 추출하는 과정.
  for (let index = 0; index < room1.length; index++) {

    if(id==room1[index].name){

      id=room1[index].socketId;
    }
  }
  io.to(id).emit("chat",name,"귓속말"+msg);
})
// 그 이후 위와같이 main.ejs의 socket.on("chat")이 실행됨.

채팅방을 변경했을 때

rooms.onchange = function(e){
  let el = e.target;
  // 여기서 room 의 값은 0번방
  socket.emit("leaveRoom",room,name);
  // room의 값은 el의 selectedIndex 의 값을 저장
  // 0번방 or 1번방이 저장됨.
  room = rooms.options[el.selectedIndex].value;
  // 여기서 room 의 값은 1번방
  socket.emit("joinRoom",room,name);
}
이후 emit 을 통해 어떤 유저가 어떤방이 들어오고 나갔는지
출력하는 socket.on 이벤트를 실행.

클라이언트가 소켓 연결을 종료했을 때

// 🚩 app.js
// 내장된 이벤트로 disconnect는 클라이언트가 서버와 연결을 해제할 떄 
// 자동으로 발생하는 이벤트
socket.on("disconnect",()=>{
  // 클라이언트가 서버에 연결했을 때 userId 라는 빈배열에 소켓ID를 푸쉬했었음.
  // 그 푸쉬한 배열에서 지금 연결을 해제한 socket.id 만 제외하고 필터하여 userId 배열 갱신
  userId = userId.filter((value)=> {
    return value!=socket.id
  });

  // 접속한 유저의 목록에서도 제외해야하니깐 똑같이 필터
  room1 = room1.filter((value)=> value.socketId!=socket.id)

  // 연결된 모든 클라이언트에게 메시지 emit
  io.emit("list","",room1);
})
// main.ejs의 socket.on("list")로 가서 이벤트 처리됨.
profile
한걸음씩

0개의 댓글