SENTENCE U | Day 9 (socket.io)

블로그 이사 완료·2023년 1월 26일
0
post-thumbnail

유저 실시간 접속상태(feat. socketio)

socketio로 유저 접속 시 백에서 배열에 저장해놨다가 중복검사를 해서 돌려주는 코드를 작성했었다.

그 배열을 돌려주는 객체에서 토큰과 유저명을 가져와 로그인중인지 확인 하는 방법을 선택했지만, 그러려면 useQuery랑 같이 사용해야하고 그 과정이 너무 복잡했다.

socket.io만 사용해서 실시간으로 접속상태를 바꾸고 싶어서 소켓을 통해 데이터를 주고 받고 접속하면 온라인 상태인 것 까지는 성공했다.

하지만 브라우저를 종료할 경우 실시간으로 접속상태가 바뀌지도 않고 로그아웃이 되지도 않았다.

하루종일 너무 과몰입해서 머리가 조금 꽉 막힌 상태여서 조금 쉬었다가 다시 시도해봤다.

// 서버
const SocketIO = require('socket.io');

module.exports = (server, app) => {
  const io = new SocketIO.Server(server, {
    path: '/socket.io',
    cors: {
      origin: '*',
      methods: ['GET', 'POST'],
      credentials: true,
    },
  });

소켓 서버를 생성 할 때 cors문제가 생기지 않으려면 origin주소를 설정해야한다. 이건 물론 배포할때 URL을 다시 설정해야한다.

// 클라이언트
const BACK_URL = 'http://localhost:8000';

useEffect(() => {
  const socketIo = io.connect(`${BACK_URL}/online`, {
    path: '/socket.io',
    cors: { origin: '*', credentials: true },
    transports: ['websocket'],
  });
})

서버를 이렇게 생성해서 연결을 시켜놨다.

const onlineMap = {};
module.exports = (server, app) => {
  const io = new SocketIO.Server(server, {
    path: '/socket.io',
    cors: {
      origin: '*',
      methods: ['GET', 'POST'],
      credentials: true,
    },
  });

  app.set('io', io);
  app.set('onlineMap', onlineMap);

  io.of('/online').on('connection', (socket) => {
    const newNamespace = socket.nsp;

    // 온라인 유저 저장할 빈 객체 생성
    if (!onlineMap[socket.nsp.name]) {
      onlineMap[socket.nsp.name] = {};
    }

    // 첫 연결 시에도 온라인 유저리스트 보내기
    newNamespace.emit('onlineList', Object.values(onlineMap[socket.nsp.name]));

    // 클라이언트에 유저 정보 요청
    socket.emit('userConnect', socket.nsp.name);

    // 유저정보 받아서 객체에 저장
    socket.on('login', (data) => {
      onlineMap[socket.nsp.name][socket.id] = data.userName;
      // 클라이언트로 온라인 유저리스트 보내기
      newNamespace.emit('onlineList', Object.values(onlineMap[socket.nsp.name]));
    });
  });
};

접속한 유저와 소켓id를 객체로 저장하길 위해 빈 객체를 생성해놓고, 서버에 클라이언트가 접속 하면 유저에게 유저명을 요청한다.

// 클라이언트
setSocket(socketIo); // 소켓 연결되면 따로 socket에 다시 저장
    if (userloginStatus && userName) {
      socketIo?.on('userConnect', (data) => {
        socketIo?.emit('login', { userName: userName }); // 유저명 보냄
      });
    }

받은 유저명과 소켓id를 객체에 저장하고 온라인 유저리스트를 클라이언트에 돌려준다.

서버로 요청을 계속 보내지 않기 위해 한 번 연결이 되면 따로 state에 저장해 연결을 끊도록 해놨다.

useEffect(() => {
    // 소켓 한 번 연결 됐으면 연결 끊기
    return () => {
      if (socket) {
        socket.disconnect();
      }
    };
  }, [socket, userName]);
 

서버로부터 온라인 유저를 받으면 여러 유저명이 중복된 배열로 들어온다.

배열에서 중복된 유저명을 지우기 위해 filter와 indexOf메소드로 새로운 배열을 온라인 리스트에 저장했다.

useEffect(() => {
    // 서버에서 온라인 리스트 배열로 받음
    socket?.on('onlineList', (data) => {
      // 배열에서 중복요소 제거해서 새로운 배열 생성
      const userArray = data.filter((ele, i) => {
        return data.indexOf(ele) === i;
      });
      // 새로운 배열 온라인리스트 state에 저장
      setOnlineList(userArray);
    });
    return () => {
      socket?.off('onlineList');
    };
  }, [socket, onlineList]);

그리고 온라인 리스트를 보여주는 컴포넌트에 props로 보내서 렌더링 할 수 있게 한다.

<LiveUsers onlineList={onlineList} />

온라인리스트 props가 바뀔때마다 해당 컴포넌트만 리렌더링 되도록 useEffect의 뎁스에 onlineList 배열을 넣어줬다.

const LiveUsers = ({ onlineList }) => {
  useEffect(() => {}, [onlineList]);
  return (
    <>
      <Container>
        접속중인 유저
        {onlineList.map((user, i) => (
          <div key={i}>{user}</div>
        ))}
      </Container>
    </>
  );
};

유저가 접속종료하면 서버에서 클라이언트의 소켓id와 맞는 객체를 지워서 다시 돌려준다.

// 연결 종료 시 온라인 유저 객체 삭제
socket.on('disconnect', () => {
  delete onlineMap[socket.nsp.name][socket.id];
  newNamespace.emit('onlineList', Object.values(onlineMap[socket.nsp.name]));
});

성공! 새로운 유저가 접속하거나 종료할 때마다 유저리스트 컴포넌트가 새로 리렌더링 된다.

profile
https://kyledev.tistory.com/

0개의 댓글