MAC-уровень
Принципы работы MAC-уровня в системах связи
В этой статье мы рассмотрим основные принципы работы 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
CSMA/CA — это протокол управления доступом к среде передачи данных, используемый в беспроводных сетях (Wi-Fi, IEEE 802.11). В отличие от CSMA/CD (Ethernet), где коллизии обнаруживаются, CSMA/CA предотвращает их за счёт прослушивания канала перед передачей (Carrier Sense), случайных задержек (Backoff) при обнаружении занятости и подтверждений (ACK) для проверки доставки, реализован данный пример на основе раннее разобранной реализации.
Алгоритм работы
-
Проверка канала (CCA — Clear Channel Assessment)
- Если канал свободен → передача.
- Если занят → ждёт случайное время (Backoff).
-
Механизм Backoff
- После каждой неудачной попытки задержка увеличивается (экспоненциальный рост).
-
Подтверждение (ACK)
- Получатель отправляет ACK в ответ на успешный приём.
-
Повторные попытки
- Если ACK не пришёл → повтор передачи (до
max_attempts
).
- Если ACK не пришёл → повтор передачи (до
Далее перейдём к реализация и первая функция - это 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
Чем отличается от прошлой версии?
- Добавлен CSMA/CA (раньше кадры отправлялись сразу).
- Backoff-таймер при занятости канала.
- Упрощённый FCS (вместо CRC32 — длина payload).
- Нет ACK (в реальном Wi-Fi он обязателен).
Далее проведём анализ ошибок.
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-уровня в системах связи.