TIL websocket 활용하기

ESH'S VELOG·2023년 7월 5일
0

websocket을 활용하여 호텔사이트같이 어떤 정보를 웹페이지에 접속한 모든 사용자에게 '이 방을 n명이 보고있습니다' 같은 기능을 구현해보려 한다.

구현 목표
1) 클라이언트가 로그인상태에서 상품 상세페이지로 들어가면 CHANGED_PAGE 이벤트를 서버로 전송

2) 보내는 데이터는 현재 접속중인 경로를 문자열로 전송

3) 프론트엔드로 클라이언트 두 명 이상이 같은 제품 상세페이지를 보고있다면 그 페이지에서 '총 n명이 이 상품을 구경하고 있습니다!'라는 문구를 전송

나의 생각
1) 클라이언트가 서버로 보내는 거니까 sock.on을 사용

2) sock.on에서 상세페이지의 url을 전송

3) io.emit으로 전달받은 정보를 가공하여 그 페이지를 보는 모든 사용자에게 전송

문제)) 같은 페이지를 보는 클라이언트의 숫자를 어떻게 보내야 하는지???

우선 답을 봤다.

const socketIdMap = {};

function emitSamePageViewerCount() {
  const urls = Object.values(socketIdMap);
  const countByUrl = urls.reduce((value, url) => {
    if (!url) return value;
    return {
      ...value,
      [url]: value[url] ? value[url] + 1 : 1,
    };
  }, {});

  for (const [socketId, url] of Object.entries(socketIdMap)) {
    const count = countByUrl[url];
    io.to(socketId).emit('SAME_PAGE_VIEWER_COUNT', count);
  }
}

코드를 보니 내가 익숙하지 않은 메서드들과 표현 방식이 보였다.
모르는 메서드 : reduce, entries, 삼항 연산자

  • reduce
    reduce는 map, forEach와 비슷하게 배열의 요소들을 순회하면서 반복한다.
    사용방법:
    reduce((초기(누산값)), 현재요소값, 현재요소의 index, 현재배열) => {return 다음 누산값;}, 초기누산값);

내가 이해한 것
초기값: 말 그대로 초기 값, 배열을 돌기 전 준비되어 있는 값이다. 현재 요소값에 그 다음 파라미터들과 앞으로 나올 return에 처리를 했을 때 다시 돌아와서 이 곳에 값이 누적된다.
현재요소 값: 배열을 도는 현재 값, 아래와 같은 num의 배열이 다 돈다고 했을 때 0번째는 1 이런식.
let num= [1, 2, 3, 4, 5]
현재요소의 index: 현재 요소의 자리(위치)
현재배열: 생략이 가능하다.

다시 기본을 써보자면 이렇다.
num.reduce((초기값, 현재요소값, 현재요소 index) => {return 다음 누산값에 처리할 무언가}, 초기 누산값)

여기서 사용한 reduce를 살펴보자
1) socketIdMap은 빈 배열이다.
2) emitSamePageVierwerCount 함수를 정의하고
3) socketIdMap에 있는 값들을 받아와 배열로 할당하는 urls을 선언한다.
4) reduce 사용! reduce는 배열을 반환하는 거니까 배열로 받아오는 3번이 필요한 것이다. 반환해서 받아온 값들을 countByUrl이라는 변수에 할당한다.
4-1) urls을 배열로 돌려서 value를 초기값으로 두고, url하나하나를 현재요소로 둔다.
4-2) 만약 url이 없다면 값을 반환하고 : 이 받아온 url이 없다면 변수를 그대로 유지하기 위함같다.
4-3) value를 spread하여 새로 객체를 만든다.
4-4) 삼항 연산자를 사용하여 url이 누적된 url과 같은 url값을 가진다면 +1 아니면 1을 반환하고 초기 누산값에 계속 누적하여 가진다. => 즉 접속한 클라이언트의 수를 세는 함수인 것이다.
5) 반복문을 적용
5-1) socketIdMap을 entries함수로 객체의 속성들을 [key,value]형태로 반환
만약 아래의 형태로 socketIdMap이 들어있다면
한 번 더 아래의 형태로 반환되게 되는 메서드이다!

const socketIdMap = {
	socketId1: 'url1',
    socketId2: 'url2',
    socketId3: 'url3',
}

Object.entries(socketIdMap)
console.log(Object.entries(socketIdMap)
=> [
	['socketId1', 'url1'],
    ['socketId2', 'url2'],
    ['socketId3', 'url3']
]

5-2) 마지막으로 count라는 변수에는 reduce로 돌렸던 값을 반복변수 url을 받아 해당하는 socketId들에게 'SAME_PAGE_VIEWER_COUNT'라는 이벤트와 count를 담아 보낸다.

프론트엔드에서는 다음과 같은 함수가 미리 작성되어 있었다.

$(document).ready(function () {
        initAuthenticatePage();
        bindSamePageViewerCountEvent(function (count) {
          // NOTE: 나 포함인 값이라 항상 1명 초과일때만 메세지를 보여준다.
          if (count > 1) {
            $("#pageViewerCount").html(
              `총 <b>${count}</b>명이 이 상품을 구경하고 있습니다!`
            );
          } else {
            $("#pageViewerCount").html("");
          }
        });
        getGoodsDetail(goodsId, function (g) {
          goods = g;
          renderItem(Number($("#numberSelect").val()));
        });

        $("#numberSelect").on("change", function () {
          renderItem(Number($(this).val()));
        });
      });

그런데 또 의문이 든다.
websocket은 실시간 웹서비스를 제공하기 때문에 현재 접속해 있는 클라이언트에만 반응하는 것이 아닌가?
지금 작성된 코드로는 총 방문자수와 비슷한 기능을 나타내고 있다. 내가 크롬 여러페이지를 열었을 때 같은 상세페이지를 열때마다 총 n명이 이 상품을 구경하고 있다고 뜨는데 웹페이지를 종료하여도 계속 증가하는 것만 볼 수 있다.. 이 의문을 풀어보기 위해 공부를 더 해봐야할 것 같다.

방법을 찾았다!
내가 disconnect 방식을 넣지 않은것이다.
마지막에 socket의 클라이언트가 종료하였을 때 실행되는 방식은 다음과 같다.

  // 현재 접속한 Socket 클라이언트가 종료하였을 때, 실행된다.
  sock.on('disconnect', () => {
    delete socketIdMap[sock.id];
    emitSamePageViewerCount();
  });

마지막에 위의 코드를 넣어주면 더 이상 웹페이지에 있지 않는 클라이언의 socket아이디는 삭제된다.

profile
Backend Developer - Typescript, Javascript 를 공부합니다.

0개의 댓글