Socket.io로 채팅기능 구현하기

조성철 (JoSworkS)·2020년 2월 21일
6

포럼(채팅기능) 개요

먼저, 본 프로젝트에서는 채팅기능을 자유로운 의견공유가 가능한 공간이라는 의미로 '포럼' 이라고 이름 지었다.

부동산을 다루는 빌리집의 특성상 유저 to 유저, 유저 to 호스트 간의 커뮤니케이션이 이루어질 수 있도록 하는 것은 중요한 포인트다. 그래서 Socket.io를 이용한 실시간 양방향 통신을 통해 채팅기능을 구현하도록 하였다.

포럼을 구현하는데 있어서 고려된 사항으로는 아래와 같다.
1. 유저가 하우스에 대한 궁금한 점을 해결할 수 있도록 호스트와 커뮤니케이션 할 수 있을 것
2. 하우스에 대해 유저와 유저간의 자유로운 의견공유가 가능할 것
3. 앱을 껐다 켜도 대화내용이 유지될 것
4. 자신이 참여한 포럼의 리스트를 확인할 수 있고, 다시 참여할 수 있을 것

포럼 아키텍쳐

포럼의 작동 로직은 아래와 같다.

  1. 포럼은 모든 유저가 호스트가 포스팅 한 하우스 상세 페이지를 통해 포럼에 참가할 수 있다.
  2. 포럼에 참가하면 유저는 채팅 스크린으로 이동하게 되고 메시지를 입력하면 해당 메시지가 Socket.io 서버로 전송된다.
  3. 메시지는 해당 소켓 서버에 접속되어 있는 모든 유저에게 보내지기 전에 앱을 껐다 켜도 지난 메시지를 확인할 수 있도록 DB에 업데이트 되는 과정을 거친 후 각 유저에게 보내지기 된다.
  4. 각 유저에게 보내진 메시지는 클라이언트 컴포넌트의 스테이트에 업데이트 된 후 메시지들을 렌더링하여 보여주게 된다.

구동 테스트

결론

Socket.io를 이용하여 실시간 양방향 커뮤니케이션 기능을 구축하는데 도전하였다. 처음 시도하는 것이었기 때문에 처음부터 끝까지 계속 시행착오를 거쳐가며 기능을 구축하였다.

처음에 가장 생소하게 느껴졌던 부분은 기존의 서버와 같이 api를 이용하여 요청/응답으로 통신이 이루어지는 형태가 아니라, socket.io 서버를 새롭게 구축하고 구축된 서버에서 통신이 이루어 진다는 점에서 개념적으로 이해가 됐지만 실제 기능을 구축해 가는데 있어서 혼란을 가져다 주었다.

또한 앱을 껐다 켜도 그 전의 메시지를 확인할 수 있도록 DB에 메시지 로그를 지속적으로 업데이트 하는 로직이 필요했는데, 이 과정에서 처음에는 서버의 api를 이용하여 쿼리로 DB에 메시지를 업데이트하는 방식으로 했었다. 하지만 이 방식을 이용했을 때, DB의 업데이트된 로그와 클라이언트의 스테이트에 업데이트 되는 로그의 차이로 인해 마지막으로 보낸 메시지가 렌더되지 않는 문제가 발생하였다.

이를 해결하기 위해, socket.io 서버가 구축된 부분에서 메시지를 받게 되면 먼저 DB에 업데이트 하고, 그 다음 연결되어 있는 소켓에게 메시지를 전달하는 방식으로 변경하여 DB와 소켓이 연결되어 있는 각 클라이언트의 스테이트를 일치시켜 렌더링 할 수 있었다.

아직 많은 부분에서 기초적인 수준에 불과한 로직이지만 '포럼' 이라는 용어를 사용하고 있는 만큼 처음 계획한 대로 단순히 채팅기능만 구현하는게 아닌 공지, 1:1채팅 등을 지원하는 복합 기능으로 개선해 나가고자 한다.

소스 코드

// socket.ts

// 중략

  socket.on(
    'chat',
    async (
      myId: string,
      userId: string,
      msg: string | number,
      name: string,
    ) => {
      const hostInfo = await getRepository(User).findOne({
        where: {
          id: userId,
        },
      });

      if (!hostInfo) {
        console.log('호스트 없음');
        return;
      }

      const log = await getRepository(Forum).findOne({
        where: {
          hostId: userId,
        },
      });

      if (!log) {
        console.log('포럼 없을 때 실행');
        const newForum = new Forum();
        newForum.hostId = Number(userId);
        newForum.forumLog = JSON.stringify([{ name, msg }]);
        newForum.isActive = true;
        await newForum.save();
        // }
      } else {
        console.log('포럼 있을 때 실행');

        const addedLog = JSON.parse(log.forumLog).concat([{ name, msg }]);

        await getConnection()
          .createQueryBuilder()
          .update(Forum)
          .set({ forumLog: JSON.stringify(addedLog) })
          .where('hostId = :hostId', { hostId: userId })
          .execute();
      }

      const joinForum = await getRepository(JoinForum).findOne({
        where: {
          userId: myId,
          hostId: userId,
        },
      });

      if (!joinForum) {
        console.log('조인포럼 없을 때 실행');
        const newJoinForum = new JoinForum();
        newJoinForum.userId = Number(myId);
        newJoinForum.hostId = Number(userId);
        newJoinForum.hostName = hostInfo.name;
        newJoinForum.isActive = true;
        await newJoinForum.save();
      } else {
        console.log('조인포럼 있을 때 실행');
      }

      io.to(userId).emit('chat', { name, msg });
    },
  );
});

3개의 댓글

comment-user-thumbnail
2020년 11월 15일

이런 갓ㅡ 글에 댓글이 없네요 잘보고갑니다!

1개의 답글
comment-user-thumbnail
2021년 4월 9일

혹시 DB는 어떤 DB 사용하셨나요?

답글 달기