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 incident 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 Event
Time::Float64
Type::Symbol
Device::String
data::Any
end
mutable struct Device
name::String
intensity::Float64
counter_query::Int
end
mutable struct Router
channels::Int
Busy channels::Int
queue::Vector{String}
function Router(channels::Int)
new(channels, 0, String[])
end
end
mutable structure of event schedulers
events::Vector{Event}
current_time::Float64
Event Scheduler function()
new(Event[], 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_include an event!(planner::Event planner, event::Event)
push!(the planner.events, events)
sort!(the planner.events, by=x->x.time, rev=true)
end
next event function(scheduler::Event Planner)
isempty(scheduler.events) ? nothing : pop!(the planner.events)
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_ generate a query!(device::Device, scheduler::Event SchedulerefErences,
Current time::Float64, router::The router)
device.counter_query += 1
запрос_ид = "$(device.name)-$(lpad(device.counter_query, 2, '0'))"
event_query = Event(
current_time,
:request,
device.name,
(id=request_id, time_query=current time)
)
add_ event!(scheduler, event_query)
next time_ = current time_ + device.intensity * (0.5 + rand())
next event = Event(
next_time,
:generation_query,
device.name,
nothing
)
add_ event!(scheduler, next event)
println(" t=$(round(current time, digits=2)) | $(device.name) | Request created $request_id")
end
function process_query(device::Device, Data, Scheduler::Event SchedulerefErences,
the router::Router, current time::Float64)
request_id = data.identifier
query time_ = data.time_query
if the router.busy channels < router.channels
the router.busy channels += 1
transmission time_ = 0.5 + rand() * 2.0
end time = current time + transfer time
event = Event(
end time_,
:end_transmission,
device.name,
(id=request_id, start time=current time,
transfer time_=transfer time_)
)
add_ event!(planner, event)
println(" t=$(round(current time, digits=2)) | $request_id | Channel received (waiting: $(round(current time is the time of the request, digits=2)))")
else
push!(the router.queue, request_id)
println(" t=$(round(current time, digits=2)) | $request_id | Queued (length: $(length(router.queue)))")
end
end
function end_transmission(device::Device, Data, Scheduler::Event SchedulerefErences,
the router::Router, current time::Float64)
the router.busy channels -= 1
request_id = data.identifier
start time_ = data.start time_
time_transmission = data.time_transmissions
println("t=$(round(current time, digits=2)) | $request_id | Channel is released (transmission: $(round(transmission time_, digits=2)))")
if !isempty(router.queue)
next_query = popfirst!(the router.queue)
имя_устройства = split(следующий_запрос, "-")[1]
transmission time_ = 0.5 + rand() * 2.0
end time = current time + transfer time
event = Event(
end time_,
:end_transmission,
device_name,
(id=next query, start time=current time,
transfer time_=transfer time_)
)
add_ event!(planner, event)
the router.busy channels += 1
println("t=$(round(current time, digits=2)) | $next_query | Has been removed from the queue and transmission has begun")
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, promotes 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 set 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_simulation is started(duration=30.0, number of devices=4, router channels=2)
println("="^50)
println("WI-FI NETWORK SIMULATION (PURE JULIA)")
println("="^50)
println("Parameters:")
println(" Duration: $unit time duration")
println(" Devices: $number of devices")
println(" Router channels: $Router channels")
println("-"^50)
scheduler = Event Planner()
router = Router(Router channels)
device_dict = Dict{String, Device}()
типы_устройств = ["A laptop", "Smartphone", "Tablet", "TV"]
for i in 1:number of devices
type = device type_[mod1(i, length(device type_))]
имя = "$(type)_$i"
intensity = 2.0 + rand() * 3.0
Device = Device(name, intensity, 0)
device_dict[name] = device
println("➕ Added: $name (interval: $(round(intensity, digits=2)))")
first_existence = Event(
rand() * 1.0,
:generation_query,
name,
nothing
)
add_ event!(planner, first_event)
end
println("-"^50)
println("Running the simulation...\n")
statistics = Dict{String, Int}()
статистика["all_questions"] = 0
статистика["transfer_completed"] = 0
while the scheduler.current_time < duration
event = next event(scheduler)
if event === nothing
break
end
the planner.current_time = event.time
if the scheduler.current_time > duration
break
end
if !haskey(device_dict, event.device)
continue
end
device = device_dict[event.device]
if event.type == :generation_query
Generate a query!(device, scheduler, scheduler.current time, router)
статистика["all_questions"] += 1
elseif event.type == :request
process_query(device, event.data, scheduler, router, scheduler.current_time)
elseif event.type == :end_transmission
end_transmission(device, event.data, scheduler, router, scheduler.current_time)
статистика["transfer_completed"] += 1
end
end
println("\n" * "-"^50)
println("SIMULATION STATISTICS")
println("-"^50)
println("Total simulation time: $(round(scheduler.current_time, digits=2))")
println("Total requests: $(statistics["всего_запросов"])")
println("Transfers completed: $(statistics["передач_завершено"])")
println("Remaining in the queue: $(length(router.queue))")
println("Number of channels used at the end: $(router.busy channels)")
println("="^50)
return statistics
end
run_simulation(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 forming 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.