Сообщество Engee

Модель Wi-Fi сети

Автор
avatar-yurevyurev
Notebook

Дискретно-событийная модель Wi-Fi сети

Модель имитирует работу роутера с ограниченным числом параллельных каналов и несколько клиентских устройств, которые генерируют сетевой трафик. Симулятор демонстрирует фундаментальные принципы работы беспроводных сетей, включая конкуренцию за ресурсы, формирование очередей и динамику загрузки каналов.
Ниже описаны основные элементы нашей модели. Структура Событие является фундаментальным элементом дискретно-событийной модели и представляет собой отдельное происшествие в системе, которое происходит в конкретный момент времени. Она хранит четыре основных атрибута. Поле время содержит метку времени, указывающую, когда событие должно произойти в условных единицах симуляции, что позволяет планировщику упорядочивать события в хронологической последовательности. Поле тип определяет категорию события с помощью символа (Symbol), такого как :генерация_запроса, :запрос или :конец_передачи, что диктует, какую функцию обработки следует вызвать. Поле устройство связывает событие с его источником или получателем, храня строковый идентификатор конкретного клиентского устройства (например, "Ноутбук_1"). Наконец, поле данные предназначено для хранения любой дополнительной информации, связанной с событием, в виде кортежа именованных полей или другого типа; например, для события запроса здесь могут храниться уникальный идентификатор запроса и время его создания. Эта структура является неизменяемой (immutable), что гарантирует целостность данных о событии после его создания на протяжении всей симуляции.

Структура Устройство моделирует клиентское оборудование в сети Wi-Fi, например, ноутбук или смартфон. Она является изменяемой (mutable), так как её состояние должно обновляться в ходе симуляции. Структура включает три поля. Поле имя содержит уникальный строковый идентификатор устройства, например "Смартфон_2", который используется для связывания событий и запросов с их источником. Поле интенсивность определяет поведение устройства, храня среднее время (в условных единицах симуляции) между генерацией последовательных запросов на передачу данных; чем меньше это число, тем чаще устройство создает нагрузку на сеть. Поле счетчик_запросов отслеживает общее количество запросов, сгенерированных данным устройством с начала симуляции; это значение инкрементируется при каждом новом запросе и часто используется для формирования уникальных идентификаторов этих запросов.

Структура Роутер представляет собой модель точки доступа Wi-Fi с ограниченными ресурсами. Это изменяемая структура, так как состояние каналов и очереди постоянно меняется. Она включает три поля. Поле каналы определяет максимальную пропускную способность роутера, храня целое число, которое обозначает количество параллельных соединений или каналов, доступных для одновременной передачи данных (например, 2). Поле занятые_каналы отслеживает текущую нагрузку, показывая, сколько из доступных каналов в данный момент времени занято обработкой передачи данных. Поле очередь представляет собой вектор строк (Vector{String}), где каждая строка — это идентификатор запроса (например, "Ноутбук_1-03") от устройства, которое пыталось начать передачу, но все каналы были заняты; этот вектор реализует логику FIFO (First-In, First-Out), где первым в очереди будет обслужен первый пришедший запрос. Структура имеет конструктор Роутер(каналы::Int), который при создании нового объекта автоматически инициализирует количество занятых каналов нулем и создает пустую очередь, обеспечивая корректное начальное состояние модели.

Структура ПланировщикСобытий является ядром движка дискретно-событийной симуляции, отвечающим за управление временем и последовательностью выполнения всех операций. Это изменяемая структура. Она содержит два поля. Поле события представляет собой хронологически упорядоченную коллекцию всех запланированных, но еще не обработанных событий, хранящуюся в виде вектора структур Событие. Для эффективного извлечения следующего по времени события вектор поддерживается в отсортированном по убыванию времени состоянии, что позволяет быстро извлекать самое раннее событие с конца вектора. Поле текущее_время хранит момент виртуального времени, до которого продвинулась симуляция; это значение обновляется каждый раз, когда извлекается и обрабатывается новое событие, и служит глобальными часами для всей модели. Структура имеет конструктор ПланировщикСобытий(), который инициализирует пустой список событий и устанавливает начальное время симуляции в 0.0. Основной цикл симуляции постоянно запрашивает у планировщика следующее событие, обновляет текущее_время на время этого события и передает событие на обработку соответствующим функциям, имитируя тем самым течение времени в системе.

In [ ]:
struct Событие
    время::Float64
    тип::Symbol
    устройство::String
    данные::Any
end

mutable struct Устройство
    имя::String
    интенсивность::Float64
    счетчик_запросов::Int
end

mutable struct Роутер
    каналы::Int
    занятые_каналы::Int
    очередь::Vector{String}
    
    function Роутер(каналы::Int)
        new(каналы, 0, String[])
    end
end

mutable struct ПланировщикСобытий
    события::Vector{Событие}
    текущее_время::Float64
    
    function ПланировщикСобытий()
        new(Событие[], 0.0)
    end
end

Функция добавить_событие! помещает новое событие в планировщик и сортирует весь список по времени в обратном порядке, обеспечивая, что самое раннее событие всегда находится в конце вектора. Функция следующее_событие извлекает и возвращает это самое раннее событие из конца вектора, а если список пуст — возвращает nothing, что сигнализирует об окончании симуляции.

In [ ]:
function добавить_событие!(планировщик::ПланировщикСобытий, событие::Событие)
    push!(планировщик.события, событие)
    sort!(планировщик.события, by=x->x.время, rev=true)
end

function следующее_событие(планировщик::ПланировщикСобытий)
    isempty(планировщик.события) ? nothing : pop!(планировщик.события)
end

Функция генерировать_запрос! создает новый сетевой запрос от устройства: она увеличивает счетчик запросов, формирует уникальный идентификатор, немедленно планирует событие типа :запрос на текущее время, а затем вычисляет случайный интервал (основанный на характеристике интенсивность устройства) для планирования следующего события :генерация_запроса, тем самым моделируя периодическую активность устройства.

Функция обработать_запрос получает запрос на передачу данных и пытается выделить для него ресурс роутера. Если есть свободный канал, она занимает его, вычисляет случайную длительность передачи, планирует событие :конец_передачи на будущее время и выводит сообщение об успехе. Если все каналы заняты, функция помещает идентификатор запроса в очередь роутера и выводит сообщение о состоянии ожидания.

Функция завершить_передачу освобождает канал роутера после завершения передачи данных, выводя соответствующее сообщение. Затем она проверяет наличие ожидающих запросов в очереди: если очередь не пуста, функция извлекает следующий запрос, немедленно выделяет для него только что освободившийся канал, планирует его завершение и выводит сообщение, реализуя таким образом непрерывную обработку и принцип FIFO (First-In, First-Out).

In [ ]:
function генерировать_запрос!(устройство::Устройство, планировщик::ПланировщикСобытий, 
                              текущее_время::Float64, роутер::Роутер)
    устройство.счетчик_запросов += 1
    запрос_ид = "$(устройство.имя)-$(lpad(устройство.счетчик_запросов, 2, '0'))"
    
    событие_запрос = Событие(
        текущее_время,
        :запрос,
        устройство.имя,
        (идентификатор=запрос_ид, время_запроса=текущее_время)
    )
    
    добавить_событие!(планировщик, событие_запрос)
    
    следующее_время = текущее_время + устройство.интенсивность * (0.5 + rand())
    следующее_событие = Событие(
        следующее_время,
        :генерация_запроса,
        устройство.имя,
        nothing
    )
    
    добавить_событие!(планировщик, следующее_событие)
    
    println("⏰ t=$(round(текущее_время, digits=2)) | $(устройство.имя) | Создан запрос $запрос_ид")
end

function обработать_запрос(устройство::Устройство, данные, планировщик::ПланировщикСобытий, 
                          роутер::Роутер, текущее_время::Float64)
    запрос_ид = данные.идентификатор
    время_запроса = данные.время_запроса
    
    if роутер.занятые_каналы < роутер.каналы
        роутер.занятые_каналы += 1
        
        время_передачи = 0.5 + rand() * 2.0
        время_окончания = текущее_время + время_передачи
        
        событие = Событие(
            время_окончания,
            :конец_передачи,
            устройство.имя,
            (идентификатор=запрос_ид, время_начала=текущее_время, 
             время_передачи=время_передачи)
        )
        
        добавить_событие!(планировщик, событие)
        
        println("✅ t=$(round(текущее_время, digits=2)) | $запрос_ид | Канал получен (ожидание: $(round(текущее_время-время_запроса, digits=2)))")
    else
        push!(роутер.очередь, запрос_ид)
        println("⌛ t=$(round(текущее_время, digits=2)) | $запрос_ид | Поставлен в очередь (длина: $(length(роутер.очередь)))")
    end
end

function завершить_передачу(устройство::Устройство, данные, планировщик::ПланировщикСобытий, 
                          роутер::Роутер, текущее_время::Float64)
    роутер.занятые_каналы -= 1
    
    запрос_ид = данные.идентификатор
    время_начала = данные.время_начала
    время_передачи = данные.время_передачи
    
    println("🔓 t=$(round(текущее_время, digits=2)) | $запрос_ид | Канал освобожден (передача: $(round(время_передачи, digits=2)))")
    
    if !isempty(роутер.очередь)
        следующий_запрос = popfirst!(роутер.очередь)
        имя_устройства = split(следующий_запрос, "-")[1]
        
        время_передачи = 0.5 + rand() * 2.0
        время_окончания = текущее_время + время_передачи
        
        событие = Событие(
            время_окончания,
            :конец_передачи,
            имя_устройства,
            (идентификатор=следующий_запрос, время_начала=текущее_время,
             время_передачи=время_передачи)
        )
        
        добавить_событие!(планировщик, событие)
        
        роутер.занятые_каналы += 1
        println("➡️  t=$(round(текущее_время, digits=2)) | $следующий_запрос | Извлечен из очереди и начал передачу")
    end
end

Функция запустить_симуляцию является главным управляющим центром программы. Она инициализирует все компоненты системы: создает планировщик событий, роутер с заданным числом каналов и заданное количество клиентских устройств со случайными характеристиками, после чего планирует для каждого из них первое событие генерации запроса.

Затем функция запускает основной цикл дискретно-событийной симуляции, который непрерывно извлекает из планировщика самое раннее событие, продвигает виртуальное время системы и, в зависимости от типа события, перенаправляет его на обработку одной из трех ключевых функций: генерировать_запрос!, обработать_запрос или завершить_передачу. Цикл выполняется до истечения заданного времени симуляции или пока не закончатся события, после чего функция выводит итоговую статистику работы сети и возвращает её в виде словаря.

In [ ]:
function запустить_симуляцию(длительность=30.0, количество_устройств=4, каналы_роутера=2)
    println("="^50)
    println("СИМУЛЯЦИЯ WI-FI СЕТИ (ЧИСТЫЙ JULIA)")
    println("="^50)
    println("Параметры:")
    println("  Длительность: $длительность ед. времени")
    println("  Устройства: $количество_устройств")
    println("  Каналы роутера: $каналы_роутера")
    println("-"^50)
    
    планировщик = ПланировщикСобытий()
    роутер = Роутер(каналы_роутера)
    
    устройства_dict = Dict{String, Устройство}()
    типы_устройств = ["Ноутбук", "Смартфон", "Планшет", "ТВ"]
    
    for i in 1:количество_устройств
        тип = типы_устройств[mod1(i, length(типы_устройств))]
        имя = "$(тип)_$i"
        интенсивность = 2.0 + rand() * 3.0
        устройство = Устройство(имя, интенсивность, 0)
        устройства_dict[имя] = устройство
        println("➕ Добавлено: $имя (интервал: $(round(интенсивность, digits=2)))")
        
        первое_событие = Событие(
            rand() * 1.0,
            :генерация_запроса,
            имя,
            nothing
        )
        добавить_событие!(планировщик, первое_событие)
    end
    
    println("-"^50)
    println("Запуск симуляции...\n")
    
    статистика = Dict{String, Int}()
    статистика["всего_запросов"] = 0
    статистика["передач_завершено"] = 0
    
    while планировщик.текущее_время < длительность
        событие = следующее_событие(планировщик)
        
        if событие === nothing
            break
        end
        
        планировщик.текущее_время = событие.время
        
        if планировщик.текущее_время > длительность
            break
        end
        
        if !haskey(устройства_dict, событие.устройство)
            continue
        end
        
        устройство = устройства_dict[событие.устройство]
        
        if событие.тип == :генерация_запроса
            генерировать_запрос!(устройство, планировщик, планировщик.текущее_время, роутер)
            статистика["всего_запросов"] += 1
            
        elseif событие.тип == :запрос
            обработать_запрос(устройство, событие.данные, планировщик, роутер, планировщик.текущее_время)
            
        elseif событие.тип == :конец_передачи
            завершить_передачу(устройство, событие.данные, планировщик, роутер, планировщик.текущее_время)
            статистика["передач_завершено"] += 1
        end
    end
    
    println("\n" * "-"^50)
    println("СТАТИСТИКА СИМУЛЯЦИИ")
    println("-"^50)
    println("Общее время симуляции: $(round(планировщик.текущее_время, digits=2))")
    println("Всего запросов: $(статистика["всего_запросов"])")
    println("Передач завершено: $(статистика["передач_завершено"])")
    println("Осталось в очереди: $(length(роутер.очередь))")
    println("Занято каналов в конце: $(роутер.занятые_каналы)")
    println("="^50)
    
    return статистика
end
In [ ]:
запустить_симуляцию(25.0, 5, 2)
==================================================
СИМУЛЯЦИЯ WI-FI СЕТИ (ЧИСТЫЙ JULIA)
==================================================
Параметры:
  Длительность: 25.0 ед. времени
  Устройства: 5
  Каналы роутера: 2
--------------------------------------------------
➕ Добавлено: Ноутбук_1 (интервал: 2.53)
➕ Добавлено: Смартфон_2 (интервал: 4.12)
➕ Добавлено: Планшет_3 (интервал: 4.73)
➕ Добавлено: ТВ_4 (интервал: 3.34)
➕ Добавлено: Ноутбук_5 (интервал: 4.24)
--------------------------------------------------
Запуск симуляции...

⏰ t=0.03 | Ноутбук_1 | Создан запрос Ноутбук_1-01
✅ t=0.03 | Ноутбук_1-01 | Канал получен (ожидание: 0.0)
⏰ t=0.1 | Планшет_3 | Создан запрос Планшет_3-01
✅ t=0.1 | Планшет_3-01 | Канал получен (ожидание: 0.0)
⏰ t=0.14 | Ноутбук_5 | Создан запрос Ноутбук_5-01
⌛ t=0.14 | Ноутбук_5-01 | Поставлен в очередь (длина: 1)
⏰ t=0.36 | Смартфон_2 | Создан запрос Смартфон_2-01
⌛ t=0.36 | Смартфон_2-01 | Поставлен в очередь (длина: 2)
⏰ t=0.42 | ТВ_4 | Создан запрос ТВ_4-01
⌛ t=0.42 | ТВ_4-01 | Поставлен в очередь (длина: 3)
🔓 t=1.17 | Планшет_3-01 | Канал освобожден (передача: 1.07)
➡️  t=1.17 | Ноутбук_5-01 | Извлечен из очереди и начал передачу
🔓 t=2.32 | Ноутбук_1-01 | Канал освобожден (передача: 2.3)
➡️  t=2.32 | Смартфон_2-01 | Извлечен из очереди и начал передачу
⏰ t=2.68 | Смартфон_2 | Создан запрос Смартфон_2-02
⌛ t=2.68 | Смартфон_2-02 | Поставлен в очередь (длина: 2)
⏰ t=2.73 | ТВ_4 | Создан запрос ТВ_4-02
⌛ t=2.73 | ТВ_4-02 | Поставлен в очередь (длина: 3)
🔓 t=2.78 | Ноутбук_5-01 | Канал освобожден (передача: 1.6)
➡️  t=2.78 | ТВ_4-01 | Извлечен из очереди и начал передачу
⏰ t=2.79 | Планшет_3 | Создан запрос Планшет_3-02
⌛ t=2.79 | Планшет_3-02 | Поставлен в очередь (длина: 3)
🔓 t=3.29 | ТВ_4-01 | Канал освобожден (передача: 0.51)
➡️  t=3.29 | Смартфон_2-02 | Извлечен из очереди и начал передачу
⏰ t=3.67 | Ноутбук_1 | Создан запрос Ноутбук_1-02
⌛ t=3.67 | Ноутбук_1-02 | Поставлен в очередь (длина: 3)
🔓 t=4.79 | Смартфон_2-01 | Канал освобожден (передача: 2.46)
➡️  t=4.79 | ТВ_4-02 | Извлечен из очереди и начал передачу
🔓 t=5.36 | Смартфон_2-02 | Канал освобожден (передача: 2.07)
➡️  t=5.36 | Планшет_3-02 | Извлечен из очереди и начал передачу
🔓 t=5.71 | ТВ_4-02 | Канал освобожден (передача: 0.93)
➡️  t=5.71 | Ноутбук_1-02 | Извлечен из очереди и начал передачу
⏰ t=5.78 | Ноутбук_5 | Создан запрос Ноутбук_5-02
⌛ t=5.78 | Ноутбук_5-02 | Поставлен в очередь (длина: 1)
⏰ t=6.02 | ТВ_4 | Создан запрос ТВ_4-03
⌛ t=6.02 | ТВ_4-03 | Поставлен в очередь (длина: 2)
🔓 t=6.34 | Ноутбук_1-02 | Канал освобожден (передача: 0.62)
➡️  t=6.34 | Ноутбук_5-02 | Извлечен из очереди и начал передачу
⏰ t=6.73 | Ноутбук_1 | Создан запрос Ноутбук_1-03
⌛ t=6.73 | Ноутбук_1-03 | Поставлен в очередь (длина: 2)
🔓 t=7.41 | Планшет_3-02 | Канал освобожден (передача: 2.05)
➡️  t=7.41 | ТВ_4-03 | Извлечен из очереди и начал передачу
⏰ t=7.81 | ТВ_4 | Создан запрос ТВ_4-04
⌛ t=7.81 | ТВ_4-04 | Поставлен в очередь (длина: 2)
🔓 t=8.0 | Ноутбук_5-02 | Канал освобожден (передача: 1.67)
➡️  t=8.0 | Ноутбук_1-03 | Извлечен из очереди и начал передачу
⏰ t=8.34 | Смартфон_2 | Создан запрос Смартфон_2-03
⌛ t=8.34 | Смартфон_2-03 | Поставлен в очередь (длина: 2)
⏰ t=8.77 | Ноутбук_1 | Создан запрос Ноутбук_1-04
⌛ t=8.77 | Ноутбук_1-04 | Поставлен в очередь (длина: 3)
⏰ t=8.82 | Планшет_3 | Создан запрос Планшет_3-03
⌛ t=8.82 | Планшет_3-03 | Поставлен в очередь (длина: 4)
🔓 t=9.03 | Ноутбук_1-03 | Канал освобожден (передача: 1.03)
➡️  t=9.03 | ТВ_4-04 | Извлечен из очереди и начал передачу
🔓 t=9.18 | ТВ_4-03 | Канал освобожден (передача: 1.77)
➡️  t=9.18 | Смартфон_2-03 | Извлечен из очереди и начал передачу
⏰ t=9.24 | Ноутбук_5 | Создан запрос Ноутбук_5-03
⌛ t=9.24 | Ноутбук_5-03 | Поставлен в очередь (длина: 3)
🔓 t=9.54 | ТВ_4-04 | Канал освобожден (передача: 0.51)
➡️  t=9.54 | Ноутбук_1-04 | Извлечен из очереди и начал передачу
⏰ t=10.5 | Смартфон_2 | Создан запрос Смартфон_2-04
⌛ t=10.5 | Смартфон_2-04 | Поставлен в очередь (длина: 3)
🔓 t=10.93 | Ноутбук_1-04 | Канал освобожден (передача: 1.39)
➡️  t=10.93 | Планшет_3-03 | Извлечен из очереди и начал передачу
🔓 t=11.14 | Смартфон_2-03 | Канал освобожден (передача: 1.96)
➡️  t=11.14 | Ноутбук_5-03 | Извлечен из очереди и начал передачу
⏰ t=11.31 | ТВ_4 | Создан запрос ТВ_4-05
⌛ t=11.31 | ТВ_4-05 | Поставлен в очередь (длина: 2)
🔓 t=11.85 | Ноутбук_5-03 | Канал освобожден (передача: 0.71)
➡️  t=11.85 | Смартфон_2-04 | Извлечен из очереди и начал передачу
⏰ t=12.11 | Ноутбук_1 | Создан запрос Ноутбук_1-05
⌛ t=12.11 | Ноутбук_1-05 | Поставлен в очередь (длина: 2)
🔓 t=12.88 | Планшет_3-03 | Канал освобожден (передача: 1.95)
➡️  t=12.88 | ТВ_4-05 | Извлечен из очереди и начал передачу
🔓 t=13.06 | Смартфон_2-04 | Канал освобожден (передача: 1.21)
➡️  t=13.06 | Ноутбук_1-05 | Извлечен из очереди и начал передачу
🔓 t=13.46 | ТВ_4-05 | Канал освобожден (передача: 0.57)
⏰ t=13.68 | Планшет_3 | Создан запрос Планшет_3-04
✅ t=13.68 | Планшет_3-04 | Канал получен (ожидание: 0.0)
🔓 t=13.86 | Ноутбук_1-05 | Канал освобожден (передача: 0.8)
⏰ t=14.62 | Ноутбук_5 | Создан запрос Ноутбук_5-04
✅ t=14.62 | Ноутбук_5-04 | Канал получен (ожидание: 0.0)
🔓 t=14.69 | Планшет_3-04 | Канал освобожден (передача: 1.01)
⏰ t=15.36 | ТВ_4 | Создан запрос ТВ_4-06
✅ t=15.36 | ТВ_4-06 | Канал получен (ожидание: 0.0)
⏰ t=15.66 | Ноутбук_1 | Создан запрос Ноутбук_1-06
⌛ t=15.66 | Ноутбук_1-06 | Поставлен в очередь (длина: 1)
⏰ t=15.99 | Смартфон_2 | Создан запрос Смартфон_2-05
⌛ t=15.99 | Смартфон_2-05 | Поставлен в очередь (длина: 2)
🔓 t=16.43 | ТВ_4-06 | Канал освобожден (передача: 1.07)
➡️  t=16.43 | Ноутбук_1-06 | Извлечен из очереди и начал передачу
🔓 t=16.48 | Ноутбук_5-04 | Канал освобожден (передача: 1.86)
➡️  t=16.48 | Смартфон_2-05 | Извлечен из очереди и начал передачу
🔓 t=17.32 | Ноутбук_1-06 | Канал освобожден (передача: 0.89)
⏰ t=17.43 | Планшет_3 | Создан запрос Планшет_3-05
✅ t=17.43 | Планшет_3-05 | Канал получен (ожидание: 0.0)
⏰ t=17.57 | Ноутбук_1 | Создан запрос Ноутбук_1-07
⌛ t=17.57 | Ноутбук_1-07 | Поставлен в очередь (длина: 1)
🔓 t=18.84 | Смартфон_2-05 | Канал освобожден (передача: 2.36)
➡️  t=18.84 | Ноутбук_1-07 | Извлечен из очереди и начал передачу
🔓 t=19.02 | Планшет_3-05 | Канал освобожден (передача: 1.59)
⏰ t=19.06 | Ноутбук_5 | Создан запрос Ноутбук_5-05
✅ t=19.06 | Ноутбук_5-05 | Канал получен (ожидание: 0.0)
⏰ t=20.06 | ТВ_4 | Создан запрос ТВ_4-07
⌛ t=20.06 | ТВ_4-07 | Поставлен в очередь (длина: 1)
⏰ t=20.28 | Смартфон_2 | Создан запрос Смартфон_2-06
⌛ t=20.28 | Смартфон_2-06 | Поставлен в очередь (длина: 2)
🔓 t=20.29 | Ноутбук_5-05 | Канал освобожден (передача: 1.23)
➡️  t=20.29 | ТВ_4-07 | Извлечен из очереди и начал передачу
⏰ t=20.38 | Ноутбук_1 | Создан запрос Ноутбук_1-08
⌛ t=20.38 | Ноутбук_1-08 | Поставлен в очередь (длина: 2)
🔓 t=20.88 | Ноутбук_1-07 | Канал освобожден (передача: 2.04)
➡️  t=20.88 | Смартфон_2-06 | Извлечен из очереди и начал передачу
⏰ t=21.66 | Планшет_3 | Создан запрос Планшет_3-06
⌛ t=21.66 | Планшет_3-06 | Поставлен в очередь (длина: 2)
🔓 t=21.85 | ТВ_4-07 | Канал освобожден (передача: 1.55)
➡️  t=21.85 | Ноутбук_1-08 | Извлечен из очереди и начал передачу
⏰ t=22.03 | Ноутбук_5 | Создан запрос Ноутбук_5-06
⌛ t=22.03 | Ноутбук_5-06 | Поставлен в очередь (длина: 2)
⏰ t=22.45 | Ноутбук_1 | Создан запрос Ноутбук_1-09
⌛ t=22.45 | Ноутбук_1-09 | Поставлен в очередь (длина: 3)
⏰ t=22.6 | ТВ_4 | Создан запрос ТВ_4-08
⌛ t=22.6 | ТВ_4-08 | Поставлен в очередь (длина: 4)
🔓 t=22.74 | Смартфон_2-06 | Канал освобожден (передача: 1.86)
➡️  t=22.74 | Планшет_3-06 | Извлечен из очереди и начал передачу
🔓 t=23.3 | Планшет_3-06 | Канал освобожден (передача: 0.56)
➡️  t=23.3 | Ноутбук_5-06 | Извлечен из очереди и начал передачу
🔓 t=23.72 | Ноутбук_1-08 | Канал освобожден (передача: 1.88)
➡️  t=23.72 | Ноутбук_1-09 | Извлечен из очереди и начал передачу
⏰ t=24.47 | ТВ_4 | Создан запрос ТВ_4-09
⌛ t=24.47 | ТВ_4-09 | Поставлен в очередь (длина: 2)
🔓 t=24.52 | Ноутбук_5-06 | Канал освобожден (передача: 1.22)
➡️  t=24.52 | ТВ_4-08 | Извлечен из очереди и начал передачу
⏰ t=24.52 | Смартфон_2 | Создан запрос Смартфон_2-07
⌛ t=24.52 | Смартфон_2-07 | Поставлен в очередь (длина: 2)

--------------------------------------------------
СТАТИСТИКА СИМУЛЯЦИИ
--------------------------------------------------
Общее время симуляции: 25.48
Всего запросов: 37
Передач завершено: 33
Осталось в очереди: 2
Занято каналов в конце: 2
==================================================
Out[0]:
Dict{String, Int64} with 2 entries:
  "передач_завершено" => 33
  "всего_запросов"    => 37

Симуляция демонстрирует работу сети в состоянии периодической перегрузки: при пяти активных устройствах и всего двух каналах роутера в системе постоянно формируется очередь ожидания, пиковая длина которой может достигать нескольких запросов, что указывает на систематическую нехватку пропускной способности для текущей нагрузки. Наблюдается явное разделение времени работы на фазы: в начальный период, когда очередь еще не сформирована, запросы обслуживаются практически мгновенно, но по мере накопления нагрузки большинство устройств вынуждено проводить значительное время в ожидании свободного канала, причем некоторые из них, генерирующие запросы с более высокой интенсивностью, создают непропорционально большую нагрузку, увеличивая среднее время ожидания для всех участников сети. Хотя система в целом справляется с обработкой подавляющего большинства запросов, наличие очереди к концу сеанса и постоянная занятость всех каналов свидетельствуют о работе на пределе возможностей, где любое увеличение интенсивности запросов или количества устройств приведет к резкому росту задержек и снижению общей эффективности.

Вывод

Эта реализация демонстрирует, как можно построить полнофункциональную дискретно-событийную модель на чистом Julia без внешних зависимостей. Этот код служит отличной основой для экспериментов с сетевыми алгоритмами. Вы можете легко модифицировать его для исследования различных сценариев: добавить разные классы обслуживания, приоритеты устройств, модели потери пакетов или визуализацию в реальном времени.

Главное преимущество такой реализации — полный контроль над логикой симуляции и отсутствие зависимостей, что делает код идеальным для обучения и прототипирования сетевых моделей.