[새싹X코딩온] 채팅 프로그램

Sohee Kwon·2023년 12월 26일
post-thumbnail

1. 들어가기

1-1. 미니 프로젝트

  • socket.io에 대한 전반적인 것들에 대해 배우고 간단한 채팅 프로그램을 만들게 되었다.
  • 프로젝트 기간은 약 3일로 예정
  • 백엔드 부분 위주로 작성되어 있으므로, 이 외의 코드는 밑에 github을 통해 확인해주세요.

1-2. 기능

계획한 기능

  • 채팅 프로그램 입장하기 위한 닉네임 로그인 ⭕️

  • 단체 채팅방(room)

    • 단체 채팅방 입장 ⭕️
    • 단체 채팅방 내에서 채팅 입력 ⭕️
    • 단체 채팅방 들어와있는 사람 목록 ⭕️
    • 단체 채팅방 나가기 ⭕️
  • 1:1 채팅방(DM 구현 응용)

    • 개인 채팅방 입장
    • 개인 채팅방에서 채팅 입력
    • 개인 채팅방 나가기

2. 프로그램 구현

2-1. 채팅 프로그램 입장하기 위한 닉네임 로그인

닉네임, 채팅방 로그인 UI

  • 닉네임을 입력하고, 채팅방을 선택한 후 입장을 누르면 해당하는 채팅방으로 입장하게 됨.

2-2. 단체 채팅방

room 구현

// 클라이언트를 특정 방으로 추가
socket.join("roomA");

// 클라리언트를 특정 방에서 제거
socket.leave("roomA");

// 특정 방에 속한 모든 클라이언트에게 특정 이벤트와 데이터를 발송
io.to("roomA").emit("msg", "hello");

단체 채팅방 입장 & 채팅 입력

1차 시도

  • server.js
socket.on("entry", (res) => {
    if (Object.values(userIdArr).includes(res.userId)) {
      // 닉네임이 중복될 경우에
      socket.emit("error", {
        msg: "중복된 아이디가 존재하여 입장이 불가합니다.",
      });
    } else {
      // 닉네임이 중복되지 않을 경우에
      // 해당하는 단체방에 입장
      socket.join(res.roomId);
      console.log(res.roomId);
      // 특정 방에 속한 모든 클라이언트에게 전달
      io.to(res.roomId).emit("notice", {
        msg: `${res.userId}님이 입장하셨습니다.`,
      });
      socket.emit("entrySuccess", { userId: res.userId });
      userIdArr[socket.id] = res.userId;
    }
    console.log(userIdArr);
    updateUserList();
  });
  • 입장시 공지 멘트는 각 채팅방에서만 확인 가능하지만
  • 채팅 메시지는 존재하는 모든 채팅방에서 확인 가능 (오류!)
    • io.to(res.roomId).emit("chat", { userId: res.userId, msg: res.msg });
      공지와 동일하게 채팅 메세지도 특정 채팅방에서 확인할 수 있도록 io.to(res.roomId).emit()으로 함수를 바꿈. (오류 해결)

2차 시도

  • 새로 고침 & 사이트를 나갈 때 서버 에러 발생

  • 💡 에러 해결

    • disconnection 하는 부분에서 매개변수를 받지 않는데, 위에 처럼 동일한 코드를 사용해서 에러 발생❗
      io.to(res.roomId).emit("notice", ~~~);

    • 해결 방법 : userIdArr 처럼 roomId를 저장해서 서버 내에서 사용할 수 있도록 객체 생성.

const userRoomIdArr = {};

socket.on("entry", (res) => {
    if (Object.values(userIdArr).includes(res.userId)) {
      socket.emit("error", {
        msg: "중복된 아이디가 존재하여 입장이 불가합니다.",
      });
    } else {
      socket.join(res.roomId);
      // userRoomIdArr에 roomId 저장
      userRoomIdArr[socket.id] = res.roomId;
      io.to(res.roomId).emit("notice", {
        msg: `${res.userId}님이 입장하셨습니다.`,
      });
      socket.emit("entrySuccess", { userId: res.userId });
      userIdArr[socket.id] = res.userId;
    }
    updateUserList();
 });

socket.on("disconnect", () => {
    if (userIdArr[socket.id]) {
      io.to(userRoomIdArr[socket.id]).emit("notice", {
        msg: `${userIdArr[socket.id]}님이 퇴장하셨습니다.`,
      });
      // 클라이언트를 특정 방에서 나가게 함
      socket.leave(userRoomIdArr[socket.id]);
      // userRoomIdArr 에서도 삭제
      delete userRoomIdArr[socket.id];
      delete userIdArr[socket.id];
    }
    console.log(userIdArr);
    updateUserList();
});

단체 채팅방 들어와있는 사람 목록

1차 시도

  • 기존에 모든 userList를 클라이언트측에 넘겨줬다면 이제는 특정 방에 대한 userList를 넘겨줌

  • 동일한 userList를 계속 변경해서 보냈더니, 가장 마지막에 입장하는 user에 대해서만 userList가 변경됨 (오류!)

const updateUserList = (roomId) => {
  // userRoomIdArr에서 roomId와 일치하는 socket.id 찾기
  const socketIdInRoom = Object.keys(userRoomIdArr).filter(
    (socketId) => userRoomIdArr[socketId] === roomId
  );

  // userIdArr에서 socketIdInRoom 배열과 일치하는 socket.id : userId 저장
  const usersInRoom = socketIdInRoom.reduce((acc, socketId) => {
    acc[socketId] = userIdArr[socketId];
    return acc;
  }, {});

  io.emit("userList", usersInRoom);
};

2차 시도

  • 동일한 userList로 계속 변경하지 않고, 특정 방마다 이벤트를 만듬 (오류 해결)
const updateAllRoom = (roomId) => {
  // userRoomIdArr에서 roomId와 일치하는 socket.id 찾기
  const socketIdInRoom = Object.keys(userRoomIdArr).filter(
    (socketId) => userRoomIdArr[socketId] === roomId
  );

  // userIdArr에서 socketIdInRoom 배열과 일치하는 socket.id : userId 저장
  const usersInRoom = socketIdInRoom.reduce((acc, socketId) => {
    acc[socketId] = userIdArr[socketId];
    return acc;
  }, {});

  if (roomId === "FRONTEND") io.emit("updateFrontList", usersInRoom);
  else if (roomId === "BACKEND") io.emit("updateBackList", usersInRoom);
  else io.emit("updateFullList", usersInRoom);
};

단체 채팅방 나가기

  • 동일한 url 내에서 실행되고 있기 때문에 socket 연결을 끊기 위해 X 버튼을 누르면 페이지 새로고침하도록 구현
const handleRefreshClick = () => {
    window.location.reload(true);
  };

<button onClick={handleRefreshClick}>✖️</button>

단체 채팅방 UI

2-3. 1:1 채팅방

개인 채팅방 입장

프로젝트 시작해서
나중에 다시 해보는걸로...

profile
개발자 성장로그

0개의 댓글