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 — язык разметки веб-страниц: парные теги задают структуру, браузер её отображает.
Проверьте себя
1. Сколько устройств можно адресовать в подсети с маской /24 (8 бит под узел)?
A256
B254
C255
D512
2. Как получить адрес сети из IP-адреса и маски подсети?
AСложить адрес и маску
BПрименить побитовое И (AND) к адресу и маске
CВычесть маску из адреса
DПрименить побитовое ИЛИ (OR)
3. Чем HTML отличается от языка программирования?
AНичем, это язык программирования
BHTML — язык разметки: он описывает структуру страницы, но не выполняет вычислений
CHTML быстрее работает
DHTML понимают только серверы
Поддержать проект