양방향 통신은 소켓의 기본이다.
소켓은 서버와 클라이언트 간의 네트워크 연결을 가능하게 하는데, 이때 소켓ID라는 각 소켓의 고유 식별자를 사용한다. 소켓ID를 통해 클라이언트의 연결 상태를 구분하고, 관리하는 것이 중요하다.
소켓ID는 임시적이라, 세션 연결이 끊어지고 새로 연결이 될 때, 다시 발급 되는 것이라 이걸로 유저를 특정하기는 어렵다.
만약 유저를 특정하고 싶다면, UUID를 사용해야 한다.
여기서 UUID(Universally Unique Identifier)는, 서버에서 임의로 발급한 ID로, 전 세계적으로 고유한 ID를 생성하기 위한 표준 규격이다.
주로 DB, 네트워크 시스템, 파일 식별 등에서 고유한 값을 부여하고 싶을 때 사용되며, 거의 충돌 없이 고유성을 보장하기 때문에 서로 다른 시스템이나 서버에서 생성된 값들도 겹치지 않도록 설계되어 있다.
UUID는 128bit의 수를 16진수로 표현하며, 일반적으로 5개의 그룹으로 나뉘어져서 하이픈(-)으로 구분된다. 보통 32자리의 16진수로 이루어져 있다.
이번에 할 것은 소켓ID를 받아서 저장하는 과정을 해볼 것이다.
핸들러를 통한 로직 처리
먼저, 앞으로 모든 이벤트를 처리할 핸들러를 만들어야 한다.
그러기 위해 ‘src’ 폴더 내부에 ‘handlers’ 폴더를 만들고 ‘register.handler.js’ 파일을 생성한다.
// 이벤트 처리하는 핸들러
// io는 웹 소켓 객체
const registerHandler = (io) => {
// connection이라는 이벤트가 발생할 때까지 대기하겠다.
io.on('connection', (socket) => {
});
};
export default registerHandler;
그리고 해당 핸들러를 서버에 등록하기 위해 socket.js 에서 registerHandler 함수를 호출한다.
import { Server as SocketIO } from "socket.io";
import registerHandler from "../handlers/register.handler";
const initSocket = (server) => {
// 중략
registerHandler(io); // 클라이언트로부터 오는 이벤트를 처리할 핸들러를 서버에 등록
};
export default initSocket;
만약, 유저가 클라이언트를 통해 서버에 접속을 했을 경우, registerHandler()의 commection이라는 곳에서 이벤트가 호출된다.
유저 모델
일단, 현재 하고 있는 건 유저 관리이기 때문에 UUID도 생성해야하고, 소켓ID도 저장해야 한다.
기본적으로 DB에 저장을 하지만, 지금은 DB를 사용하지 않으므로 서버 메모리에만 올리기 위해 models 폴더의 user.model.js 파일을 생성한다.
DB를 사용하게 되면, 새로 등록한 사람들은 새로운 UUID를 발급하고, 해당 내용은 DB에 저장한 다음에, 두번째 이후부터 connection이라는 이벤트라 호출될 때, DB에서 맨 처음 한 번 검색을 한 다음에 새로 발급한다라는 로직이 추가될 수 있다.
const users =[];
// 서버 메모리에 유저의 세션(소켓ID) 저장
// 이때, 유저는 users에 객체 형태로 저장
// { uuid: string; socketId: string; };
export const addUser = (user) => {
users.push(user);
};
// 전체 유저 조회
export const getUsers = () => {
return users;
};
이젠 UUID를 서버에서 임의로 추가해야 한다.
이를 user.model.js에다 만들 수 있겠지만, 기능적으로 분리하기 위해 핸들러에서 처리할 것이다.
따라서 UUID를 사용하기 위해 먼저, UUID 라이브러리를 다운받아야 한다.
npm install uuid
다운이 완료되었으면 다음과 같이 register.handler.js를 수정한다.
// 이벤트 처리하는 핸들러
import {v4 as uuidv4} from 'uuid';
import {addUser} from '../models/user.model.js';
// io는 웹 소켓 객체
const registerHandler = (io) => {
// connection이라는 이벤트가 발생할 때까지 대기하겠다.
io.on('connection', (socket) => {
const userUUID = uuidv4(); // UUID 생성
addUser({uuid: userUUID, socketId: socket.id}); // 사용자 추가
});
};
export default registerHandler;
지금까지 유저가 접속했을 때, 유저를 추가하는 코드를 작성했다.
그런데 접속을 했다면, 해당 접속을 끊는 경우도 있다. 그렇게 되면 서버 메모리에서 유저를 삭제해야만 한다. 따라서 user.model.js 파일에 유저를 삭제하는 함수를 작성하면 다음과 같다.
// 접속 해제한 경우, 배열에서 유저 삭제
export const removeUser = (socket) => {
// 만약 index가 -1이 아니라면, users 배열에 없다는 뜻이기 때문에 삭제해야 한다.
const index = users.findIndex((user) => user.socket === socket);
if(index !== -1) {
return users.splice(index, 1)[0];
}
};
접속 해제 배열을 만들었다면, 이젠 disconnect 이벤트를 처리해줄 함수를 선언해야 한다.
그러기 위해 먼저 handlers 폴더 안에 helper.js 파일을 생성한다. 해당 파일은 앞으로 컨텐츠 외에 필수 이벤트 처리 핸들러가 선언될 것이다.
import {getUsers, removeUser} from '../models/user.model.js';
// uuid는 언젠가 사용할지도 모르니 일단 추가만 했다.
export const handleDisconnect = (socket, uuid) => {
removeUser(socket.id); // 사용자 삭제
console.log(`User disconnected: ${socket.id}`);
console.log(`Current users: ${getUsers()}`);
};
이후 해당 이벤트를 처리하기 위해 register.handler.js를 다음과 같이 수정한다.
// io는 웹 소켓 객체
const registerHandler = (io) => {
// connection이라는 이벤트가 발생할 때까지 대기하겠다.
io.on('connection', (socket) => {
// 중략
// 접속 해제 시 이벤트 처리
socket.on('disconnect', () => handleDisconnect(socket, userUUID));
});
};
여기서 잠깐!
io.on('connection')은 서버에 접속하는 모든 유저를 대상으로 일어나는 이벤트이고, socket.on()은 하나의 유저를 대상으로 한 이벤트가 처리된다.
'프로젝트' 카테고리의 다른 글
24/09/30 - [실습] 점핑 액션 게임(5): 클라이언트 연동, 트러블 슈팅 (0) | 2024.09.30 |
---|---|
24/09/30 - [실습] 점핑 액션 게임(4): 커넥션 핸들러, 이벤트 핸들러(클라이언트 버전 확인, 스테이지 이동, 점수 계산) (0) | 2024.09.30 |
24/09/27 - [실습] 점핑 액션 게임(2): 환경 설정, 데이터 테이블(feat. FS 모듈) (0) | 2024.09.27 |
24/09/26 - [실습] 점핑 액션 게임(1): 게임 기획 (0) | 2024.09.26 |
24/09/25 - [팀] 풋살 온라인(4): 최종 회고 (0) | 2024.09.25 |