퍼즐 플레이룸 입장 및 퇴장 시나리오 및 구현

  1. 사용자는 퍼즐 목록(메인페이지 또는 퍼즐저장소)에서 퍼즐을 클릭한다.
  2. 서버에게 랜덤주소를 요청하여 받아온다. 같은 그림으로 퍼즐을 맞춰도 채팅 및 플레이는 구별되어야 하기 때문에 퍼즐 플레이룸 주소를 room/:puzzleID/랜덤주소 로 구성했다.
    1. 서버는 기존의 랜덤주소와 중복되지 않는 랜덤 문자열을 만들어 보내준다.
    2. 서버는 랜덤주소들을 roomURL 이란 집합에 저장한다.
    3. 랜덤주소는 socket.join(랜덤주소) 에 쓰인다. 그럼 특정 주소 내에서만 원하는 통신을 할 수 있다.
  3. room/:puzzleID/랜덤주소로 들어간다. 이 주소로 들어오는 사람들은 서로 채팅을 할 수 있다.
    1. 소켓연결

      1. useState로 소켓 다루기: 불필요한 리렌더링이 생겼다. 또한 useEffect로 unmount 관리할 때 socket 값이 null이 된다. 해결: context로 소켓을 다룬다.

      Untitled

      //socket.tsx
      export const socket = io(`${process.env.REACT_APP_ROOT_URL}`);
      export const SocketContext = React.createContext(socket);
      
      //play-puzzle/index.tsx
      import { SocketContext, socket } from "@src/context/socket";
      useEffect(() => {
          setPuzzle(); //puzzle 정보 fetch
          socket.emit("joinRoom", { roomID: roomID });
          return () => {
            socket.emit("leaveRoom", { roomID: roomID });
          };
      }, []);
      
      return (
      <SocketContext.Provider value={socket}>
      <Chat chatVisible={chatVisible} roomID={roomID} />
      </SocketContext.Provider>
      )
      
      //chat.tsx
      import { SocketContext } from "@src/context/socket";
      const Chat = (props: any) => {
        const { roomID, chatVisible } = props;
        const socket = useContext(SocketContext);
      ...
      }
      
    2. 방 퇴장

      1. 정상적으로 방을 나갈 때 useEffect에서 cleanup한다. leaveRoom이란 메시지를 받은 서버는 socket.leave('해당랜덤주소') 한다. 해당랜덤주소 방의 클라이언트가 삭제된다.
      2. 정상적으로 방을 나가지 않을 때 socket js 자체에서 disconnect를 한다. 소켓은 disconnect를 할 때 leave를 먼저 하고 나간다고 한다. 그래서 마찬가지로 해당랜덤주소 방의 클라이언트가 삭제된다.
      3. setInterval 로 10분마다 io.sockets.adapter.rooms.get(roomURL 집합의 원소들) 로 client 수를 파악한다. client 수가 0인 랜덤 주소는 roomURL 집합에서 삭제된다.
      4. 🤔처음 랜덤 주소 관리방법을 고민할 땐 클라이언트 수를 직접 map이나 배열로 관리하려고 했으나 리렌더링, 새로고침, 창 강제 종료 등에서 클라이언트 증감을 관리하기 힘들었다. 그러던 중 소켓의 disconnect는 leave를 하고 수행된다는 점과, io.sockets.adapter.rooms.get()으로 클라이언트 수를 알 수 있다는 것을 알게 된 후 현재의 방법으로 랜덤주소 관리를 구현했다.
      import { clear } from 'console';
      import { roomURL } from './roomInfo';
      
      const updateRoomURL = (io: any) => {
        const cb = (io: any) => {
          let mySet = new Set<string>();
          const _roomURL = Array.from(roomURL);
          for (let item of _roomURL) {
            const clients = io.sockets.adapter.rooms.get(item);
            const numClients = clients ? clients.size : 0;
            if (numClients === 0) mySet.add(item);
          }
          mySet.forEach(item => roomURL.delete(item));
        };
        setInterval(cb, 60000, io);
      };
      
      export default (io: any) => {
        console.log('socket 연결!');
        io.on('connection', (socket: any) => {
          updateRoomURL(io);
          socket.on('joinRoom', (res: { roomID: string }) => {
            socket.join(res.roomID);
          });
          socket.on('message', (res: { roomID: string; message: object }) => {
            io.sockets.in(res.roomID).emit('message', res.message);
          });
          socket.on('leaveRoom', (res: { roomID: string }) => {
            socket.leave(res.roomID);
          });
          socket.on('disconnect', () => {});
        });
      };