---
# System prepended metadata

title: '[2024: Комп''ютерні системи та мережі. Екзаменаційний лист 2.]()'

---

# [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 клієнта. 
![image](https://hackmd.io/_uploads/SJaSkgG7R.png)

Після перепідключення 5 клієнта і надсилання 1 повідомлення ним.
![image](https://hackmd.io/_uploads/SyxgglG7A.png)


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