Skip to content

Instantly share code, notes, and snippets.

@kerus1024
Last active May 17, 2019 10:41
Show Gist options
  • Save kerus1024/13e89f84bbc464ddc653071adb7d220b to your computer and use it in GitHub Desktop.
Save kerus1024/13e89f84bbc464ddc653071adb7d220b to your computer and use it in GitHub Desktop.
Node.js Console Mutlicast Chat Application
global.MULTICAST_DGRAM_PORT = 1111;
global.MULTICAST_GROUP = '239.99.99.99';
global.PEER_TIMEOUT = 30
global.PEER_HEARTBEAT = 5;
global.KEY = 'ashe';
const dgram = require("dgram");
const process = require("process");
const readline = require('readline');
const os = require('os');
const interfaces = os.networkInterfaces();
const addresses = [];
const crypto = require('crypto');
const algorithm = 'aes256';
const inputEncoding = 'utf8';
const outputEncoding = 'hex';
encryptText = (text) => {
// Deprecated Node 12 ( IV )
const cipher = crypto.createCipher(algorithm, KEY);
let ciphered = cipher.update(text, inputEncoding, outputEncoding);
ciphered += cipher.final(outputEncoding);
return ciphered;
}
decryptText = (text) => {
// Deprecated Node 12
const decipher = crypto.createDecipher(algorithm, KEY);
let deciphered = decipher.update(text, outputEncoding, inputEncoding);
deciphered += decipher.final(inputEncoding);
return deciphered;
}
let r1;
let nick = '';
const mchat = {};
mchat.peers = {};
newReadline = () => {
rl = readline.createInterface(process.stdin, process.stdout);
}
closeReadline = (x) => {
rl.close();
if (!x) newReadline();
}
// Logs a message keeping prompt on last line
print = (message) => {
readline.cursorTo(process.stdout, 0);
console.log(message);
rl.prompt(true);
}
determineIPs = () => {
// Getting Interfaces IPv4
for (let interface in interfaces) {
for (let ver in interfaces[interface]) {
if (interfaces[interface][ver].family != 'IPv4') continue;
addresses.push(interfaces[interface][ver].address);
}
}
}
determineIPs();
console.log(addresses.join(' '));
//--------------------------------------------------------------------------------------
//
// Initialize Environment
//
async function program1(){
return new Promise((resolve, reject) => {
newReadline();
rl.question('[INFO] 인터페이스 IP를 지정하세요 : ', userInput1 => {
closeReadline();
if (addresses.indexOf(userInput1) !== -1) {
rl.question('[INFO] Nickname을 지정하세요. : ', userInput2 => {
closeReadline();
nick = userInput2;
resolve(userInput1);
});
} else {
console.error('인터페이스를 찾을 수 없습니다.');
process.exit(1);
//reject();
}
});
});
}
//--------------------------------------------------------------------------------------
//
// Chatting Application
//
async function program5(ifIP){
return new Promise((resolve, reject) => {
const socket = dgram.createSocket({ type: "udp4", reuseAddr: true });
socket.bind(MULTICAST_DGRAM_PORT, '0.0.0.0', () => {
// 0.0.0.0 이 아니면, 이상하게 Linux에서 패킷이 받아지지 않네. 방화벽의 영향?
// 적용 되지 않는 코드같아 보임.
socket.setMulticastInterface(ifIP);
});
socket.on("listening", () => {
// UDP
socket.addMembership(MULTICAST_GROUP, ifIP);
print(`[INFO] UDP socket listening on ${socket.address().address} pid: ${process.pid}`);
let capsula = {
mc: true, // 메시지 구분
proto: 'HELLO',
nick: nick
}
let message = Buffer.from(encryptText(JSON.stringify(capsula)));
socket.send(message, 0, message.length, MULTICAST_DGRAM_PORT, MULTICAST_GROUP, (err) => {
if (err) throw err;
});
print(`[INFO] Hello 메시지를 보냈습니다.`);
print(`[INFO] 명령어는 // 입니다?`);
// announce heartbeat
capsula = {
mc: true,
proto: 'ALIVE',
nick: nick
}
message = Buffer.from(encryptText(JSON.stringify(capsula)));
setInterval(function announce(){
socket.send(message, 0, message.length, MULTICAST_DGRAM_PORT, MULTICAST_GROUP, (err) => { });
return announce;
}(), 1000 * PEER_HEARTBEAT);
setTimeout(() => {
printPeers();
}, 1000 * PEER_HEARTBEAT + 1000);
});
socket.on("message", (message, rinfo) => {
try {
const decode = JSON.parse(decryptText(message.toString()));
if (decode.mc === true) {
switch (decode.proto) {
case 'ALIVE':
addPeer(rinfo, decode);
break;
case 'HELLO':
print(`[INFO][HELLO] ${rinfo.address} : nickname : ${decode.nick}`);
break;
case 'BYE':
print(`[INFO][BYE] ${rinfo.nick}(${rinfo.address}) has left.`);
// Not implemented
break;
case 'TEXT':
print(`${decode.nick} (${rinfo.address}) : ${decode.data}`);
break;
default:
console.error(`[ERROR] ${rinfo.address}로 알 수 없는 메시지를 받았습니다.`);
break;
}
} else {
throw new Error(`[ERROR] ${rinfo.address}로 부터 잘못 된 메시지를 받았습니다. ${message}`);
}
} catch (e) {
console.error(`[ERROR] ${rinfo.address}로 부터 잘못 된 메시지를 받았습니다.`);
console.error(e);
}
});
const recursiveInput = () => {
rl.question('>> ', userInput => {
// User Command
if (userInput.startsWith('//')) {
if (userInput === '//peers') {
printPeers();
} else {
print('----- Commands -----');
print(`//peers : Print current peers.\n`)
}
recursiveInput();
return;
}
const capsula = {
mc: true, // 메시지 구분
proto: 'TEXT',
nick: nick,
data: userInput
}
const message = Buffer.from(encryptText(JSON.stringify(capsula)));
socket.send(message, 0, message.length, MULTICAST_DGRAM_PORT, MULTICAST_GROUP, (err) => {
recursiveInput();
if (err) {
console.error(`[ERROR] Failed message send. ${err}`);
}
});
});
}
recursiveInput();
});
}
process.stdin.setEncoding('utf8');
(async function() {
const ip = await program1();
await program5(ip);
})();
//------------------------------------------------------------------------
addPeer = (rinfo, message) => {
mchat.peers[rinfo.address] = {
nick: message.nick,
hbtime: new Date().getTime() // Heartbeat
}
}
printPeers = () => {
if (!Object.keys(mchat.peers).length) {
print('-- There are no peers');
} else {
print('-- Listing peers');
}
for (let peer in mchat.peers) {
print(`${peer} - ${mchat.peers[peer].nick}`);
}
}
// Heartbeat
setInterval(() => {
const epoch_target = (new Date().getTime()) - (1000 * PEER_TIMEOUT);
for (let peer in mchat.peers) {
if (mchat.peers[peer].hbtime < epoch_target){
print(`[INFO] TIMEOUT PEER ${peer}`);
delete mchat.peers[peer];
}
}
}, 1000 * PEER_TIMEOUT)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment