Discrete event model of a Wi-Fi network
The model simulates the operation of a router with a limited number of parallel channels and several client devices that generate network traffic. The simulator demonstrates the fundamental principles of wireless networks, including competition for resources, queuing, and channel loading dynamics
.\ The main elements of our model are described below. Structure Событие It is a fundamental element of the discrete event model and represents a separate occurrence in the system that occurs at a specific time. It stores four main attributes. Field время It contains a timestamp indicating when an event should occur in conditional simulation units, which allows the scheduler to arrange events in chronological order. Field тип defines the event category using a Symbol, such as :генерация_запроса, :запрос or :конец_передачи, which dictates which processing function should be called. Field устройство binds an event to its source or recipient by storing the string identifier of a specific client device (for example, "Laptop_1"). Finally, the field данные It is intended for storing any additional information related to an event, in the form of a tuple of named fields or another type; for example, for a request event, a unique request identifier and the time of its creation can be stored here. This structure is immutable, which guarantees the integrity of the event data after its creation throughout the simulation.
Structure Устройство simulates client hardware on a Wi-Fi network, such as a laptop or smartphone. It is mutable, since its state must be updated during the simulation. The structure includes three fields. Field имя It contains a unique string identifier of the device, for example "Smartphone_2", which is used to associate events and requests with their source. Field интенсивность determines the behavior of the device by storing the average time (in conditional simulation units) between the generation of consecutive data requests; the lower this number, the more often the device creates a load on the network. Field счетчик_запросов It tracks the total number of requests generated by this device since the beginning of the simulation; this value is incremented with each new request and is often used to generate unique identifiers for these requests.
Structure Роутер represents a Wi-Fi hotspot model with limited resources. This is a changeable structure, as the status of channels and queues is constantly changing. It includes three fields. Field каналы defines the maximum bandwidth of the router by storing an integer that indicates the number of parallel connections or channels available for simultaneous data transmission (for example, 2). The field занятые_каналы It tracks the current load, showing how many of the available channels are currently busy processing data transmission. Field очередь it is a vector of strings (Vector{String}), where each string is the identifier of a request (for example, "Laptop_1-03") from a device that tried to start transmitting, but all channels were busy; this vector implements FIFO (First—In, First-Out) logic, where the first The first incoming request will be served in the queue. The structure has a constructor Роутер(каналы::Int) which, when creating a new object, automatically initializes the number of occupied channels to zero and creates an empty queue, ensuring the correct initial state of the model.
Structure ПланировщикСобытий it is the core of the discrete event simulation engine, responsible for managing the time and sequence of all operations. This is a mutable structure. It contains two fields. Field события It is a chronologically ordered collection of all scheduled but not yet processed events, stored as a vector of structures. Событие. To efficiently extract the next event in time, the vector is maintained in a time-sorted state, which allows you to quickly extract the earliest event from the end of the vector. Field текущее_время stores the virtual time point to which the simulation has progressed; this value is updated each time a new event is retrieved and processed, and serves as a global clock for the entire model. The structure has a constructor ПланировщикСобытий(), which initializes an empty list of events and sets the initial simulation time to 0.0. The main simulation loop constantly requests the scheduler for the next event, updates текущее_время at the time of this event, and transmits the event for processing to the appropriate functions, thereby simulating the passage of time in the system.
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
Function добавить_событие! places a new event in the scheduler and sorts the entire list by time in reverse order, ensuring that the earliest event is always at the end of the vector. Function следующее_событие retrieves and returns this earliest event from the end of the vector, and if the list is empty, returns nothing, which signals the end of the simulation.
function добавить_событие!(планировщик::ПланировщикСобытий, событие::Событие)
push!(планировщик.события, событие)
sort!(планировщик.события, by=x->x.время, rev=true)
end
function следующее_событие(планировщик::ПланировщикСобытий)
isempty(планировщик.события) ? nothing : pop!(планировщик.события)
end
Function генерировать_запрос! creates a new network request from the device: it increments the request counter, generates a unique identifier, and immediately schedules an event like :запрос for the current time, and then calculates a random interval (based on the characteristic интенсивность devices) for planning the next event :генерация_запроса, thereby simulating the periodic activity of the device.
Function обработать_запрос receives a data transfer request and tries to allocate a router resource for it. If there is a free channel, it occupies it, calculates a random transmission duration, and schedules an event. :конец_передачи for the future, and displays a success message. If all channels are busy, the function places the request ID in the router queue and displays a message about the standby status.
Function завершить_передачу releases the router channel after the data transfer is completed, displaying the corresponding message. Then it checks for pending requests in the queue: if the queue is not empty, the function retrieves the next request, immediately allocates the newly released channel for it, schedules its completion and outputs a message, thus implementing continuous processing and the FIFO principle (First-In, First-Out).
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
Function запустить_симуляцию He is the main managing director of the program. It initializes all system components: it creates an event scheduler, a router with a set number of channels, and a set number of client devices with random characteristics, after which it schedules the first request generation event for each of them.
Then the function starts the main cycle of discrete event simulation, which continuously extracts the earliest event from the scheduler, advances the virtual time of the system and, depending on the type of event, redirects it to processing one of the three key functions.: генерировать_запрос!, обработать_запрос or завершить_передачу. The loop runs until the specified simulation time expires or until the events run out, after which the function outputs the final network statistics and returns them as a dictionary.
function запустить_симуляцию(длительность=30.0, количество_устройств=4, каналы_роутера=2)
println("="^50)
println("WI-FI NETWORK SIMULATION (PURE JULIA)")
println("="^50)
println("Parameters:")
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("SIMULATION STATISTICS")
println("-"^50)
println("Общее время симуляции: $(round(планировщик.текущее_время, digits=2))")
println("Всего запросов: $(статистика["всего_запросов"])")
println("Передач завершено: $(статистика["передач_завершено"])")
println("Осталось в очереди: $(length(роутер.очередь))")
println("Занято каналов в конце: $(роутер.занятые_каналы)")
println("="^50)
return статистика
end
запустить_симуляцию(25.0, 5, 2)
The simulation demonstrates the operation of the network in a state of periodic overload: with five active devices and only two router channels, a waiting queue is constantly formed in the system, the peak length of which can reach several requests, which indicates a systematic lack of bandwidth for the current load. There is a clear division of operating time into phases: in the initial period, when the queue has not yet been formed, requests are served almost instantly, but as the load accumulates, most devices are forced to spend considerable time waiting for a free channel, and some of them, generating requests with higher intensity, create a disproportionately large load, increasing the average waiting time. for all members of the network. Although the system as a whole copes with processing the vast majority of requests, the presence of a queue by the end of the session and the constant employment of all channels indicate that it is working at the limit of its capabilities, where any increase in the intensity of requests or the number of devices will lead to a sharp increase in delays and a decrease in overall efficiency.
Conclusion
This implementation demonstrates how a fully functional discrete event model can be built on pure Julia without external dependencies. This code provides an excellent basis for experimenting with network algorithms. You can easily modify it to explore different scenarios: add different service classes, device priorities, packet loss models, or real-time visualization.
The main advantage of this implementation is full control over the simulation logic and the absence of dependencies, which makes the code ideal for training and prototyping network models.