.개요
웹 브라우저 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> <input type="text" value="yeskey.coforward.com:9101" id="txtServerurl" placeholder="Server IP:WebSocket Port" class="form-control">
<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> <input type="text" id="txtUsername" class="form-control" placeholder="user">
<button id="btnLogin" type="button" class="btn btn-sm btn-primary">Login</button>
<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> <input type="text" id="txtServiceName" class="form-control"> -->
<label for="txtItemName">Item Name:</label>
<input type="text" id="txtItemName" value="" placeholder="IBM.N" class="form-control">
<label for="txtServiceName">Service Name:</label> <input type="text" id="txtServiceName" class="form-control" placeholder="[Optional]">
<button id="btnSubscribe" type="button" class="btn btn-sm btn-primary">Subscribe</button>
<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
'프로그래밍 > Js' 카테고리의 다른 글
websocket 바이너리 전송 arraybuffer (0) | 2022.05.06 |
---|---|
Shared Workers 이용하여 WebSocket 연결 방법 (웹소켓을 연결한 상태에서 새로고침 또는 다른 페이지로 이동시 웹소켓 연결을 유지하는게 목적) (0) | 2021.12.17 |
node + ws + express webSocket 샘플 예제 (0) | 2021.11.08 |
[ES6+] 버튼 클릭시 클랙스 메소드 바로실행(if문 사용 안함) (0) | 2021.11.04 |
service worker push API 제작기 (1) - 권한요청 (0) | 2021.10.29 |