# РГР Філіпенко Микита Володимирович ## 1. Постановка Задачі Створити мульти-клієнт - серверний додаток з візуалізацією поточної топології мережі та стану з'єднань, що використовує хостинг replit.com з реалізацією функціоналу за допомогою API Socket.IO Додаток має забезпечувати 1) Передачу повідомлень від вашого клієнта будь-якому іншому (unicasting) 2) Передачу повідомлень від вашого клієнта всім іншим (broadcasting) Повідомлення мають бути двох типів * текстові повідомлення * зображення Під час передачі даних додаток повинен контролювати поточну затримку поширення (propagation delay)) від клієнта і пропускну здатність канала клієнта та візуалізувати їх на діаграмі товщиною ребер графа діаграми та розміром вузлів. Клієнти з різними ip повинні мати різний колір вузлів, клієнти з однаковим ip повинні мати однаковий колір вузлів. Використовувати діаграму шарів потоків для демонстрації зміни в часі пропускних здібностей множини каналів. ## 2. Теоретичні відомості - Socket.IO - це бібліотека JavaScript, яка дозволяє реалізувати багатонаправлене з'єднання між клієнтом та сервером через веб-сокети. Вона забезпечує підтримку реального часу, а також можливість обміну даними між клієнтом і сервером. - Мережева топологія описує фізичну або логічну структуру мережі. Додаток має візуалізувати поточну топологію мережі та стан з'єднань. Для цього можна використовувати використовувати граф, де вузли будуть представляти клієнтів, а ребра - з'єднання між ними. - Delay та Bandwidth **Propagation Delay** (затримка поширення) визначає час, який потрібно для передачі сигналу від одного кінця каналу до іншого. **Bandwidth** (пропускна здатність) вимірює максимальну кількість даних, яку можна передати через канал за певний час. ## 3. Результати Запустивши програму, я зайшов на сайт ще з одного клієнту, а також на сайт зайшов друг з іншого комп'ютера. ![Снимок экрана 2024-05-15 в 07.35.38](https://hackmd.io/_uploads/BJ5ionbm0.png) ### 3.1 Відправка повідомлення усім користувачам Я написав усім користувачам "Привіт усім!". Спочатку в консоль йдуть початкові bandwidth та delay (які я налаштував заздалегідь), далі - bandwidth та delay після відправлення повідомлення. ![Снимок экрана 2024-05-15 в 07.38.27](https://hackmd.io/_uploads/S1M8h3bQ0.png) **Delay** розраховується за формулою **Час отримання** - **час відправки**. **Bandwidth** розраховуэться за формулою **Delay** * **message_size**. Після відправки повідомлення маю наступний граф: ![Снимок экрана 2024-05-15 в 07.39.13](https://hackmd.io/_uploads/rk0dhhbXC.png) Далі я роздивляюся різні розміри повідомлень. - Надсилаю довге повідомлення ![Снимок экрана 2024-05-15 в 07.40.25](https://hackmd.io/_uploads/Syvp33bQ0.png) ![Снимок экрана 2024-05-15 в 07.40.58](https://hackmd.io/_uploads/r1FJTn-XC.png) Товщина ребра змінилася (стала товще). - Надсилаю коротке повідомлення ![Снимок экрана 2024-05-15 в 07.41.43](https://hackmd.io/_uploads/HyLf6hZX0.png) ![Снимок экрана 2024-05-15 в 07.42.18](https://hackmd.io/_uploads/Hk9Va3Wm0.png) Товщина ребра змінилася (стала тонще). ### 3.2 Відправка повідомлення одному користувачу ![Снимок экрана 2024-05-15 в 07.43.26](https://hackmd.io/_uploads/Hynd6hZXR.png) ![Снимок экрана 2024-05-15 в 07.44.13](https://hackmd.io/_uploads/Sk13p2WXR.png) Я надіслав повідомлення від клієнту tYjsQ4Ou7PRPyCWBAAAB до клієнту dc-nkgFddjyKgQJ_AAAD. Можна побачити, що тільки цей клієнт отримав повідомлення. Клієнт pFCQqLEsdLBZ-z-tAAAF повідомлення не отримав. ![telegram-cloud-photo-size-2-5343882036231920170-y](https://hackmd.io/_uploads/Sy5ZA2-XC.jpg) ### 3.3 Відправка зображення усім користувачам ![Снимок экрана 2024-05-15 в 07.49.00](https://hackmd.io/_uploads/BJoT0h-XA.png) ![Снимок экрана 2024-05-15 в 07.49.49](https://hackmd.io/_uploads/BJoxkpZX0.png) Можна побачити що Bandwidth став значно більшим (відповідно, ребро графа стало товщим). ![Снимок экрана 2024-05-15 в 07.50.32](https://hackmd.io/_uploads/H1wQy6ZQA.png) Зображення отримали усі користувачі. ![telegram-cloud-photo-size-2-5343882036231920172-y](https://hackmd.io/_uploads/HkJO16Z7C.jpg) ### 3.4 Відправка зображення одному користувачу ![Снимок экрана 2024-05-15 в 07.52.54](https://hackmd.io/_uploads/HkB3k6W7R.png) Відправив інше зображення клієнту tYjsQ4Ou7PRPyCWBAAAB від клієнта 9ErxPBHd4jQ3AEhuAAAH. ![telegram-cloud-photo-size-2-5343882036231920173-y](https://hackmd.io/_uploads/H1DzlabXC.jpg) В свою чергу клієнт 1FnxuoU9NLdgc2sBAAAD нове зображення не отримав. ![Снимок экрана 2024-05-15 в 07.54.21](https://hackmd.io/_uploads/H1iZxTWQC.png) ## 5. Висновок В цій розрахунково-графічній роботі я створив мульти-клієнт - серверний додаток з візуалізацією поточної топології мережі та стану з'єднань, використовуючи хостинг replit.com та API Socket.IO. Додаток забезпечує передачу повідомлень від клієнта будь-якому іншому (unicasting) і всім іншим (broadcasting). Повідомлення можуть бути текстовими або зображеннями. Під час передачі даних додаток контролює поточну затримку поширення (propagation delay) від клієнта та пропускну здатність канала клієнта, і візуалізує їх на діаграмі за допомогою товщини ребер графа та розміру вузлів. ## Додаток Посилання на replit - https://replit.com/join/jivwlfljil-nikitafilipenko index.html файл ```javascript=1 <!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Network Visualization</title> <style> #network { width: 100%; height: 500px; border: 1px solid lightgray; } img { width: 250px; } </style> </head> <body> <div id="network"></div> <p id="my_id"></p> <select id="client-list"></select ><br /><br /> <input type="text" id="message-input" /> <button type="button" id="send-message"> Відправити повідомлення всім </button> <br /> <br /> <input type="file" id="image-input" /> <button type="button" id="send-image"> Відправити зображення всім </button> <br /> <br /> <button type="button" id="send-to-client">Відправити клієнту</button> <div class="messages"></div> <div class="images"></div> <canvas id="bandwidth-chart"></canvas> <script src="/socket.io/socket.io.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis-network.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script> function createRandomColor() { return `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`; } const socket = io(); const nodes = new vis.DataSet([]); const edges = new vis.DataSet([]); const container = document.getElementById("network"); const data = { nodes: nodes, edges: edges }; const options = {}; const network = new vis.Network(container, data, options); /// --- Отримання свого id socket.on("connect", () => { const clientId = socket.id; document.getElementById("my_id").innerText = `My ID: ${clientId}`; }); /// --------- Оновлення топології при зміні socket.on("updateTopology", (topology) => { let clientColors = new Map(); nodes.clear(); edges.clear(); // Додаємо сервер nodes.add({ id: "server", label: "Server", color: { background: "green", border: "black" }, shape: "dot", size: 20, title: `IP: ${topology.server.ip}\nBandwidth: ${topology.server.bandwidth} bytes/sec\nDelay: ${topology.server.delay} msec`, }); // Додаємо вузли клієнтів Object.values(topology).forEach((node) => { if (node.id !== "server") { let ipColor = node.ip.split(",")[0]; if (!clientColors.has(ipColor)) { clientColors.set(ipColor, createRandomColor()); } nodes.add({ id: node.id, label: node.id, color: { background: clientColors.get(ipColor) }, shape: "dot", size: Math.abs(calculateNodeSize(node.delay)), title: `IP: ${node.ip}\n Bandwidth: ${node.bandwidth} bytes/sec\nDelay: ${node.delay} msec`, }); } }); // Додаємо зв'язки між клієнтами та сервером Object.keys(topology).forEach((clientId) => { if (clientId !== "server") { edges.add({ from: "server", to: clientId, width: Math.abs( calculateEdgeWidth( topology[clientId].bandwidth, ), ), }); } }); }); /// -------- Обробка події оновлення підключень socket.on("updateClientList", (clientList) => { const select = document.getElementById("client-list"); // Очищаємо перелік select.innerHTML = ""; // Додаємо нове підключення clientList.forEach((clientId) => { const option = document.createElement("option"); option.value = clientId; option.textContent = clientId; select.appendChild(option); }); }); //Обробка натискання кнопки "Відправити повідомлення" document .getElementById("send-message") .addEventListener("click", () => { const message = document .getElementById("message-input") .value.trim(); if (message !== "") { socket.emit("sendMessage", { message: message, bandwidth: message.length, startTime: Date.now(), }); document.getElementById("message-input").value = ""; } }); /// ---- Обробка події отримання тексту socket.on("receiveMessage", (data) => { const messageElement = document.createElement("p"); messageElement.innerHTML = `Id: ${data.senderId}, Message: ${data.message}`; document.querySelector(".messages").appendChild(messageElement); // Додаємо зображення до сторінки }); // Обробка події "Відправити клієнту" document .getElementById("send-to-client") .addEventListener("click", () => { const selectedClientId = document.getElementById("client-list").value; const file = document.getElementById("image-input").files[0]; const message = document .getElementById("message-input") .value.trim(); if (selectedClientId) { if (message || file) { const data = { recipientId: selectedClientId }; if (message != "") { data.message = message; data.sendTime = Date.now(); data.bandwidth = message.length; socket.emit("sendToClient", data); document.getElementById("message-input").value = ""; } if (file && message == "") { const reader = new FileReader(); reader.onload = (e) => { data.imageData = e.target.result; data.sendTime = Date.now(); data.bandwidth = e.target.result.length; socket.emit("sendToClient", data); }; reader.readAsDataURL(file); } } } }); // Обробка натискання кнопки "Send Image" document .getElementById("send-image") .addEventListener("click", (event) => { const file = document.getElementById("image-input").files[0]; const reader = new FileReader(); reader.onload = (e) => { const imageData = e.target.result; socket.emit("sendImage", { imageData: imageData, bandwidth: e.target.result.length, startTime: Date.now(), }); }; reader.readAsDataURL(file); }); /// - Обробка події отримання зображення socket.on("receiveImage", (data) => { const image = new Image(); image.src = data.imageData; document.querySelector(".images").appendChild(image); // Додаємо зображення до сторінки }); // Функція для обчислення розміру вузла function calculateNodeSize(delay) { return 200 / (10 + delay / 5); } // Функція для обчислення товщини ребра function calculateEdgeWidth(bandwidth) { return Math.asinh(bandwidth); } </script> </body> </html> ``` index.js файл ```javascript=1 const express = require("express"); const http = require("http"); const socketIo = require("socket.io"); const vis = require("vis-network"); const app = express(); const server = http.createServer(app); const io = socketIo(server); // Зберігаємо поточну топологію мережі та стан з'єднань let networkTopology = {}; // Функція для оновлення топології мережі function updateNetworkTopology() { io.emit("updateTopology", networkTopology); } // Функція для оновлення поточного стану підключень function sendClientListUpdate() { const clientList = Object.keys(networkTopology).filter( (clientId) => clientId !== "server", ); io.emit("updateClientList", clientList); } io.on("connection", (socket) => { console.log("New client connected"); // Додамо нового клієнта до мережі networkTopology[socket.id] = { id: socket.id, connections: [], ip: socket.handshake.headers["x-forwarded-for"], bandwidth: 50, delay: 10, }; //console.log("aaaa", socket.handshake.headers["x-forwarded-for"]); //console.log(typeof socket.handshake.headers["x-forwarded-for"]); // Оновлюємо топологію мережі для всіх клієнтів updateNetworkTopology(); //Оновлюємо перечень підключень sendClientListUpdate(); // Отримання адреси IP сервера const serverAddress = socket.request.connection.remoteAddress; // Визначення ID сервера const serverId = "server"; // Додаємо сервер до топології networkTopology[serverId] = { id: serverId, connections: [], ip: serverAddress, bandwidth: 10000, delay: 0, }; // Оновлюємо топологію мережі для всіх клієнтів updateNetworkTopology(); //Оновлюємо перечень підключень sendClientListUpdate(); /// --- отримання поточного стану відключень socket.on("disconnect", () => { console.log("Client disconnected"); // Видаляємо відключений клієнт з мережі delete networkTopology[socket.id]; // Оновлюємо топологію мережі для всіх клієнтів updateNetworkTopology(); sendClientListUpdate(); }); // Додайте з'єднання до кожного клієнта Object.keys(networkTopology).forEach((clientId) => { if (clientId !== socket.id) { networkTopology[socket.id].connections.push(clientId); } }); // Оновлюємо топологію мережі після додавання з'єднань updateNetworkTopology(); sendClientListUpdate(); // Обробник події відправки текстового повідомлення socket.on("sendMessage", (message) => { console.log(`Message from ${socket.id}: ${message.message}`); console.log( `Network bandwidth for ${socket.id} is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} is ${networkTopology[socket.id].delay}`, ); networkTopology[socket.id].bandwidth = (Date.now() - message.startTime) * message.bandwidth; networkTopology[socket.id].delay = Date.now() - message.startTime; console.log( `Network bandwidth for ${socket.id} after resize is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} after resize is ${networkTopology[socket.id].delay}`, ); // Оновлюємо топологію мережі після додавання з'єднань updateNetworkTopology(); // Ретранслюємо повідомлення всім іншим клієнтам socket.broadcast.emit("receiveMessage", { senderId: socket.id, message: message.message, }); }); // Обробник події відправки зображення socket.on("sendImage", (imageData) => { console.log(`Image data received from ${socket.id}`); console.log( `Network bandwidth for ${socket.id} is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} is ${networkTopology[socket.id].delay}`, ); networkTopology[socket.id].bandwidth = (Date.now() - imageData.startTime) * imageData.bandwidth; networkTopology[socket.id].delay = Date.now() - imageData.startTime; console.log( `Network bandwidth for ${socket.id} after resize is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} after resize is ${networkTopology[socket.id].delay}`, ); // Оновлюємо топологію мережі після додавання з'єднань updateNetworkTopology(); // Ретранслюємо зображення всім іншим клієнтам socket.broadcast.emit("receiveImage", { senderId: socket.id, imageData: imageData.imageData, }); }); // Обробник події відправки повідомленя або зображення конкретному клієнту socket.on("sendToClient", (data) => { const recipientId = data.recipientId; if (recipientId) { if (data.message && data.message != null) { // Отримати повідомлення console.log("Got message"); console.log( `Network bandwidth for ${socket.id} is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} is ${networkTopology[socket.id].delay}`, ); networkTopology[socket.id].bandwidth = (Date.now() - data.sendTime) * data.bandwidth; networkTopology[socket.id].delay = Date.now() - data.sendTime; // Оновлюємо топологію мережі після додавання з'єднань updateNetworkTopology(); console.log( `Network bandwidth for ${socket.id} after resize is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} after resize is ${networkTopology[socket.id].delay}`, ); socket.to(recipientId).emit("receiveMessage", { senderId: socket.id, message: data.message, }); } if (data.imageData && data.imageData != null) { // Отримати зображення console.log("Got image"); console.log( `Network bandwidth for ${socket.id} is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} is ${networkTopology[socket.id].delay}`, ); networkTopology[socket.id].bandwidth = (Date.now() - data.sendTime) * data.bandwidth; networkTopology[socket.id].delay = Date.now() - data.sendTime; // Оновлюємо топологію мережі після додавання з'єднань updateNetworkTopology(); console.log( `Network bandwidth for ${socket.id} after resize is ${networkTopology[socket.id].bandwidth}`, ); console.log( `Network delay for ${socket.id} after resize is ${networkTopology[socket.id].delay}`, ); socket.to(recipientId).emit("receiveImage", { senderId: socket.id, imageData: data.imageData, }); } } else { console.log(`Client with ID ${recipientId} not found.`); } }); }); // Налаштування Express для віддачі статичних файлів app.use(express.static("public")); // Запускаємо сервер на порту 3000 const PORT = 3000; server.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); }); ```