[WIL] 20241214 WIL

Jaeyoung Ko·2024년 12월 13일
0

이번 주간은 Socket.IO 라이브러리를 통한 웹 소켓 통신을 이용해 프론트엔드와 백엔드 기능 구현을 하는 프로젝트 진행하는 것이 주를 이루었다.


이미 만들어진 스켈레톤 코드에 대해서 웹 소켓을 이용한 로직 구현을 해야 하기 때문에 기존 스켈레톤 코드에 대한 이해하는 데에 시간을 할애하는 부분이 챌린지였다.


물론 간단하게 이해하고 채팅이나 점수 기록같은 기능은 이미 개발을 시작했으나, recap하는 차원에서 주요 코드 스페이스의 주요한 부분에 대해서 정리해보고자 한다.



사실, 다른 폴더의 소스들,

즉 init, models, utils 폴더 내용이나 기타 메인 진입점인 app.js에 대해서는 워낙에 담당하는 부분이 자명하기 때문에 따로 설명하지 않아도 좋을 것 같다.

결국, 가장 이해하는 데에 있어서 중추적인 역할은 handlers 폴더라고 생각한다.

일단 통신 순서를 생각해서 써보고자 한다.



(1) 클라이언트 단에서의 요청

클라이언트단 Socket.js

// 이벤트 전송 함수
const sendEvent = (handlerId, payload) => {
  	// 로그 추가
    console.log("sendEvent called with:", handlerId, payload); 
    socket.emit('event', {
        userId,
        clientVersion: CLIENT_VERSION,
        handlerId,
        payload,
    });
};

export { sendEvent };

클라이언트 단에서는 Socket.js에서 다음과 같이 구현한 전송 함수를 통해 서버에 이벤트를 전송하여 처리를 요청한다. 이를 테면,

// 채팅 입력 후 누를 시에 전송
sendButton.addEventListener('click', () => {
  const msg = messageInput.value;
  console.log("send clicked : " + msg);
  if (msg) {
    sendEvent(5, { message: msg });
    messageInput.value = '';
  }
});

위의 코드는 내가 구현한 채팅 제출 버튼에 등록된 이벤트 리스너이다.
sendEvent(5, { message: msg });
를 통해 서버에 5번의 핸들러 ID로 payload로서는 문자열 메시지를 담아 전송하는 것이다.



서버 단 helper.js

//====================================================================================================================
//====================================================================================================================
// src/handlers/helper.js
// 컨텐츠 외 이벤트 핸들러
//====================================================================================================================
//====================================================================================================================

..

export const handleEvent = (io, socket, data) => {

    // 서버에 저장된 클라이언트 배열에서 메세지로 받은 clientVersion을 확인합니다.
    if (!CLIENT_VERSION.includes(data.clientVersion)) {
        // 만약 일치하는 버전이 없다면 response 이벤트로 fail 결과를 전송합니다.
        socket.emit('response', { status: 'fail', message: 'Client version mismatch' });
        return;
    }

    // 핸들러id에 따라 핸들러 매핑 과정
    const handler = handlerMappings[data.handlerId];
    if (!handler) {
        socket.emit('response', { status: 'fail', message: 'Handler not found' });
        return;
    }
    // 적절한 핸들러에 userID 와 payload를 전달하고 결과를 받습니다.
    const response = handler(data.userId, data.payload);
    // 만약 결과에 broadcast (모든 유저에게 전달)이 있다면 broadcast 합니다.
    if (response.broadcast) {
        io.emit('response', response);
        return;
    }
    // 해당 유저에게 적절한 response를 전달합니다.
    socket.emit('response', response);
};

이 소스코드에서 handleEvent 함수는 핸들러 아이디에 따라 적절한 핸들러를 매핑시켜
해당 이벤트에 대해 적절한 처리를 하도록 일종의 라우팅을 하는 역할을 맡는다.

이 중 주요한 건, 브로드캐스팅 여부에 따라,

브로드캐스트일 경우

   if (response.broadcast) {
       io.emit('response', response);
       return;
   }

io.emit 메서드를 통해 접속된 모든 클라이언트들에게 전송을 해주는 반면,
그렇지 않을 경우에는 단순 요청에 대한 응답을 보내기 위해

    socket.emit('response', response);

를 사용한다.



handlerMapping.js

//====================================================================================================================
//====================================================================================================================
// src/handler/handlerMapping.js
// 핸들러 매핑
//====================================================================================================================
//====================================================================================================================

import { moveStageHandler } from './stage.handler.js';
import { gameEnd, gameStart } from './game.handler.js';
import { itemGained } from './item.handler.js';
import { chatReceived } from './chat.handler.js';

const handlerMappings = {
   2: gameStart,
   3: gameEnd,
   4: itemGained,
   5: chatReceived,
   11: moveStageHandler,
};

export default handlerMappings;

이것은 앞선 helper에서 이벤트 종류에 따라 적절한 핸들러를 대응시키기 위한 mapper 역할이다. 마치, 클라이언트에서 switch - case문으로 분기하여 연결해주는 방식으로 이해하면 편할 것 같다.

위에서 예시를 든 것은 5번 아이디이므로 chatReceived를 받는다면..

서버 단 chat.handler.js

//====================================================================================================================
//====================================================================================================================
// src/handler/chat.handler.js
// 아이템 핸들러
//====================================================================================================================
//====================================================================================================================

import { getGameAssets } from '../init/assets.js';

export const chatReceived = (userId, payload) => {
    console.log("Payload received in chatReceived:", payload);
    if (!payload || !payload.message) {
        console.error('Invalid payload received:', payload);
        return { status: 'fail', message: 'Invalid payload' };
    }
    const message = payload.message;
    // 채팅 메시지를 모든 클라이언트에게 브로드캐스트
    return { status: 'success', message: `${userId} : ${message}`, broadcast: true };
};

다음과 같이 chatReceived 함수를 통해 전송이 올바른지 판단하는 과정을 먼저 거친다. 채팅의 경우, 결국 입력한 목표 내용이 서버 단을 통해서 모든 유저가 볼 수 있도록 브로드캐스트해야하므로, 추가적인 브로드캐스트 과정을 거치기 위해 다음과 같은 형태로 반환시켰다.

위에서 설명한 helper.js에서 이제 브로드캐스트 메시지임을 확인하고

   if (response.broadcast) {
       io.emit('response', response);
       return;
   }

을 통해 모두에게 쏴주면,

클라이언트단 Socket.js

// response 이벤트 수신 및 broadcast 여부 확인
socket.on('response', (data) => {
   if (data.broadcast) {
       // 브로드캐스트 시 채팅에 입력
       console.log("This response was broadcasted.");
       const item = document.createElement('div');
       item.textContent = data.message;
       messages.appendChild(item);
       messages.scrollTop = messages.scrollHeight;
   } else {
       console.log("This response was not broadcasted.");
   }
});

response로 확인된 이벤트이므로 저 부분에서 처리되어 다음의 과정을 거쳐서 응답을 수신한다.



기본적으로 이런 방식의 동작을 이해하고 나머지 부분도 간단히 만들면 된다고 생각한다.

profile
안녕하세요, 고재영입니다. 언제나 즐겁게 살려고 노력합니다.

0개의 댓글