# [2024: Комп'ютерні системи та мережі.]
# [Тиченко Варвара Вікторівна.]
---
[TOC]
---
# Завдання
Створити мульти-клієнт - серверний додаток з візуалізацією поточної топології мережі та стану з'єднань, що використовує хостинг replit.com з реалізацією функціоналу за допомогою API Socket.IO
Додаток має забезпечувати
1) передачу повідомлень від вашого клієнта будь-якому іншому (unicasting)
2) передачу повідомлень від вашого клієнта всім іншим (broadcasting)
Повідомлення мають бути двох типів
* текстові повідомлення
* зображення
Під час передачі даних додаток повинен контролювати поточну затримку поширення (propagation delay)) від клієнта
і пропускну здатність канала клієнта та візуалізувати їх на діаграмі товщиною ребер графа діаграми та розміром вузлів.
Клієнти з різними ip повинні мати різний колір вузлів, клієнти з однаковим ip повинні мати однаковий колір вузлів.
Використовувати діаграму шарів потоків для демонстрації зміни в часі пропускних здібностей множини каналів.
# Звіт до РГЗ
## Код програми
### Серверна частина (server.js)
Цей скрипт використовує бібліотеку Express для створення HTTP та WebSocket сервера, який обробляє запити на два маршрути: / та /ws. Статичні файли клієнта знаходяться у папці public
```javascript=
const express = require("express");
const expressWs = require("express-ws");
const { app } = expressWs(express());
app.use(express.static("public"));
let clients = []; // Масив клієнтів
let networkData = { // Дані ро граф
nodes: [],
edges: [],
};
// Обробник підключення клієнтів
app.ws("/ws", (ws, req) => {
console.log("start");
const id = req.query.id || `client${clients.length + 1}`;
// Відключити попереднє з'єднання з таким же id
const existingClient = clients.find((client) => client.id === id);
if (existingClient) {
console.log("disconnect previous", id);
existingClient.close();
clients = clients.filter((client) => client !== existingClient);
networkData.nodes = networkData.nodes.filter((node) => node.id !== id);
}
// Додати нового клієнта
ws.id = id;
console.log("open", id);
clients.push(ws);
networkData.nodes.push({ id: ws.id, label: ws.id, value: 1 });
// Оновити список клієнтів
broadcast(JSON.stringify({ type: "update", networkData }), ws);
// Обробка повідомлень від клієнтів
ws.on("message", (msg) => {
const message = JSON.parse(msg);
if (message.type === "broadcast") {
handleBroadcast(message, ws);
} else if (message.type === "unicast") {
handleUnicast(message, ws);
}
});
// Обробка закриття з'єднання
ws.on("close", () => {
console.log("close", id);
clients = clients.filter((client) => client !== ws);
networkData.nodes = networkData.nodes.filter((node) => node.id !== ws.id);
networkData.edges = networkData.edges.filter(
(edge) => edge.from !== ws.id && edge.to !== ws.id,
);
broadcast(JSON.stringify({ type: "update", networkData }));
});
ws.send(JSON.stringify({ type: "init", id: ws.id, networkData }));
});
// відправка повідомлення всім клієнтам і оновлення розмірів графа
function handleBroadcast(message, sender) {
clients.forEach((client) => {
if (client !== sender && client.readyState === client.OPEN) {
client.send(JSON.stringify(message));
}
});
// Оновлення розмірів графа
networkData.nodes.forEach((node) => {
if (node.id !== sender.id) {
updateEdge(sender.id, node.id);
}
});
updateNode(sender.id);
broadcast(JSON.stringify({ type: "update", networkData }));
}
// відправка повідомлення когнкретному клієнту
function handleUnicast(message, sender) {
const recipient = clients.find((client) => client.id === message.to);
if (recipient && recipient.readyState === sender.OPEN) {
recipient.send(JSON.stringify(message));
updateEdge(sender.id, recipient.id);
updateNode(sender.id);
broadcast(JSON.stringify({ type: "update", networkData }));
}
}
// Оновлення розмірів з'єднань між клієнтами
function updateEdge(from, to) {
let edge = networkData.edges.find(
(edge) =>
(edge.from === from && edge.to === to) ||
(edge.from === to && edge.to === from),
);
if (!edge) {
edge = { from, to, value: 1 };
networkData.edges.push(edge);
} else {
edge.value += 1;
}
}
// Оновлення розмірів клієнтів
function updateNode(id) {
const node = networkData.nodes.find((node) => node.id === id);
if (node) {
node.value += 1;
}
}
// Відправка данних графа всім клієнтам
function broadcast(msg, sender) {
clients.forEach((client) => {
if (client !== sender && client.readyState === client.OPEN) {
client.send(msg);
}
});
}
app.listen(3000, () => console.log("Server is listening on port 3000"));
```
### Клієнтська частина (script.js)
Клієнт підключаєтсья до сервера, підписується на зміни графіка, має можливість відправки broadcast і unicast повідомлень
```javascript=
let ws;
let lastSelectValue; // Зберігає значення останнього вибраного типу відправлення
// Встановлення підключення до WebSocket-сервера
function connect() {
const nameInput = document.getElementById("nameInput").value;
if (ws) {
ws.close();
}
ws = new WebSocket(`wss://${window.location.host}/ws?id=${nameInput}`);
// Обробка відкриття з'єднання
ws.onopen = () => {
console.log("Connected to server");
};
// Обробка повідомлень від сервера
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "init") {
ws.id = message.id;
updateNetwork(message.networkData);
updateRecipientSelect(message.networkData.nodes);
} else if (message.type === "update") {
updateNetwork(message.networkData);
updateRecipientSelect(message.networkData.nodes);
} else if (message.type === "broadcast" || message.type === "unicast") {
displayMessage(message);
}
};
// Обробка помилок з'єднання
ws.onerror = (error) => {
updateNetwork({ nodes: [], edges: [] });
console.error("WebSocket Error:", error);
};
// Обробка закриття з'єднання
ws.onclose = () => {
updateNetwork({ nodes: [], edges: [] });
console.log("Disconnected from server");
};
}
// Відпарвка повідомлення
function sendMessage() {
const messageInput = document.getElementById("messageInput");
const recipient = document.getElementById("recipientSelect").value;
lastSelectValue = recipient;
let message = {
type: recipient === "broadcast" ? "broadcast" : "unicast",
from: ws.id,
text: messageInput.value,
};
if (recipient !== "broadcast") {
message.to = recipient;
}
ws.send(JSON.stringify(message));
messageInput.value = "";
}
// Оновлення відображення графа
function updateNetwork(networkData) {
const container = document.getElementById("mynetwork");
const data = {
nodes: new vis.DataSet(networkData.nodes),
edges: new vis.DataSet(networkData.edges),
};
const options = {
nodes: {
shape: "dot",
scaling: {
min: 10,
max: 30,
customScalingFunction: (min, max, total, value) => {
return value / total;
},
},
},
edges: {
smooth: {
type: "continuous",
},
scaling: {
min: 0.05,
max: 10,
customScalingFunction: (min, max, total, value) => {
return Math.asinh(value) / 3;
},
},
},
};
new vis.Network(container, data, options);
}
// Оновлення списку вибору типу відправлення (всім чи конкретному клієнту)
function updateRecipientSelect(nodes) {
const recipientSelect = document.getElementById("recipientSelect");
recipientSelect.innerHTML = '<option value="broadcast">Broadcast</option>';
nodes.forEach((node) => {
if (node.id !== ws.id) {
recipientSelect.innerHTML += `<option value="${node.id}" ${lastSelectValue === node.id ? `selected` : ""}>${node.label}</option>`;
}
});
}
// Відображення повідомлення в чаті
function displayMessage(message) {
const chatBox = document.getElementById("chatBox");
const messageElement = document.createElement("div");
messageElement.textContent = `${message.from}: ${message.text}`;
chatBox.appendChild(messageElement);
}
```
## Приклад виконання коду
5 клієнтів зв'язані між собою. Надіслано багато запитів з 2 та 3 клієнта.

Після перепідключення 5 клієнта і надсилання 1 повідомлення ним.

# Посилання на replit:
https://replit.com/join/pyekoynunm-varvara-viktori