IP-адресация, маски подсети и основы веб-технологий
Как из 32 бит рождается адрес каждого устройства в сети — и как маска делит интернет на подсети.
IP-адрес (версии 4) — 32-битное число, обычно записываемое как четыре десятичных байта через точку; уникально адресует устройство в сети.
Зачем это нужно
IP-адресация — одна из самых «инженерных» тем школьной информатики и неизменный гость ЕГЭ. За точками в адресе вроде 192.168.1.10 стоит стройная двоичная логика, которую вы уже знаете из раздела о системах счисления. Разобравшись с масками подсети, вы поймёте, как сеть делится на части, сколько устройств помещается в подсеть и как маршрутизаторы решают, «свой» адрес или «чужой». А заодно заглянем в HTML — язык, на котором написан каждый сайт.
Эта тема — прекрасная иллюстрация того, как разные разделы информатики смыкаются в одной задаче. Чтобы понять IP-адресацию, вам понадобится всё сразу: системы счисления (адрес — это двоичное число в десятичной «упаковке»), побитовые операции (адрес сети получают через И с маской), степени двойки (число узлов в подсети). То, что в начале курса могло казаться абстрактной игрой с нулями и единицами, здесь превращается в совершенно практический инструмент, без которого не настроить ни одну сеть. Это и есть взросление в информатике — когда отдельные кирпичики знаний складываются в способность решать настоящие инженерные задачи. А завершим мы курс взглядом на HTML, замкнув круг: от того, как устроены адреса в сети, к тому, как устроены страницы, которые по этим адресам открываются.
Структура IPv4-адреса
IP-адрес — это 32 бита, разбитые на 4 группы по 8 бит (байта). Каждый байт записывают десятичным числом от 0 до 255 — отсюда привычные «четыре числа через точку». Переведём адрес в двоичный вид и обратно, опираясь на то, что один байт = 8 бит:
ip = "192.168.1.10"
# адрес -> биты
octets = [int(x) for x in ip.split(".")]
binary = ".".join(format(o, "08b") for o in octets)
print("десятичный:", ip)
print("двоичный: ", binary)
# адрес как одно 32-битное число
as_number = 0
for o in octets:
as_number = as_number * 256 + o
print("как число: ", as_number)
Вывод:
десятичный: 192.168.1.10 двоичный: 11000000.10101000.00000001.00001010 как число: 3232235786
Маска подсети: где сеть, а где узел
Адрес делится на две части: адрес сети (общий для всех устройств подсети) и адрес узла (уникальный для устройства). Где проходит граница — задаёт маска подсети: подряд идущие единицы помечают биты сети, нули — биты узла. Маска 255.255.255.0 означает «первые 24 бита — сеть, последние 8 — узел». Адрес сети получают побитовым И адреса и маски — вот зачем нам были побитовые операции:
def to_int(addr):
parts = [int(x) for x in addr.split(".")]
n = 0
for p in parts:
n = n * 256 + p
return n
def to_ip(n):
return ".".join(str((n >> (8 * i)) & 255) for i in (3, 2, 1, 0))
ip = to_int("192.168.1.137")
mask = to_int("255.255.255.0")
network = ip & mask # побитовое И -> адрес сети
print("адрес сети:", to_ip(network))
print("маска: ", to_ip(mask))
Вывод:
адрес сети: 192.168.1.0 маска: 255.255.255.0
Сколько устройств помещается в подсеть
Если на узловую часть отведено k бит, то адресов в подсети 2^k, но два из них служебные: адрес сети (все нули в узловой части) и широковещательный (все единицы). Значит, устройств помещается 2^k − 2. Посчитаем для разных масок — это любимый тип задач ЕГЭ:
def hosts(prefix):
host_bits = 32 - prefix # сколько бит под узел
total = 2 ** host_bits
usable = total - 2 if host_bits >= 1 else total
return host_bits, total, usable
for prefix in (24, 26, 28, 30):
host_bits, total, usable = hosts(prefix)
print(f"/{prefix}: {host_bits} бит узла, всего {total}, для устройств {usable}")
Вывод:
/24: 8 бит узла, всего 256, для устройств 254 /26: 6 бит узла, всего 64, для устройств 62 /28: 4 бит узла, всего 16, для устройств 14 /30: 2 бит узла, всего 4, для устройств 2
Свой или чужой: одна ли подсеть
Маршрутизатор постоянно решает: адресат в той же подсети (отправить напрямую) или в другой (через шлюз)? Ответ прост: два адреса в одной подсети, если их адреса сети (адрес И маска) совпадают. Проверим:
def to_int(addr):
n = 0
for p in addr.split("."):
n = n * 256 + int(p)
return n
mask = to_int("255.255.255.0")
def same_subnet(a, b, mask):
return (to_int(a) & mask) == (to_int(b) & mask)
pairs = [
("192.168.1.10", "192.168.1.200"),
("192.168.1.10", "192.168.2.10"),
]
for a, b in pairs:
print(f"{a} и {b}: одна подсеть? {same_subnet(a, b, mask)}")
Вывод:
192.168.1.10 и 192.168.1.200: одна подсеть? True 192.168.1.10 и 192.168.2.10: одна подсеть? False
HTML — язык, на котором написан веб
Любая веб-страница — это текст на языке разметки HTML. Он размечает содержимое тегами: что заголовок, что абзац, что ссылка. Браузер читает теги и отображает страницу. Вот минимальная страница (этот блок не запускается — он для чтения, угловые скобки показаны как есть):
<!DOCTYPE html>
<html>
<head>
<title>Моя страница</title>
</head>
<body>
<h1>Привет, веб!</h1>
<p>Это <strong>первый</strong> абзац.</p>
<a href="https://codechick.io">ссылка</a>
</body>
</html>
Теги почти всегда парные: открывающий <p> и закрывающий </p>. Между ними — содержимое. Покажем, что HTML — это просто структурированный текст: посчитаем теги в строке кода обычным Python:
html = "<p>Привет, <strong>мир</strong>! Это <em>HTML</em>.</p>"
# найдём все открывающие теги
import re
tags = re.findall(r"<(\w+)>", html)
print("теги в порядке появления:", tags)
print("количество тегов:", len(tags))
print("уникальные теги:", sorted(set(tags)))
Вывод:
теги в порядке появления: ['p', 'strong', 'em'] количество тегов: 3 уникальные теги: ['em', 'p', 'strong']
Попробуй сам
Рассчитаем подсеть полностью: по адресу и префиксу найдём адрес сети, широковещательный адрес и число доступных устройств. Это «всё в одном» задание на адресацию.
def to_int(a):
n = 0
for p in a.split("."): n = n * 256 + int(p)
return n
def to_ip(n):
return ".".join(str((n >> (8 * i)) & 255) for i in (3, 2, 1, 0))
ip = "10.0.5.73"
prefix = 26
mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF
network = to_int(ip) & mask
broadcast = network | (~mask & 0xFFFFFFFF)
usable = 2 ** (32 - prefix) - 2
print("адрес: ", ip, "/", prefix)
print("маска: ", to_ip(mask))
print("адрес сети: ", to_ip(network))
print("широковещательный:", to_ip(broadcast))
print("устройств: ", usable)
Вывод:
адрес: 10.0.5.73 / 26 маска: 255.255.255.192 адрес сети: 10.0.5.64 широковещательный: 10.0.5.127 устройств: 62
Частые ошибки
- Забывают про два служебных адреса. Для устройств доступно 2^k − 2, а не 2^k: адрес сети и широковещательный заняты.
- Путают маску и префикс.
/24и255.255.255.0— это одно и то же, записанное по-разному. - Считают байт больше 255. Каждый октет IPv4 — это 8 бит, диапазон строго 0–255.
- Думают, что HTML — язык программирования. HTML — язык разметки: он описывает структуру, но не вычисляет.
Итоги
- IPv4-адрес — 32 бита (4 байта по 0–255); легко переводится в двоичный вид и в одно число.
- Маска подсети делит адрес на часть сети и часть узла; адрес сети = адрес
&маска. - В подсети с k битами узла доступно 2^k − 2 адресов (минус сеть и широковещательный).
- HTML — язык разметки веб-страниц: парные теги задают структуру, браузер её отображает.