프로그래밍/Js

웹워커 안에서 웹소켓 구동 시험 테스트 + Node 소켓 서버

소행성왕자 2021. 11. 9. 18:16

.개요

웹 브라우저 JavaScript 런타임은 기본적으로 단일 스레드 환경입니다.

그러나 HTML 표준은 웹 브라우저가 메인 스레드와 백그라운드 스레드(워커 스레드)에서 JavaScript를 실행할 수 있도록 하는 웹 워커 기능을 도입하여 개발자가 웹 브라우저에서 다중 스레드 JavaScript 응용 프로그램을 구현할 수 있도록 합니다.

본 문서는 JavaScrip 웹 워커를 사용하여 WebSocket API 웹 애플리케이션을 구현하는 방법을 보여줍니다.

 

.웹소켓 개요

WebSocket 사양은 웹 페이지가 원격 호스트와의 양방향 통신을 위해 WebSockets 프로토콜을 사용할 수 있도록 하는 API를 정의합니다. WebSocket 인터페이스를 소개하고 웹을 통해 단일 소켓을 통해 작동하는 전이중 통신 채널을 정의합니다. 이 사양은 나중에 Python, Ruby, Go, Java 등과 같은 다른 클라이언트 기술 측면에도 적용되었습니다.

.웹워커 개요

웹 작업자는 브라우저 스레드와 백그라운드에서 실행되는 하나 이상의 JavaScript 스레드의 동시 실행을 허용합니다. 기본 JavaScript 애플리케이션 스레드는 이벤트 모델 및 postMessage() 함수를 통해 작업자 스레드 애플리케이션과 통신합니다. postMessage() 함수는 문자열 또는 JSON 객체를 단일 인수로 받아들일 수 있습니다.

 

.웹워커 테스트

main.js

let wk = new Worker("worker.js"); 
wk.addEventListener("message", function (e) {
  console.log('Message from Workers: ', e.data);
}, false);
wk.postMessage('This is from main.js'); 


worker.js
self.addEventListener('message', function(e) {
      self.postMessage(e.data);
    }, false);
    self.postMessage('This is from Workers.js');

.지원되는 웹브라우져

  • 크롬
  • 파이어폭스
  • 엣지

.파일구조

혹시 모를 내 mac 경로 /cofo~~~~/worker2

혹시 모를 서버경로 /131서버/home/예s키/에이치티/worker2/

접속주소 https://ye스키.cofor~.com/worker2/

index.html: 애플리케이션 HTML 페이지
app/main.js: 애플리케이션 메인 파일
app/worker.js: 애플리케이션 웹 작업자 파일
css/cover.css: 애플리케이션 CSS 파일
libs/jquery-3.2.1.min.js: jQuery 라이브러리 파일
bootstrap/css, bootstarp/fonts 및 bootstrap/js 폴더: Bootstrap CSS 및 라이브러리 파일 용 폴더
node_modules 폴더: 웹 서버 실행을 위한 Node.js 및 Express.js 모듈용 폴더
server.js: 웹 서버 애플리케이션 파일
package.json: 프로젝트 npm 종속성 파일

.구현도 사진

마지막 구현하면 아래와 같은 그림으로 표현할수 있습니다.

.적용된 소스

main.js

(function ($) {

// worker 서비스 등록 	
navigator.serviceWorker.register('./app/ws_workers.js').then(function(registration) {
	    console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err){
	    alert("sw error : "+err);
	    console.log('ServiceWorker registration failed: ', err);
});

// 알림  허용/차단 
Notification.requestPermission().then(function (result)
{
  if (result === 'granted')
  {
     console.log('[Notification] 허용: ', result);
  }
  else
  {
     console.log('[Notification] 차단: ', result);
  }
});




    //Define variables
    var serverurl = '',
        username = '',
        itemID = 0,
        wk = new Worker("./app/worker.js");
	const protocol = 'tr_json2';
        const loginID = 1;

    /* -----------------  UI Events  ----------------- */
    $(document).ready(function () {

        $('#btnConnect').click(function () {
            // serverurl = `ws://${$('#txtServerurl').val()}/WebSocket`;
            //serverurl = 'wss://' + $('#txtServerurl').val() + '/WebSocket';
	    serverurl = 'wss://' + $('#txtServerurl').val();
            connect(serverurl);
        });

        $('#btnLogin').click(function () {
            let username = $('#txtUsername').val();
            sendLoginrequest(username);
        });

        $('#btnSubscribe').click(function () {
            let servicename = $('#txtServiceName').val();
            let itemname = $('#txtItemName').val();
            sendItemrequest(servicename, itemname);
        });

        $('#btnUnSubscribe').click(function () {
            sendItemCloserequest();
        });

        $('#btnLogout').click(function () {
            sendLoginCloserequest();
        });


    });

    /* -----------------  Web Workers Events  ----------------- */

    //Receive events from Web Workers ws_worker.js file
    wk.addEventListener("message", function (oEvent) {
	
        let response = oEvent.data;

	console.log(response);    
	    
	//Get command parameter to identify operation
        let command = response.command;

	const res = JSON.parse(response);
	    console.log(res);
	if(res.Key.Name == "incomingMsg") {
	  $('#messagesPre').html(response);
	}





        if (command === 'connect') { //WebSocket connection event
            processConnectionEvent(response);
        } else if (command === 'incomingMsg') { //Receive incoming messages from ADS WebSocket
            processData(response.msg);
        } else if(command === 'disconnect') { //Receive Disconnect sucess from ADS WebSocket
            $('#btnConnect').html('Connect');
        }

    }, false);

    function processConnectionEvent(response) {
        $('#btnConnect').html(response.msg);
    }

    //Process incoming messages from ADS WebSocket
    function processData(data) {
	    
	let msgtype = data.Type;

console.log(">>>:"+msgtype);

        //Clear previous message
        $('#messagesPre').html('');
        //If incoming message is REFRESH_RESP
        if (msgtype === 'Refresh') {

            if (data.Domain === 'Login') {
                $('#messagesPre').html('Receive: Login REFRESH_RESP:<br/>'); //Login Refresh_resp
            } else {
                $('#messagesPre').html('Receive: Data REFRESH_RESP:<br/>'); //Data Refresh_resp
            }
            //$('#messagesPre').html(`${$('#messagesPre').html()} ${JSON.stringify(data, undefined, 2)}`); //IE10 does not support JS Template literals
            $('#messagesPre').html($('#messagesPre').html() + JSON.stringify(data, undefined, 2)); //Display REFRESH_RESP
        } else if (msgtype === 'Update') { //If incoming message is UPDATE_RESP

            //$('#messagesPre').html(`Receive: UPDATE_RESP:<br/> ${JSON.stringify(data, undefined, 2)}`); //Update_resp
            $('#messagesPre').html('Receive: UPDATE_RESP:<br/>' + JSON.stringify(data, undefined, 2)); //Display Update_resp
        } else if (msgtype === 'Status') {//If incoming message is STATUS_RESP

            //$('#messagesPre').html(`Receive: STATUS_RESP:<br/> ${JSON.stringify(data, undefined, 2)}`); //Status_resp
            $('#messagesPre').html('Receive: STATUS_RESP:<br/>' + JSON.stringify(data, undefined, 2)); //Display Status_resp
        } else if (msgtype === 'Error'){//If incoming message is ERROR_RESP
            $('#messagesPre').html('Receive: ERROR_RESP:<br/>' + JSON.stringify(data, undefined, 2)); //Display Status_resp
        } else if (msgtype === 'Ping') { //If incoming message is PING (server ping)

            //$('#messagesPre').html(`Recieve Ping:</br> ${JSON.stringify(data, undefined, 2)}`); //Server Ping
            $('#messagesPre').html('Recieve Ping:</br>' + JSON.stringify(data, undefined, 2)); //Server Ping
            sendPong();
        }
    }

    /* -----------------  WebSocket functions  ----------------- */
    //Establish WebSocket connection
    function connect(serverurl) {

        //$('#commandsPre').html(`ws = new WebSocket('${serverurl}', '${protocol}');`);
        $('#commandsPre').html('ws = new WebSocket("' + serverurl + '", "' + protocol + '");');
        let connectObj = {
            'commandObj': {
                'serverurl': serverurl,
                'protocol': protocol
            },
            'command': 'connect'
        };
        //Send message to Web Workers
        wk.postMessage(connectObj);
    }

    //Send a Login Request message to Real-Time Advanced Distribution Server WebSocket
    function sendLoginrequest(username) {

	//Create Login request message
        let loginMsg = {
            'ID': loginID,
            'Domain': 'Login',
            'Key': {
                'Elements': {
                    'ApplicationId': '256',
                    'Position': '127.0.0.1'
                },
                'Name': ''
            }
        };
        loginMsg.Key.Name = username;

        //$('#commandsPre').html(`Sending Login request message to Web Workers: WebWorkers.post(${JSON.stringify(loginMsg, undefined, 2)});`);
        $('#commandsPre').html('Sending Login request message to Web Workers: WebWorkers.post(' + JSON.stringify(loginMsg, undefined, 2) + ');');
        let loginObj = {
            'commandObj': loginMsg,
            'command': 'login'
        }
        //Send Login message to Web Workers
        wk.postMessage(loginObj);
    }

    //Send Item Request message to ADS WebSocket
    function sendItemrequest(service, itemname) {
        //Create stream ID, must not be 1 (Login)

	if (itemID === 0) {
            itemID = loginID + 1;
        } else {
            itemID += 1;
        }

    
        //create Market Price request message, if user does not set service name, let the Real-Time Advanced Distribution Server use default service
        let itemrequestMsg = {
            'ID': itemID,
            'Key': {
                'Name': itemname
            }
        };
        //if user set service name, request interested service name 
        if(service !==''){
            itemrequestMsg.Key.Service = service
        } 

        let itemrequestObj = {
            'commandObj': itemrequestMsg,
            'command': 'requestitem'
        }
        //Send Item Request message to Web Workers
        wk.postMessage(itemrequestObj);

        //$('#commandsPre').html(`Sending Item request message to Web Workers: WebWorkers.post(${JSON.stringify(itemrequestMsg, undefined, 2)});`);
        $('#commandsPre').html('Sending Item request message to Web Workers: WebWorkers.post(' + JSON.stringify(itemrequestMsg, undefined, 2) + ');');
    }

    //Send Item Close Request message to ADS WebSocket
    function sendItemCloserequest() {

        let closeitemrequestMsg = {
            'ID': itemID,
            'Type': 'Close'
        };

        let closeitemrequestObj = {
            'commandObj': closeitemrequestMsg,
            'command': 'closeitem'
        }
        //Send Item Close Request message to Web Workers
        wk.postMessage(closeitemrequestObj);

        //$('#commandsPre').html(`Sending Item Close request message to Web Workers: WebWorkers.post(${JSON.stringify(closeitemrequestMsg, undefined, 2)});`);
        $('#commandsPre').html('Sending Item Close request message to Web Workers: WebWorkers.post('
            + JSON.stringify(closeitemrequestMsg, undefined, 2) +
            ');');
    }

    //Send { 'Type': 'Pong' } for acknowledge Server PING
    function sendPong() {

        let pongObj = {
            'commandObj': { 'Type': 'Pong' },
            'command': 'pong'
        }
        //Send PONG message to Web Workers
        wk.postMessage(pongObj);

        //$('#commandsPre').html(`Sending Client Pong: ws.send(${JSON.stringify({ 'Type': 'Pong' }, undefined, 2)});`);
        $('#commandsPre').html('Sending Client Pong to Web Workers: WebWorkers.post(' + JSON.stringify({ 'Type': 'Pong' }, undefined, 2) + ');');
    }

    //Send Login Close Request message to ADS WebSocket
    function sendLoginCloserequest() {
        let closeloginrequestMsg = {
            'Domain': 'Login',
            'ID': loginID,
            'Type': 'Close'
        };

        let closeloginrequestObj = {
            'commandObj': closeloginrequestMsg,
            'command': 'logout'
        }
        //Send Login Close Request message to Web Workers
        wk.postMessage(closeloginrequestObj);

        //$('#commandsPre').html(`Sending Login Close Request: ws.send(${JSON.stringify(closeloginrequestMsg, undefined, 2)});`);
        $('#commandsPre').html('Sending Login Close Request to Web Workers: WebWorkers.post(' + JSON.stringify(closeloginrequestMsg, undefined, 2) + ');');
    }

})($);
worker.js


(function () {

    //Define WebSocket and protocol variables
    let ws = null;
    let sid = null;

    //https://ayushgp.github.io/scaling-websockets-using-sharedworkers/
    //const broadcastChannel = new BroadcastChannel("WebSocketChannel");

    //Define based object response to market_price_app.js 
    let onMessage_response = {
        'command': 'incomingMsg',
        'msg': null,
	'sid': null
    };

    //Receive message from market_price_app.js
    self.addEventListener('message', function (e) {
	let data = e.data;
        //Get command parameter to identify operation
        let command = data.command;

	   
	    //console.log("[worker.js] data.command:"+data.command);
	    //console.log("[worker.js] data.commandObj:");
	    //console.log(data.commandObj);

        if (command === 'connect') {
            connect(data.commandObj); //Establish WebSocket connection
        } else if (command === 'logout') {
            sendOMMmessage(data.commandObj);
            disconnect(); //Terminate WebSocket connection
        } else {
            sendOMMmessage(data.commandObj);
        }
        //self.postMessage(response);

    }, false);

    /* -----------------  Application events functions  ----------------- */

    //Establish WebSocket connection
    function connect(commandObj) {
	ws = new WebSocket(commandObj.serverurl, commandObj.protocol);
        ws.onopen = onOpen;
        ws.onmessage = onMessage;
        ws.onerror = onError;
        ws.onclose = onClose;
    }

    function disconnect() {
        ws.close();

        var disconnect_response = {
            'command': 'disconnect',
            'msg': 'Disconnected',
	    'sid': sid
        };
        self.postMessage(disconnect_response);
    }

    //Send message to ADS WebSocket
    function sendOMMmessage(commandObj) {
	console.log("[worker.js] sendOMMessage start");
	 ws.send(JSON.stringify(commandObj));
    }

    /* -----------------  WS events  ----------------- */

    //Establish WebSocket connection success
    function onOpen(event) {
	    console.log("[worker.js] onOpen start");
	    console.log(event);
        var onOpen_response = {
            'command': 'connect',
            'msg': 'Connected',
	    'sid': sid
        };
        self.postMessage(onOpen_response);
    }
    //Receives incoming message from WebSocket
    function onMessage(event) {
        console.log("[worker.js] onMessage start");
	let incomingdata = JSON.parse(event.data.toString());
	    self.postMessage(event.data);

        //Iterate each JSON message and send it to market_price_app.js
        for (let index = 0; index < incomingdata.length; index++) {

            onMessage_response.msg = incomingdata[index];
	    //console.log(">>>[worker.js] incomingdata");
	    //console.log(onMessage_response);
	    self.postMessage(onMessage_response);
	}
    };

    function onError(event) {
        var onError_response = {
            'command': 'error',
            'msg': event,
	    'sid': sid
        };
        self.postMessage(onError_response);
    };

    function onClose(event) {
        var onClose_response = {
            'command': 'close',
            'msg': 'WebSocket Closed',
	    'sid': sid
        };
        self.postMessage(onClose_response);
    };




})();
index.html

<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>WebSocket API Market Price Streaming Example</title>
        <!-- Bootstrap  Latest compiled and minified CSS -->
        <link rel="stylesheet" href="./bootstrap/css/bootstrap.min.css">
        <!-- Bootstrap  Optional theme -->
        <link rel="stylesheet" href="./bootstrap/css/bootstrap-theme.min.css">
        
        <!-- customize CSS -->    
        <link href="./css/cover.css" rel="stylesheet">
        
        <!-- jQuery -->
        <script src="./libs/jquery-3.2.1.min.js"></script>

        <!-- Bootstrap  Latest compiled and minified JavaScript -->
        <script src="./bootstrap/js/bootstrap.min.js"></script>
        
        <!-- Application JS -->
        <script src="./app/main.js"></script>
    </head>

<body>
    <!-- WebSocket connection UI input form -->
    <div class="container-fluid">
        <h4>WebSocket API with Web Workers Example</h4>
        <form class="form-inline">
            <div class="form-group">
                <label for="txtServerurl">Server:</label> &nbsp; <input type="text" value="yeskey.coforward.com:9101" id="txtServerurl" placeholder="Server IP:WebSocket Port" class="form-control">&nbsp;&nbsp;
                <button id="btnConnect" type="button" class="btn btn-sm btn-primary">Connect</button>
            </div>
            <br/><br/>
            <div class="form-group">
                <label for="txtUsername" >User:</label> &nbsp; <input type="text" id="txtUsername" class="form-control" placeholder="user">&nbsp;&nbsp;
                <button id="btnLogin" type="button" class="btn btn-sm btn-primary">Login</button> &nbsp;&nbsp;
                <button id="btnLogout" type="button" class="btn btn-sm btn-primary">Logout</button>
            </div>
            <br/>
            <br/>
            <div class="form-group">
                <!--<label for="txtServiceName">Service Name:</label> &nbsp; <input type="text" id="txtServiceName" class="form-control"> &nbsp;&nbsp;-->
                <label for="txtItemName">Item Name:</label>&nbsp;
                <input type="text" id="txtItemName" value="" placeholder="IBM.N" class="form-control"> &nbsp;&nbsp;
                <label for="txtServiceName">Service Name:</label> &nbsp; <input type="text" id="txtServiceName" class="form-control" placeholder="[Optional]">  &nbsp;&nbsp;
                <button id="btnSubscribe" type="button" class="btn btn-sm btn-primary">Subscribe</button> &nbsp;&nbsp;
                <button id="btnUnSubscribe" type="button" class="btn btn-sm btn-primary">Unsubscribe</button>
            </div>
        </form>
        <!-- Display outgoing messages and commands -->
        <h5>Commands:</h5>
        <div id="command">
            <pre id="commandsPre">
        </pre>
        </div>
        <!-- Display incoming messages -->
        <h5>Incoming Messages:</h5>
        <div id="message">
            <pre id="messagesPre">
        </pre>
        </div>
    </div>
</body>

</html>

.Node 웹소켓 서버

웹워커를 사용하기 위해서는 https 필요하기 때문에 웹소켓 서버 wss 와 port:9101 사용한다.

server.js

var https = require('https');
var fs   = require('fs');
var WebSocketServer = require('websocket').server;


var https_options = {
	 ca: fs.readFileSync("chain1.pem"),
	 key: fs.readFileSync("privkey1.pem"),
	 cert: fs.readFileSync("cert1.pem")
};


var httpsServer = https.createServer( https_options, function(request, response) {
	    console.log((new Date()) + ' Received request for ' + request.url);
	    response.writeHead(404);
	    response.end();
})

httpsServer.listen(9101);
var WebSocketServer = require('ws').Server;

var wss = new WebSocketServer({
	    server: httpsServer,
	    autoAcceptConnections: false
});

//https://stackoverflow.com/questions/13364243/websocketserver-node-js-how-to-differentiate-clients
let CLIENTS=[];

wss.getUniqueID = function () {
    function s4() {
        return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
    }
    return s4() + s4() + '-' + s4();
};


wss.on('connection', function connection(ws,request) {
        ws.id = wss.getUniqueID();
	CLIENTS.push(ws);

	const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
	console.log(ws.id+` 새로운 클라이언트[${ip}] 접속`);


	if(ws.readyState === ws.OPEN){
	    let sendData = {command: 'message', msg: ' 클라이언트'+ip+' 접속을 환영합니다 from 서버', sid:ws.id};
   	    //ws.send(JSON.stringify(sendData));
	    sendAll(sendData);
	}

	ws.on('message', function incoming(message) {
		message = JSON.parse(message);
		console.log('received: %s', message);
		//ws.send(JSON.stringify(message));
		sendAll(message);
	});

});


function sendAll (message) {
    for (var i=0; i<CLIENTS.length; i++) {
	    console.log(i);
        CLIENTS[i].send(JSON.stringify(message));
    }
}

.실행방법

$ npm install
$ node server.js

 

 

 

- YouTube

 

www.youtube.com