메시지 브릿지

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

페이지 정보

본문

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);

}

 

댓글목록

등록된 댓글이 없습니다.

메시지 브릿지

Total 7건 1 페이지
  • RSS

[웹] 기본 엔진 소스 설명 (v1.4.1 보안 패치 적용) N새글

서버 보안 패치 v1.4.1이 적용됨에 따라, 기본 엔진 소스 역시 서버 업데이트 정책과 동일한 기준으로 수정되었습니다. 당초 엔진 소스의 변경 폭이 크지 않을 것으로 예상했으나, 보안 정책 강화와 시스템 안정성 향상을 위해 서버와 동일한 변경 사항이 반영되었습니다. 향후에도 보안 패치 적용 시 변..

작성자: 관리자 댓글 0 조회 8

[웹] 서버에서 받은 소스 설명

updateDisplay() 내 postData의 역할 & 활용 설명updateDisplay() 안에서 만들어지는 postData는서버(WebSocket)에서 받은 메시지를 프론트에서 활용하기 좋은 ‘가공용 데이터 꾸러미’로 만들어둔 것입니다.const postData = { type..

작성자: 관리자 댓글 0 조회 208

[웹] WebSocket 서버 전송 소스 설명

아래 코드는 실시간 알림 게시판에 적용되는 예제 소스입니다.WebSocket 연결 상태를 자동으로 확인하여, 알림 데이터를 서버에 안전하게 전송하는 역할을 합니다.이 코드를 정상적으로 동작시키기 위해서는 반드시 페이지 상단에 WebSocket 엔진 소스(WS 초기화, sendEvent, connect..

작성자: 관리자 댓글 0 조회 210

열람중 [웹] 기본 엔진 소스 설명

WebSocket 엔진(상단 핵심 로직) 설명서※ 이 영역은 전체 시스템의 핵심 엔진이므로 가능한 수정 없이 그대로 사용하는 것을 권장합니다.※ 필요한 기능 확장은 하단 UI 영역 또는 별도 함수에서 처리하는 것을 권장합니다.1. 기본 정보 설정const domain = window.location...

작성자: 관리자 댓글 0 조회 200

웹은 어떻게 연결해야 하나요?

브라우저(웹)에서 메시지 브릿지 서버에 연결하는 방법은 아주 간단합니다.딱 한 줄로 WebSocket을 만들고, 이벤트만 달아주면 됩니다.const ws = new WebSocket("wss://designonex.com:14147/?group=도메인"); 꼭 알아야 하는 핵심 3가지1.&nbs..

작성자: 관리자 댓글 0 조회 1156

검색


© 2025 Designonex. All rights reserved. · 이용약관 · 개인정보 처리방침