В этой статье мы рассмотрим основные принципы работы MAC (Media Access Control) уровня на языке Engee, включая формирование кадров запроса и ответа между станциями. MAC-уровень отвечает за управление доступом к среде передачи данных и обеспечивает:
Реализацию примера мы начнём с объявления структуры пакетов MacHeader и MacFrame, эти структуры представляют стандартный формат кадров в беспроводных сетях 802.11 (Wi-Fi). В реальных реализациях могут быть дополнительные поля или вариации в зависимости от конкретного типа кадра (управляющие, контрольные или кадры данных). Наш код корректен для ad-hoc сети, где addr3 может быть любым, например, "00:00:00:00:00:00", но FCS в примере считается только по телу кадра.
Структура MacHeader (Заголовок MAC-кадра)
| Поле | Тип | Назначение |
|---|---|---|
frame_control |
UInt16 |
Управляющее поле (тип кадра, флаги, версия протокола) |
duration |
UInt16 |
Время резервирования канала (μs) для NAV (Virtual Carrier Sense) |
addr1 |
String |
Receiver Address (RA) — MAC получателя. |
addr2 |
String |
Transmitter Address (TA) — MAC отправителя. |
addr3 |
String |
BSSID (в инфраструктурных сетях — MAC точки доступа, в ad-hoc — идентификатор группы). |
seq_ctrl |
UInt16 |
Управление последовательностью |
Кратко о назначении адресов
addr1 (RA) — Кому кадр направлен (получатель).addr2 (TA) — Кто кадр отправил.addr3 (BSSID) — Идентификатор сети (для инфраструктурных сетей — MAC точки доступа).Структура MacFrame (Весь MAC-кадр)
| Поле | Тип | Назначение |
|---|---|---|
header |
MacHeader |
Заголовок кадра (см. выше). |
body |
Vector{UInt8} |
Полезная нагрузка (данные). |
fcs |
UInt32 |
Frame Check Sequence (CRC-32) — контрольная сумма для проверки целостности. |
struct MacHeader
frame_control::UInt16
duration::UInt16
addr1::String
addr2::String
addr3::String
seq_ctrl::UInt16
end
struct MacFrame
header::MacHeader
body::Vector{UInt8}
fcs::UInt32
end
Теперь перейдём к формированию кадра запроса. (Request Frame) , он состоит из функции crc32 и create_request_frame.
Первая функция имитирует расчёт CRC-32 (контрольной суммы) для данных, но на самом деле использует упрощённый алгоритм: суммирует все байты в массиве и ограничивает результат 32-битным числом (как в настоящем CRC-32). Настоящий CRC-32 использует полиномиальное деление (например, 0xEDB88320).
Вторая функция создаёт MAC-кадр запроса (например, для передачи данных в сети Wi-Fi), давайте по шагам разберём работу данной функции.
**frame_control = 0x00b4 **
Указывает, что это кадр данных (тип 0b10) с флагами по умолчанию. В реальных сетях здесь могут быть флаги защиты, фрагментации и др.
duration = UInt16(0)
Время резервирования канала (0 — кадр короткий, NAV не используется).
seq_num = rand(UInt16)
Случайный 16-битный номер последовательности (в реальности должен инкрементироваться).
Создание заголовка (MacHeader)
addr1 — MAC получателя (receiver_addr).addr2 — MAC отправителя (sender_addr).addr3 — BSSID (в этом примере 00:00:00:00:00:00, что означает ad-hoc сеть).frame_body = Vector{UInt8}(payload)
Преобразует строку payload в массив байт.
fcs = crc32(frame_body)
Вычисляет "контрольную сумму" (упрощённую) для тела кадра.
Возвращает MacFrame - готовый кадр с заголовком, данными и FCS.
function crc32(data::Vector{UInt8})
return UInt32(sum(data) % UInt32(0xFFFFFFFF))
end
function create_request_frame(sender_addr, receiver_addr, payload)
frame_control = 0x00b4
duration = UInt16(0)
seq_num = rand(UInt16)
header = MacHeader(frame_control, duration, receiver_addr, sender_addr, "00:00:00:00:00:00", seq_num)
frame_body = Vector{UInt8}(payload)
fcs = crc32(frame_body)
MacFrame(header, frame_body, fcs)
end
Далее сформируем кадр ответа. (Response Frame), сам кадр включает в себя:
Заголовок кадра (MAC Header)
Полезная нагрузка (Payload), преобразует строку payload в байты (Vector{UInt8}).
Контрольная сумма (FCS), вычисляет CRC32 от тела кадра.
Возвращает готовый MacFrame с заголовком, телом и FCS.
function create_response_frame(sender_addr, receiver_addr, request_frame, payload)
frame_control = 0x00c4
new_duration = max(0, Int(request_frame.header.duration) - 100)
header = MacHeader(frame_control, UInt16(new_duration), receiver_addr, sender_addr, "00:00:00:00:00:00", request_frame.header.seq_ctrl + UInt16(1))
frame_body = Vector{UInt8}(payload)
fcs = crc32(frame_body)
MacFrame(header, frame_body, fcs)
end
После этого можно переходить к реализации алгоритма взаимодействия между станциями, этот тест имитирует обмен MAC-кадрами между двумя станциями (A и B) в сети.
А - отправляет запрос
B - отправляет ответ
station_a = "AA:BB:CC:DD:EE:FF"
station_b = "11:22:33:44:55:66"
request_payload = "Запрос данных от A"
request_frame = create_request_frame(station_a, station_b, request_payload)
println("Станция A отправляет кадр запроса:")
println("Кому: ", request_frame.header.addr1)
println("От: ", request_frame.header.addr2)
println("Полезная нагрузка: ", String(request_frame.body))
println("Контрольная сумма FCS: ", request_frame.fcs)
println()
response_payload = "Ответ с данными от B"
response_frame = create_response_frame(station_b, station_a, request_frame, response_payload)
println("Станция B отправляет кадр ответа:")
println("Кому: ", response_frame.header.addr1)
println("От: ", response_frame.header.addr2)
println("Полезная нагрузка: ", String(response_frame.body))
println("Контрольная сумма FCS: ", response_frame.fcs)
CSMA/CA — это протокол управления доступом к среде передачи данных, используемый в беспроводных сетях (Wi-Fi, IEEE 802.11). В отличие от CSMA/CD (Ethernet), где коллизии обнаруживаются, CSMA/CA предотвращает их за счёт прослушивания канала перед передачей (Carrier Sense), случайных задержек (Backoff) при обнаружении занятости и подтверждений (ACK) для проверки доставки, реализован данный пример на основе раннее разобранной реализации.
Алгоритм работы
Проверка канала (CCA — Clear Channel Assessment)
Механизм Backoff
Подтверждение (ACK)
Повторные попытки
max_attempts).Далее перейдём к реализация и первая функция - это csma_ca_send, она имитирует занятость канала (rand() < 0.3 — 30% вероятность занятости), включает в себя Backoff-таймер (rand(1:10) * 10 мс) при занятом канале и лимит попыток (max_attempts=3).
function csma_ca_send(sender, receiver, payload, max_attempts=3)
attempt = 1
while attempt <= max_attempts
channel_busy = rand() < 0.3
if !channel_busy
frame = create_request_frame(sender, receiver, payload)
println("Попытка $attempt: Кадр отправлен от $sender к $receiver")
return frame
else
backoff_time = rand(1:10) * 10
println("Попытка $attempt: Канал занят, ожидание $backoff_time мс")
sleep(backoff_time / 1000)
attempt += 1
end
end
println("Не удалось отправить после $max_attempts попыток")
return nothing
end
Вторая функция - это create_request_frame она включает в себя упрощённый заголовок (без реального FCS, вместо него — длина payload) и фиксированные поля (seq_ctrl = 0xf198, addr3 = 00:00:00:00:00:00).
function create_request_frame(sender, receiver, payload)
body_data = Vector{UInt8}(payload)
fcs_value = length(body_data)
return (
header = (
frame_control = 0x00b4,
duration = 0x0000,
addr1 = receiver,
addr2 = sender,
addr3 = "00:00:00:00:00:00",
seq_ctrl = 0xf198
),
body = body_data,
fcs = fcs_value
)
end
И последняя функция receive_frame выполняет проверку адресата (addr1 должен совпадать с MAC-адресом станции или быть broadcast), а так же проверяет контрольную сумму (FCS) — проверяется длина данных (упрощённо).
function receive_frame(receiver_addr, frame)
if frame.header.addr1 != receiver_addr && frame.header.addr1 != "FF:FF:FF:FF:FF:FF"
println("Кадр не для этой станции")
return false
end
calculated_fcs = length(frame.body)
if calculated_fcs != frame.fcs
println("Кадр повреждён (несовпадение FCS)")
return false
end
println("Кадр успешно получен:")
println("От: ", frame.header.addr2)
println("Данные: ", String(frame.body))
return true
end
Чем отличается от прошлой версии?
Далее проведём анализ ошибок.
station_x = "AA:11:BB:22:CC:33"
station_y = "DD:44:EE:55:FF:66"
sent_frame = csma_ca_send(station_x, station_y, "Test data with CSMA/CA")
station_c = "CC:CC:CC:CC:CC:CC"
station_d = "DD:DD:DD:DD:DD:DD"
request = create_request_frame(station_c, station_d, "Test data")
receive_frame(station_d, request)
В этом примере мы рассмотрели основные принципы работы MAC-уровня, реализовав на языке Engee формирование кадров запроса и ответа, базовые структуры MAC-заголовков, упрощённую реализацию протокола CSMA/CA и механизмы обработки ошибок, и убедились что наши алгоритмы работают корректно.
Представленные примеры демонстрируют ключевые концепции MAC-уровня в системах связи.