[웹] 기본 엔진 소스 설명 > 메시지 브릿지

본문 바로가기

사이트 내 전체검색

뒤로가기 메시지 브릿지


매뉴얼 [웹] 기본 엔진 소스 설명

본문

WebSocket 엔진(상단 핵심 로직) 설명서


※ 이 영역은 전체 시스템의 핵심 엔진이므로 가능한 수정 없이 그대로 사용하는 것을 권장합니다.

※ 필요한 기능 확장은 하단 UI 영역 또는 별도 함수에서 처리하는 것을 권장합니다.


1. 기본 정보 설정

const domain = window.location.hostname;
const MB_ID = "아이디";
const USER_IP = "ip주소(해당 언어 참고)"
const myKey = MB_ID || USER_IP;


domain
현재 접속한 웹사이트의 도메인 이름.
WebSocket 서버로 전송되는 group 파라미터로 사용되며, 같은 도메인끼리만 접속자 그룹을 분리하여 관리하는 역할을 한다.


MB_ID
로그인된 사용자의 회원 ID.
비회원이라면 빈 문자("") 또는 null.

USER_IP
비회원 사용자를 구분하기 위한 식별자.

myKey
현재 사용자를 대표하는 고유 키.
우선순위: 회원 → MB_ID
비회원 → USER_IP

이 값은 이후 접속자 Map에서 Key로 사용되며, 접속자 중복 여부 판단 등에 매우 중요하므로 수정하지 않을 것을 권장.


2. 접속 사용자 목록 저장소

let ws;
let activeUsers = new Map(); // {key: data}
let userListString = "";


ws
WebSocket 인스턴스를 저장하는 변수.
재연결 또는 종료 시 동일 객체에 접근하기 위해 전역으로 선언.

activeUsers (Map 구조)
현재 WebSocket 그룹에 접속 중인 사용자 목록을 저장하는 저장소.
key: 사용자 식별자(MB_ID 또는 IP)
value: 사용자 정보 객체(접속시간, id, ip 등 서버에서 내려주는 데이터)

userListString
화면에 보여주기 좋은 문자열 형태(“아이디1 | 아이디2 | …”)로 만든 접속자 목록.
activeUsers와 userListString은 실시간 UI 업데이트에 활용되므로 구조 변경은 권장하지 않음.


3. WebSocket 연결 함수

function connectWebSocket() {
    ws = new WebSocket('wss://designonex.com:14147/?group=' + encodeURIComponent(domain));


WebSocket 서버에 연결하는 엔트리 포인트.
접속 시 group=도메인 값을 전달하여 도메인 단위의 접속자 방(그룹)을 자동으로 형성한다.
이 그룹 구조는 서버 설계에 맞춰져 있으므로 반드시 수정 없이 유지해야 한다.


4. WebSocket 이벤트 처리


4-1. 접속 성공 (onopen)

ws.onopen = () => {
    sendEvent({type: "join", mb_id: MB_ID, ip: USER_IP});
};


서버에 “현재 사용자가 접속했다”는 정보를 전송.

서버는 이를 기반으로 전체 접속자 목록을 갱신한다.


4-2. 서버 메시지 수신 (onmessage)

ws.onmessage = (event) => {
    let data;
    try { data = JSON.parse(event.data); } catch { return; }


서버에서 받은 데이터를 JSON 형태로 변환한다.
이후 메시지 타입에 따라 다른 동작을 수행한다.


메시지 타입별 처리
① init — 전체 접속자 목록을 최초로 받는 경우

if (data.type === "init") {
    activeUsers.clear();
    for (const u of data.users) {
        const key = u.mb_id || u.ip || Math.random();
        activeUsers.set(key, u);
    }
}


서버가 현재 유지하고 있는 전체 접속 사용자 목록을 배열로 전달함.
Map을 초기화한 뒤 새 목록을 모두 다시 저장.


② join / update — 누군가 접속했거나 정보가 갱신된 경우

else if (data.type === "join" || data.type === "update") {
    const key = data.mb_id || data.ip || Math.random();
    activeUsers.set(key, data);
}

 

새 사용자가 들어온 경우(join)
닉네임‧멤버 정보가 바뀐 경우(update)
activeUsers에 새로운 정보로 갱신함.


③ leave — 특정 사용자가 나간 경우

else if (data.type === "leave") {
    const key = data.mb_id || data.ip;
    activeUsers.delete(key);
}


후처리

rebuildUserString();

if (typeof updateDisplay === "function") {
    updateDisplay(activeUsers, data); 
}


userListString을 다시 구성
UI가 필요할 경우 개발자가 직접 만든 updateDisplay()에 데이터를 전달
updateDisplay는 “하단 사용 UI 로직”에서 작성하는 것을 권장.
상단 엔진은 건드리지 않고 UI만 교체할 수 있는 구조로 설계됨.


4-3. 연결 종료 (onclose)

ws.onclose = () => {
    setTimeout(connectWebSocket, 2000); // 자동 재연결
};


WebSocket이 끊기면 2초 후 자동 재연결을 시도.
서버 재부팅·네트워크 불안정 상황에서도 지속 접속을 보장.


4-4. 오류 발생 (onerror)

ws.onerror = () => {
    ws.close();
};


오류 발생 시 즉시 연결을 닫고, onclose → 자동 재연결 동작이 이어짐.


5. 메시지 전송 함수

function sendEvent(jsonData) {
    if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify(jsonData));
    }
}


WebSocket이 정상적으로 열려 있는 경우에만 JSON 데이터 전송.
서버로 join/update/chat 등 다양한 이벤트 메시지를 보낼 때 사용한다.
엔진에서 사용하는 기본 전송 구조이므로 변경하지 않는 것을 권장한다.


6. 안전한 종료 처리 (gracefulDisconnect)

function gracefulDisconnect() {
    const leaveData = {type: "leave", mb_id: MB_ID, ip: USER_IP};


6-1. WebSocket으로 정상 종료 신호 보내기

if (ws && ws.readyState === WebSocket.OPEN) {
    try {
        ws.send(JSON.stringify(leaveData));
        ws.close();
    } catch(e) {}
}


6-2. 모바일 대비 sendBeacon 보조 전송

try {
    const blob = new Blob([JSON.stringify(leaveData)], {type: "application/json"});
    navigator.sendBeacon("/ws-leave", blob);
} catch(e) {}


모바일 브라우저는 탭 닫힘 시 WS 전송이 실패하는 문제가 빈번하기 때문에 sendBeacon으로 백엔드에 안전하게 한 번 더 전송하여 접속자 정보가 꼬이지 않도록 보완한다.

이 종료 처리 구조는 필수 요소이며, 엔진의 안정성을 위해 그대로 유지하는 것을 강력 권장.


7. 브라우저 종료·백그라운드 이동 감지

window.addEventListener("beforeunload", gracefulDisconnect);
window.addEventListener("pagehide", gracefulDisconnect);

document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "hidden") {
        gracefulDisconnect();
    }
});


PC와 모바일에서 브라우저가 페이지를 떠나는 다양한 상황을 모두 감지하여, 접속 종료 정보가 빠짐없이 서버에 전달되도록 설계됨.


8. 접속자 문자열 생성

function rebuildUserString() {
    userListString = Array.from(activeUsers.values())
        .map(u => u.mb_id || u.ip)
        .join(" | ");
}


activeUsers(Map)을 기반으로 “아이디 | 아이디 | IP…” 형태의 문자열을 구성.
UI에서 접속자 리스트를 간단히 표현할 때 활용.


9. 최초 실행

connectWebSocket();


페이지 로드와 동시에 WebSocket 연결을 시작함.



기본엔진 전체소스

const domain = window.location.hostname;
const MB_ID = "아이디";
const USER_IP = "ip주소(해당 언어 참고)"
const myKey = MB_ID || USER_IP;

let ws;
let activeUsers = new Map(); // {key: data}
let userListString = "";

// ---------------------------
// 웹소켓 연결
// ---------------------------
function connectWebSocket() {
    ws = new WebSocket('wss://designonex.com:14147/?group=' + encodeURIComponent(domain));

    ws.onopen = () => {
        sendEvent({type: "join", mb_id: MB_ID, ip: USER_IP});
    };

    ws.onmessage = (event) => {
        let data;
        try { data = JSON.parse(event.data); } catch { return; }

        // 초기 접속자 목록
        if (data.type === "init") {
            activeUsers.clear();
            for (const u of data.users) {
                const key = u.mb_id || u.ip || Math.random();
                activeUsers.set(key, u);
            }
        }
        // 접속 또는 업데이트
        else if (data.type === "join" || data.type === "update") {
            const key = data.mb_id || data.ip || Math.random();
            activeUsers.set(key, data);
        }
        // 접속 종료
        else if (data.type === "leave") {
            const key = data.mb_id || data.ip;
            activeUsers.delete(key);
        }

        rebuildUserString();

		if (typeof updateDisplay === "function") {
			updateDisplay(activeUsers, data); 
		}
    };

    ws.onclose = () => {
        setTimeout(connectWebSocket, 2000); // 자동 재연결
    };

    ws.onerror = () => {
        ws.close();
    };
}

// ---------------------------
// 메시지 전송
// ---------------------------
function sendEvent(jsonData) {
    if (ws && ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify(jsonData));
    }
}

// ---------------------------
// 안전한 종료 처리
// ---------------------------
function gracefulDisconnect() {
    const leaveData = {type: "leave", mb_id: MB_ID, ip: USER_IP};

    // 1️⃣ WebSocket으로 전송 (가능하면)
    if (ws && ws.readyState === WebSocket.OPEN) {
        try {
            ws.send(JSON.stringify(leaveData));
            ws.close();
        } catch(e) {}
    }

    // 2️⃣ 모바일 대비 — sendBeacon으로 보조 전송
    try {
        const blob = new Blob([JSON.stringify(leaveData)], {type: "application/json"});
        navigator.sendBeacon("/ws-leave", blob);
    } catch(e) {}
}

// ---------------------------
// 브라우저 이벤트 감지
// ---------------------------

// PC 및 일부 모바일 브라우저
window.addEventListener("beforeunload", gracefulDisconnect);

// iOS / Android에서 동작 안정적인 이벤트
window.addEventListener("pagehide", gracefulDisconnect);

// 탭 비활성화 시 (백그라운드 이동 등)
document.addEventListener("visibilitychange", () => {
    if (document.visibilityState === "hidden") {
        gracefulDisconnect();
    }
});

// ---------------------------
// 문자열 갱신
// ---------------------------
function rebuildUserString() {
    userListString = Array.from(activeUsers.values())
        .map(u => u.mb_id || u.ip)
        .join(" | ");
}


// ---------------------------
// 최초 실행
// ---------------------------
connectWebSocket();

</script>


<script>
function updateDisplay(activeUsers, lastMessage) {
    // 접속자 수 표시
    $("#userConCount").text("(" + activeUsers.size + ")");

	// 사이드 패널에 접속자 리스트 표시
    const userListString = Array.from(activeUsers.values())
        .map(u => {
            // mb_id가 null, undefined, 빈 문자열이 아니면 mb_id 표시
            if (u.mb_id && u.mb_id.trim() !== "") {
                return u.mb_id;
            }
            return u.ip; // 회원이 아니면 IP 표시
        })
        .join(" | ");
    $("#sidePanelContent").text(userListString);

}

 

댓글목록 0

등록된 댓글이 없습니다.

전체 19건 1 페이지
게시물 검색


Copyright © 소유하신 도메인. All rights reserved.

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

PC 버전으로 보기