Last active
May 17, 2019 10:41
-
-
Save kerus1024/13e89f84bbc464ddc653071adb7d220b to your computer and use it in GitHub Desktop.
Node.js Console Mutlicast Chat Application
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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