Skip to content

실시간 채팅

hyelie edited this page Oct 28, 2022 · 10 revisions

읽기 전에

Socket.io와 room에 대해 알고 있어야 합니다. WebSocket & Socket.io 상세Socket.io 공식 문서를 참고해 주세요.


미어캣의 채팅 로직

실시간 채팅을 위해서는 다양한 경우를 고려해야 합니다. 단순하게 모든 사용자가 socket 접속이 되어 있을 경우만 생각한다면 socket 이벤트만 전송하면 되겠지만 실제로는 그렇지 않습니다.

누군가는 인터넷 연결 환경 문제로 인해 오프라인이었다가 온라인이 되는 경우도 있을 수 있고, 누군가는 나중에 채팅을 확인하기 위해 앱을 껐을 수도 있습니다. 지금부터 미어캣이 이 경우의 수들을 어떻게 처리했는지 알아보겠습니다.

미어캣은 실시간 채팅을 위해 socket.io를 사용합니다. 클라이언트에 2개의 socket 연결을 이용하는데, 하나는 실시간 메시징을 위해서이고 나머지 하나는 메시지 수신에 대한 알림을 위해서입니다. 편의상 실시간 메시징을 위한 socket 연결을 room socket, 메시지 수신에 대한 알림을 위한 socket 연결을 global socket이라 하겠습니다. global socket은 앱을 켰을 때부터 끌 때까지 계속 연결되며, room socket은 채팅방에 들어갈 때부터 나올 때까지 연결되어 있습니다.

참고로 모든 메시지에 종단간 암호화가 적용되어 있으며 클라이언트가 메시지를 송신했을 때 암호화하고, 수신했을 때 복호화합니다.


실시간 채팅

제일 먼저 클라이언트 간 실시간 채팅이 어떤 과정을 거치는지 알아보겠습니다.

  1. 클라이언트가 채팅방에 들어가면 room socket이 client:joinChatroom 이벤트를 emit합니다.
  2. 서버에서 client:joinChatroom 이벤트를 수신하면 해당 클라이언트를 room에 join시킵니다.
  3. 클라이언트가 메시지에 정보를 담아 client:speakMessage 이벤트를 emit합니다.
  4. 서버가 client:speakMessage 이벤트를 수신하면 해당 메시지를 DB에 넣고, message ID를 받아옵니다.
  5. 서버는 해당 이벤트가 발생한 room의 모든 socket에 server:hearMessage 이벤트를 emit합니다. 이 때 io.in([room ID]).emit()을 이용합니다.
  6. 클라이언트가 server:hearMessage 이벤트를 수신하면 메시지 발송자인 경우 창 오른쪽에, 메시지 발송자가 아닌 경우 창 왼쪽에 메시지를 렌더링합니다.
  7. 클라이언트가 채팅방에서 나갈 경우 room socket을 disconnect합니다.

그러나 이 경우는 채팅에 참여하는 모든 인원이 채팅 화면을 보고 있어야 합니다. 지금부터는 다른 경우를 알아보겠습니다.


앱을 처음 켰을 때

  1. 클라이언트의 global socket이 연결됩니다. 이후 자신이 속해있는 room list를 서버에 요청합니다.
  2. 서버는 해당 room list를 클라이언트에게 돌려줍니다.
  3. 클라이언트가 자신이 속한 모든 room에 접속시켜 달라고 서버에 요청합니다.
  4. 서버는 해당 클라이언트를 room에 접속시킵니다.

채팅방 목록 화면에서 메시지가 올 때

채팅방에 들어가 있지 않더라도 메시지가 왔을 때 알람을 보여주어야 합니다. 미어캣은 global socket을 이용해 이를 구현했습니다.

  1. 클라이언트가 앱을 켜는 시점에 global socket이 연결되면서 자신이 속해 있는 room list를 받습니다. 동시에 해당 room에서 사용자가 읽지 않은 메시지 개수도 받아와 채팅방 목록에서 자신이 속해있는 room list과 읽지 않은 메시지 개수를 렌더링합니다.
  2. 서버가 다른 클라이언트의 client:speakMessage 이벤트(메시지 발송 이벤트)를 수신하면 해당 room에 속해있는 모든 사용자에게 server:notificateMessage 이벤트를 emit합니다.
  3. 클라이언트가 server:notificateMessage 이벤트를 수신하면 해당 채팅방의 읽지 않은 메시지 개수를 더해 줍니다.

읽지 않은 메시지가 있는 채팅방에 들어갈 때

사용자가 채팅방 화면에 있을 때는 room socket의 이벤트를 수신해서 실시간으로 메시지를 전달받을 수 있지만, socket.io의 특성상 연결되지 않은 사용자에게는 메시지가 전달되지 않습니다. 미어캣은 사용자가 채팅방에 있지 않으면 room socket 연결을 끊어버리기 때문에 메시지 전달을 받을 수 없습니다.

그러나 편리한 채팅을 위해서는 읽지 않은 채팅도 확인할 수 있어야 합니다. 미어캣은 global socket으로 이를 핸들링했으며 해당 과정은 아래와 같습니다.

  1. 클라이언트가 채팅방에 들어가면 서버에 해당 채팅방에서 읽지 않은 메시지 목록을 요청합니다.
  2. 서버는 해당 클라이언트가 해당 채팅방에서 제일 최근에 읽은 메시지를 저장하고 있습니다. 그 메시지 이후에 온 메시지들을 전부 클라이언트에게 돌려줍니다.
  3. 읽지 않은 메시지를 수신한 클라이언트는 그 메시지들을 화면에 출력해 줍니다.

메시지를 읽은 사람/읽지 않은 사람 목록

앞에서 서버에 해당 클라이언트가 채팅방에서 제일 최근에 읽은 메시지를 저장하고 있다고 했습니다. message ID는 계속 오름차순으로 증가하는 자연수이기 때문에 이 값으로 채팅방에 있는 어떤 사람이 특정 메시지를 읽었는지, 읽지 않았는지 식별할 수 있습니다.

  1. 클라이언트가 "채팅방에 들어가거나(room socket 접속)", "채팅을 보내거나(client:speakMessage 이벤트 발생)", "채팅을 받는 event가 발생 시(server:hearMessage 이벤트 발생 시)" 서버에서 해당 사용자가 제일 최근에 읽은 메시지를 갱신합니다.
  2. 클라이언트가 어떤 메시지를 읽지 않은 사람 목록을 요청합니다.
  3. 서버에서 해당 채팅방에 있는 사용자들의 제일 최근에 읽은 메시지를 이용해 메시지를 읽은 사람, 읽지 않은 사람 목록을 돌려줍니다.
  4. 클라이언트가 해당 정보를 화면에 출력해 줍니다.

요약

클라이언트의 모든 채팅 로직은 실시간 온라인 일 경우에는 socket 이벤트를, 오프라인에서 온라인으로 되는 경우에는 API 요청을 합니다. 서버에서는 채팅과 관련된 socket 이벤트 수신과 동시에 DB에 저장합니다.

  • 앱을 처음 켜면 global socket이 연결되면서 속한 모든 채팅방과 읽지 않은 메시지 개수를 받아옵니다. 해당 채팅방에서 메시지가 오면 읽지 않은 메시지 개수를 늘립니다.
  • 클라이언트가 채팅방 진입 시 room socket이 연결되며, 동시에 해당 room에서 읽지 않은 메시지를 서버에 요청합니다. 이후 실시간 메시징은 room socket event로 처리합니다.
    • 클라이언트가 메시지를 전송하면 client:speakMessage 이벤트를 emit하고, 서버가 해당 이벤트를 수신하면 DB에 메시지를 저장합니다. 동시에 해당 room의 클라이언트에게 server:hearMessage 이벤트를 emit합니다.
    • 클라이언트가 server:hearMessage 이벤트를 수신하면 메시지를 렌더링하며, 해당 클라이언트가 제일 최근에 읽은 메시지를 갱신합니다.