В сети передачу информации можно разделить на два типа:
Идеи пакетной сети заложились в реализации ARPANET - ранней версии Интернета. Уже тогда появилась концепция сокета - интерфейс для общения приложений, который на уровне ОС абстрагировал сеть для программиста в удобных буфер чтения/записи, что облегчало взаимодействие с сетью (как раз разные типы коммутаций привели к разделению на TCP и UDP)
Определение сокета менялось до тех пор, пока не пришли к тому, что сокет - это конечная точка для коммуникации процесса с другими процессами путем отправки и приема сообщений
Впервые сокет появился в BSD. Сокеты в Unix, а также в Unix-подобных системах, включая Linux, делятся:
По семейству адресов (domain):
AF_UNIX - сокет для локального общенияAF_LOCAL - синоним для AF_UNIX, но рекомендуется использовать AF_UNIXAF_INET - сокет для общения по IPv4-сетиAF_INET6 - сокет для общения по IPv6-сетиAF_BLUETOOTH - сокет для BluetoothAF_CAN - сокет промышленной CAN-шины (Controller Area Network) для применения в автомобилестроении и промышленной автоматизациии другие типы, включая типы для ныне устаревших и редко используемых протоколов
И по типу соединения (type):
SOCK_STREAM для отправки потока с установлением надежного соединения и с гарантией порядка доставленных сообщений (как в TCP)SOCK_DGRAM для отправки датаграмм (таких как в UDP), то есть сообщений фиксированной длины без установления надежного соединения и без гарантии порядка
SOCK_SEQPACKET - сокет с установлением соединения, обеспечивающий надёжную передачу сообщений с сохранением границ и порядка (через него реализуется протокол SCTP)SOCK_RAW для доступа к IP-пакетам без обработки на транспортном уровнеSOCK_RDM (Reliably Delivered Messages) для надежной отправки датаграмм, но без гарантии их порядкаSOCK_PACKET - устаревшийСейчас зачастую используются первые два типа для протоколов TCP и UDP соответственно, поэтому они и будут рассматриваться
Источник: https://man7.org/linux/man-pages/man2/socket.2.html
Для установки соединения с сокетами типов SOCK_STREAM и SOCK_DGRAM используется базовый системный вызов - connect()
Для сокетов типа SOCK_STREAM вызов connect() инициирует стандартную процедуру установления соединения (трехстороннее рукопожатие). Соединение устанавливается только один раз, а повторный вызов connect() для того же сокета, как правило, невозможен без его предварительного закрытия
Для сокетов типа SOCK_DGRAM вызов connect() не устанавливает соединение в привычном смысле, так как протокол UDP не поддерживает соединений. Подключение к сокету с типом SOCK_DGRAM осуществляется лишь для того, чтобы ядро знало адрес и порт получателя
Подключенный датаграммный сокет можно переподключить к другому адресу, вызвав connect() повторно. Для отключения сокета SOCK_DGRAM от адреса используют connect() с семейством адресов AF_UNSPEC
Хотя connect() является основой, в процессе установки соединения для SOCK_STREAM на стороне сервера используются и другие ключевые вызовы:
bind(): Привязывает сокет к локальному адресу и порту. Для сервера это обязательно, для клиента обычно выполняется автоматическиlisten(): Переводит сокет в режим ожидания входящих соединений. Используется только для SOCK_STREAM на стороне сервераaccept(): Принимает входящее соединение от клиента на слушающем сокете. При успехе создает новый сокет для взаимодействия с этим конкретным клиентом. Используется только для SOCK_STREAM на стороне сервераПодключенный сокет представляет из себя файловый дескриптор, к которому применимы системные вызовы read и write. Для сокетов также применимы системные вызовы recv и send с дополнительными флагами (например, не ждать получение подтверждения MSG_DONTWAIT)
Для сокетов есть виртуальная файловая система sockfs. Локальные сокеты могут быть представлены файлами в файловой системе. В псевдофайлах /proc/net/tcp и /proc/net/udp содержится информация о созданных сокетах. Так, например, выглядят сокеты TCP:
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 3500007F:0035 00000000:0000 0A 00000000:00000000 00:00000000 00000000 101 0 17053 1 ffff91f576046300 100 0 0 10 0
1: 00000000:0016 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 20021 2 ffff91f57512e300 100 0 0 10 0
2: 0100007F:1538 00000000:0000 0A 00000000:00000000 00:00000000 00000000 109 0 20138 1 ffff91f57512a400 100 0 0 10 0
3: 0100007F:AC43 00000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 25724 1 ffff91f575128000 100 0 0 10 0
4: 0100007F:8124 00000000:0000 0A 00000000:00000000 00:00000000 00000000 110 0 20456 1 ffff91f57512ec00 100 0 0 10 0
5: 0100007F:0CEA 00000000:0000 0A 00000000:00000000 00:00000000 00000000 110 0 20458 1 ffff91f577aebf00 100 0 0 10 0
Здесь содержатся:
sl - индекс сокета в таблицеlocal_address - локальный адрес и порт в шестнадцатеричном виде
Здесь в первой строке 3500007F - это 127.0.0.53rem_address - удалённый адрес и портst - состояние сокета в шестнадцатеричном виде. Коды состояний определены в заголовочных файлах ядра, то есть в /include/net/tcp_states.htx_queue - количество данных в очереди отправки в байтахrx_queue - количество данных в очереди приёма в байтахtr - тип таймера активности (timer run)tm->when - время в джиффи (примерно 10 мс) до срабатывания текущего таймера, если он запущенretrnsmt - счётчик повторных передач для текущего сегментаuid - идентификатор пользователя, которому принадлежит сокет. Здесь в первой строке 101 - это пользователь systemd-resolvetimeout - таймаут для текущего состояния сокетаinode - номер индексного дескриптора сокета в файловой системе sockfs. Этот номер можно связать с открытыми файловыми дескрипторами процессов через /proc/<pid>/fd/Далее идут дополнительные поля (например, указатель на структуру в памяти), количество которых зависит от конфигурации и версии ядра
Для упрощенного просмотра существует утилита ss (от socket statistics). Так для просмотра слушающих TCP и UDP сокетов можно использовать команду ss -tulnp
Пакет для ядра определяется структурой sk_buff с множеством полей
Сетевая карта в компьютере получает новый поток данных, драйвер сетевой карты получает прерывание (или запрос NAPI), обрабатывает это, создает структуру sk_buff и передает в подсистему Ethernet (или другую для конкретного протокола). Далее эта подсистема проверяет пакет, а потом сетевая подсистема определяет протокол транспортного уровня (в нашем случае IPv4 или IPv6) и передает пакет через сетевой фильтр
На сетевой фильтре на этапе премаршрутизации (Prerouting) принимается решение о дальнейшей судьбе пакета, которое кэшируется в таблицу, чтобы похожие пакеты проходили проверку быстрее
После премаршрутизации локальные пакеты для этого хоста попадают в фильтр для входящих пакетов (Input), где их обрабатывают TCP и UDP обработчики соответственно, которые находят сокеты и процессы, которые владеют сокетами
Проходящие через хост процессы проходят через фильтр для транзитных пакетов (Forward). Исходящие пакеты, созданные процессами, проходят фильтр для исходящих пакетов (Output)
После фильтров для транзитных и исходящих пакетов идет фильтр постмаршрутизации (Postrouting), который аналогично премаршрутизации принимает решение об отправке пакет
На этапах фильтров, премаршрутизации и постмаршрутизации, ядро вызывает хуки зарегистрированных обработчиков - так, например, работает утилита iptables
Далее после постмаршрутизации пакет проходит через подсистемы Neigh, Egress, Taps и другие, попадает в очередь отправки, откуда драйвер передает его на сетевую карту

Так как проходящий пакет приводит к вызову множества системных вызовов, для оптимизации применяют технологию RDMA (Remote Direct Memory Access). В ней сетевые карты напрямую общаются и могут записывать в память удаленного компьютера, минуя ядро ОС и процессор. RDMA применяется для баз данных, распределенных хранилищ и высокопроизводительных вычислениях
В ядре Linux реализации протоколов содержатся в отдельных модулях в папке /net/ архива с исходным кодом. Например, реализация Ethernet хранится в файле /net/ethernet/eth.c
Для встраиваемых (embedding) системах, например, одноплатных компьютеров или контроллеров, до компиляции ядро конфигурируется так, что бы не включать ненужные сетевые модули и компоненты. Дополнительно, так как мы знаем подключенные, нужные нам устройства (например, датчик температуры), то вместо процедуры обнаружения идентификатора устройства и производителя, загрузки драйвера из таблицы, можно составить дерево устройств, где описаны все периферийные устройства, их адреса и нужные драйверы (которые часто вшиты в ядро для скорости и надёжности)