Для обмена информацией в компьютерных сетях придуманы стандартные протоколы (договорённости о том, кто когда что кому отправляет и что это значит). Стек протоколов Интернета, или стек TCP/IP, выглядит примерно так:
Уровень | Протоколы этого уровня |
---|---|
Прикладной (application) | HTTP, SSH, DNS, ... |
Транспортный (transport) | TCP, UDP, SCTP, QUIC, ... |
Межсетевой (inter-net) | IPv4, IPv6 |
Канальный (link) | Ethernet, Wi-Fi (IEEE 802.11), ... |
Обычно мы используем набор протоколов разных уровней, чтобы решить свою прикладную задачу. Например, при загрузке веб-страницы http://wiki.cs.hse.ru/ будут как минимум использоваться HTTP, TCP, IPv4 и какие-то канальные протоколы.
Фрагмент данных с верхнего уровня стека заворачивается в “конверт” нижнего уровня (картинка из википедии на примере UDP):
Данные доходят до адресата через цепочку промежуточных устройств, каждое из которых распаковывает и просматривает столько конвертов, сколько ему нужно и сколько оно умеет:
Например, пока пакет не дошёл до хоста (компьютера) – адресата, никто* не смотрит на заголовки транспортного уровня. Благодаря этому поверх имеющейся инфраструктуры межсетевого уровня можно реализовывать новые способы обмена информацией.
Как правило, протоколы прикладного уровня реализованы в userspace (в программах и библиотеках), транспортного и межсетевого — в ядре ОС, а канальный уровень делят между собой ОС и аппаратура.
На межсетевом уровне появляется глобальная адресация: у каждого хоста в сети есть уникальный идентификатор — адрес. Адрес IPv4 — 4 байта, их записывают в десятичной системе: 92.242.58.220
. Адрес IPv6 — 16 байт, их записывают в 16-ричной системе двухбайтовыми последовательностями через двоеточие: 2a02:6b8::2:242
(там, где два двоеточия подряд, подразумевается последовательность нулевых байт).
Протокол TCP представляет абстракцию “трубы с данными”, похожей на канал в Unix: гарантируется надёжная доставка данных в изначальной последовательности, не сохраняются границы между отдельными записями в канал. Протокол UDP представляет абстракцию “голубиной почты”: можно отправлять датаграммы — отдельные фрагменты данных известного размера, которые могут дойти до получателя в произвольном порядке, дойти несколько раз или не дойти вовсе.
TCP и UDP используют адрес межсетевого уровня и добавляют ещё 16 бит, которые называются «порт». Есть well-known порты, которые обычно используются для разных надобностей (см. /etc/services
): например, сервер HTTP обычно использует порт 80.
POSIX даёт нам слой абстракции поверх протоколов транспортного уровня и ниже. Абстракция называется sockets (гнёзда). В userspace сокет виден как файловый дескриптор.
Создаём сокет (man 2 socket):
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
Параметр domain выбирает используемый протокол межсетевого уровня (ну примерно). Нам будут интересны три варианта:
Name Purpose Man page
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_UNIX Local communication unix(7)
Сокеты бывают как минимум двух типов (параметр type): потоковые и датаграммные (ср. TCP и UDP). Для первых хорошо подходят стандартные операции read/write, для вторых не очень.