# РГР Філіпенко Микита Володимирович
## 1. Постановка Задачі
Створити мульти-клієнт - серверний додаток з візуалізацією поточної топології мережі та стану з'єднань, що використовує хостинг replit.com з реалізацією функціоналу за допомогою API Socket.IO
Додаток має забезпечувати
1) Передачу повідомлень від вашого клієнта будь-якому іншому (unicasting)
2) Передачу повідомлень від вашого клієнта всім іншим (broadcasting)
Повідомлення мають бути двох типів
* текстові повідомлення
* зображення
Під час передачі даних додаток повинен контролювати поточну затримку поширення (propagation delay)) від клієнта
і пропускну здатність канала клієнта та візуалізувати їх на діаграмі товщиною ребер графа діаграми та розміром вузлів.
Клієнти з різними ip повинні мати різний колір вузлів, клієнти з однаковим ip повинні мати однаковий колір вузлів.
Використовувати діаграму шарів потоків для демонстрації зміни в часі пропускних здібностей множини каналів.
## 2. Теоретичні відомості
- Socket.IO - це бібліотека JavaScript, яка дозволяє реалізувати багатонаправлене з'єднання між клієнтом та сервером через веб-сокети. Вона забезпечує підтримку реального часу, а також можливість обміну даними між клієнтом і сервером.
- Мережева топологія описує фізичну або логічну структуру мережі. Додаток має візуалізувати поточну топологію мережі та стан з'єднань. Для цього можна використовувати використовувати граф, де вузли будуть представляти клієнтів, а ребра - з'єднання між ними.
- Delay та Bandwidth
**Propagation Delay** (затримка поширення) визначає час, який потрібно для передачі сигналу від одного кінця каналу до іншого. **Bandwidth** (пропускна здатність) вимірює максимальну кількість даних, яку можна передати через канал за певний час.
## 3. Результати
Запустивши програму, я зайшов на сайт ще з одного клієнту, а також на сайт зайшов друг з іншого комп'ютера.

### 3.1 Відправка повідомлення усім користувачам
Я написав усім користувачам "Привіт усім!". Спочатку в консоль йдуть початкові bandwidth та delay (які я налаштував заздалегідь), далі - bandwidth та delay після відправлення повідомлення.

**Delay** розраховується за формулою **Час отримання** - **час відправки**.
**Bandwidth** розраховуэться за формулою **Delay** * **message_size**.
Після відправки повідомлення маю наступний граф:

Далі я роздивляюся різні розміри повідомлень.
- Надсилаю довге повідомлення


Товщина ребра змінилася (стала товще).
- Надсилаю коротке повідомлення


Товщина ребра змінилася (стала тонще).
### 3.2 Відправка повідомлення одному користувачу


Я надіслав повідомлення від клієнту tYjsQ4Ou7PRPyCWBAAAB до клієнту dc-nkgFddjyKgQJ_AAAD. Можна побачити, що тільки цей клієнт отримав повідомлення.
Клієнт pFCQqLEsdLBZ-z-tAAAF повідомлення не отримав.

### 3.3 Відправка зображення усім користувачам


Можна побачити що Bandwidth став значно більшим (відповідно, ребро графа стало товщим).

Зображення отримали усі користувачі.

### 3.4 Відправка зображення одному користувачу

Відправив інше зображення клієнту tYjsQ4Ou7PRPyCWBAAAB від клієнта 9ErxPBHd4jQ3AEhuAAAH.

В свою чергу клієнт 1FnxuoU9NLdgc2sBAAAD нове зображення не отримав.

## 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}`);
});
```