[React] socket.io 중복으로 join 되지 않게 하기

준리·2022년 11월 4일
0
post-thumbnail

프로젝트를 진행하면서 socket.io로 채팅 기능을 만들고 있다.
자세한 socket에 대한 이야기를 적어보기 전에, io에 join 된 채팅방을 확인하고, 중복 조인이 안되도록 작업한 로직을 기록해보고자 한다.

// 기본 명령어
socket.connect ( 플랫폼에 접속한다 ) ex: kakaotalk 접속
socket.join ("e", 방에 접속한다 ) ex: kakaoTalk 가족방에 들어간다.
: join을 하게 되면 해당 방에 메세지를 받을 수 있다.
socket.emit ("e", 메세지를 보낸다 ) .to 로 특정 방에도 메세지를 보낼 수 있다.
: 양방향 소통 답게 서버에서 먼저 emit을 client로 보낼 수도 있다.
socket.on ("e", 메세지를 받는다 )

event name을 받아서 특정 event를 대상으로 주로 처리 된다.

현재 상황(join 위주로)

  1. chatList(채팅 목록)에서 모든 방의 join을 실행해주고 있다.
    왜냐하면, 내가 chatList를 보고 있는 상황에서 다른 사람이 메세지를 추가하면 실시간으로 채팅이 업데이트 되도록 하고 싶기 때문이다. 그래서 chatList 에 on을 달아놓았다.
        socket.onSync("alert-new-message", (message) => {
            console.log("message 받은 :>> ", message);
            setUpdatedRoom(message);
        });

이 기능은 chatroom이 join 된 상태에서 실행되기 때문이다.
내가 가진 채팅방의 메세지를 모두 받는 방을 조인하는 것도 대안이 될 수 있겠다.

  1. 문제는 새로운 채팅이 올 때마다 갱신이 되긴하지만, state가 새로 바뀌면서 rerendering이 일어나 join이 반복적으로 일어난다는 것이었다.
    useEffect(() => {
        if (updatedRoom) {
            const newChatRooms = [];
            for (const value of chatList) {
                if (value.chatRoom === updatedRoom.chatRoom) {
                    value.message = updatedRoom.message;
                    value.createAt = updatedRoom.createAt;
                }
                newChatRooms.push(value);
            }
            setChatList(newChatRooms);
        }
    }, [updatedRoom]);

    useEffect(() => {
        chatList?.forEach((item) => {
            socket.joinRoom(String(item.chatRoom), (res) => {
                if (res) {
                    console.log("join ===>", res);
                }
            });
            // console.log(chatList);
        });
    }, [chatList]);

두개의 useEffect가 물리고 물려 계속 렌더링이 일어나는 것이다. 새로운 채팅이 들어오면 1번째 useEffect 가 실행되면서 chatlist state를 바꾼다. 그래서 다시 렌더링이 일어나게 되고, chatList를 디펜던시 어레이로 가진 2번째 useEffect가 실행되며 다시 join이 실행된다. 새로운 메세지가 올때마다 join이 다시 된다는 건 무척이나 좋지 않다. 사실 궁극적으로 해결해야하는 문제이긴 하지만, 일시방편으로 해결해보았다.

해결 방안 (backend 에서 방 정보확인)

현재 socket 코드는 io의 은닉성을 위해 class로 관리되고 있다.
socket의 메서드들은 setter 함수를 만들어 사용했다.

//client socket class

	joinRoom(roomId, cb) {
        const socketId = this.io.id;
        this.io.emit("joinRoom", { roomId, socketId }, cb);
    }

일단 front 에서는 룸에 접속된 정보를 확인할 수 없다.
socket의 room 정보를 확인하기 위해 socketId를 함께 server 에 태워보낸다.

// server socket class

      so.on('joinRoom', (obj: any, callback: any) => {
        const { roomId, socketId } = obj;

        if (!obj) callback('join에서 에러가 발생했어요.');

        if (this.io.sockets.adapter.rooms.get(roomId)) {
          console.log(
            socketId +
              'tried to join ' +
              roomId +
              ' but the room does not exist.'
          );
        } else {
          so.join(roomId);
          callback(roomId);
          console.log('room이 연결됨 :>>', roomId);
          // Socket.join is not executed, hence the room not created.
        }
      }

먼저 에러 처리를 해주고,
this.io.sockets.adapter.rooms은 Map 자료 구조로 되어있다. 빌트인 메서드로 has, get 등을 가지고 있는데, get('key')값을 넣어주면 value 가 튀어나온다. 그래서 true 가되면 이미 존재하는 것으로 판단하고 console을 때려준다.

해당 조건에서 undefined가 나오게 되면 조인을 추가해주고 해당 룸을 조인했다는 메세지를 client와 server에 뿌려주는 로직을 적용했다.

다른 조건으로 테스트해보니 전혀 잘못짠 코드였다. 방이 있을 때 조인이 아예 안되게 되므로 2명이 방에 못들어 가는 현상이 발생했다.

여기서 해결해야할 문제

현재 채팅 접근 로직은 1개다.
chatlist => chat 으로 연결되는 로직인데, 여기서는 이미 생성된 채팅room 정보를 chatlist 에서 가지고 있기 때문에 chat 에 입장할 때 그 방을 찾아서 들어 갈 수 있었다.

장터기능을 구현하면서 추가될 사항은 새로운 채팅 접근을 뚫는 것이다.
1. seller와 user가 이미 채팅이 있는 경우, 채팅방을 찾아서 join 시켜줘야한다.

  1. seller와 user가 처음 채팅을 하는 경우, 위를 확인 후 없을 때 새로운 채팅방을 만들어서 join해줘야한다. 이 때 처음 메세지가 보내지지 않을 때는 채팅방을 개설할 필요가 없다.

어떻게 구현할지 고민해봐야겠다.

진짜 해결한 join 문제

 so.on('joinRoom', (obj: any, callback: any) => {
        if (!obj) callback('join에서 에러가 발생했어요.');
        const { roomId, socketId } = obj;
        const isRoom = this.io.sockets.adapter.rooms.get(roomId)?.size ?? 1;
        //typeScirpt 때문에 undefind 를 안주기 위해 널병합 연산자로 1을 할당한다. 

        console.log(
          '채팅방 확인>>>>>>>>>>',
          this.io.sockets.adapter.rooms.get(roomId)?.has(socketId)
        );
        // console.log(this.io.sockets.adapter.rooms);
        // console.log('위에서 찍히나요', isRoom);

   
   		// isRoom 이 있을 때, 2보다 작을 때, 내가 그 방에 없을 때 
        if (
          isRoom &&
          isRoom < 2 &&
          !this.io.sockets.adapter.rooms.get(roomId)?.has(socketId)
        ) {
          so.join(roomId);
          callback(roomId);
          console.log('room이 연결됨 :>>', roomId);
        } else {
          console.log(
            socketId + 'tried to join roomNumber : ' + roomId + ' already join'
          );

          // Socket.join is not executed, hence the room not created.
        }
      });
   const isRoom = this.io.sockets.adapter.rooms.get(roomId)?.size ?? 1;

if (isRoom && isRoom < 2 && !this.io.sockets.adapter.rooms.get(roomId)?.has(socketId)
)
isRoom 이 있을 때, 2보다 작을 때, 내가 그 방에 없을 때

해당 조건을 주어 join 중복을 제거했다.

chatlist > chat
MarketDetail > chat
다른 경로로 입장 시에도 Join이 잘 작동하게 했다. (중복없이)

참고자료

profile
트렌디 풀스택 개발자

0개의 댓글