сокет линукс что такое
Сокеты¶
Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.
Принципы сокетов¶
Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы (в UNIX непривилегированные процессы не могут использовать порты меньше 1024). Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения. При этом сохраняется возможность проверить наличие соединений на данный момент, установить тайм-аут для операции и т.д.
Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.
Обычно клиент явно подсоединяется к слушателю, после чего любое чтение или запись через его файловый дескриптор будут передавать данные между ним и сервером.
Основные функции¶
Общие | |
Socket | Создать новый сокет и вернуть файловый дескриптор |
Send | Отправить данные по сети |
Receive | Получить данные из сети |
Close | Закрыть соединение |
Серверные | |
Bind | Связать сокет с IP-адресом и портом |
Listen | Объявить о желании принимать соединения. Слушает порт и ждет когда будет установлено соединение |
Accept | Принять запрос на установку соединения |
Клиентские | |
Connect | Установить соединение |
socket()¶
Создаёт конечную точку соединения и возвращает файловый дескриптор. Принимает три аргумента:
domain указывающий семейство протоколов создаваемого сокета
type
protocol
Протоколы обозначаются символьными константами с префиксом IPPROTO_* (например, IPPROTO_TCP или IPPROTO_UDP). Допускается значение protocol=0 (протокол не указан), в этом случае используется значение по умолчанию для данного вида соединений.
Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.
Связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:
Возвращает 0 при успехе и −1 при возникновении ошибки.
Автоматическое получение имени хоста.
listen()¶
Подготавливает привязываемый сокет к принятию входящих соединений. Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:
После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.
accept()¶
Используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:
Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.
connect()¶
Устанавливает соединение с сервером.
Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.
Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.
Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.
Передача данных¶
Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write, но есть специальные функции для передачи данных через сокеты:
Нужно обратить внимание, что при использовании протокола TCP (сокеты типа SOCK_STREAM) есть вероятность получить меньше данных, чем было передано, так как ещё не все данные были переданы, поэтому нужно либо дождаться, когда функция recv возвратит 0 байт, либо выставить флаг MSG_WAITALL для функции recv, что заставит её дождаться окончания передачи. Для остальных типов сокетов флаг MSG_WAITALL ничего не меняет (например, в UDP весь пакет = целое сообщение).
Что такое сокеты Unix и как они работают?
Сокеты Unix — это форма связи между двумя процессами, которая отображается в виде файла на диске. Этот файл может использоваться другими программами для установления очень быстрых соединений между двумя или более процессами без каких-либо сетевых накладных расходов.
Что такое сокеты?
Сокеты — это прямая связь между двумя процессами. Представьте, что вы хотите позвонить своему другу по дороге; вы можете сделать звонок, направив его через вашу телефонную компанию и обратно в их дом, или вы можете провести провод прямо в их дом и отключить посредника. Последнее, очевидно, непрактично в реальной жизни, но в мире Unix очень распространено устанавливать эти прямые связи между программами.
Собственное имя для сокетов unix — сокеты домена Unix (Unix Domain Sockets), потому что все они находятся на одном компьютере. В некотором смысле сокеты — это сеть, полностью содержащаяся в ядре; вместо того, чтобы использовать сетевые интерфейсы и соответствующие накладные расходы для отправки данных, те же самые данные могут быть отправлены напрямую между программами.
Несмотря на создание файлов на диске, сокеты Unix на самом деле не записывают данные, которые они отправляют на диск, так как это было бы слишком медленно. Вместо этого все данные хранятся в памяти ядра; единственная цель файла сокета — поддерживать ссылку на сокет и давать ему разрешения файловой системы для управления доступом. В современных системах сокеты обычно расположены в директории /usr/lib/systemd/system/. Например, сокет MariaDB обычно находится по адресу:
Этот файл ничего не содержит, и вы не должны изменять его напрямую, за исключением разрешений, где это применимо. Это просто имя.
Как работают сокеты?
Сокеты просто предоставляют фактическое оборудование для перемещения данных. Сокеты на основе TCP называются потоковыми сокетами, куда все данные будут поступать по порядку. Сокеты на основе UDP — это сокеты для дейтаграмм, для которых порядок (или даже доставка) не гарантируется. Существуют также необработанные (raw) сокеты, которые не имеют каких-либо ограничений и используются для реализации различных протоколов и утилит, которые должны проверять низкоуровневый сетевой трафик, например Wireshark.
Сокеты обычно по-прежнему используют TCP или UDP, поскольку они не являются чем-то особенным, кроме причудливого канала внутри ядра. TCP и UDP — это транспортные протоколы, которые определяют, как данные передаются с места на место, но не заботятся о том, что это за данные. TCP и UDP обеспечивают платформу для большинства других протоколов, таких как FTP, SMTP и RDP, которые работают на более высоких уровнях.
Приложение может использовать несколько иную реализацию TCP; потоковые сокеты используют протокол SOCK_STREAM, который TCP также использует для транспорта почти всё время, и хотя они в основном взаимозаменяемы, технически они немного отличаются. Хотя это низкоуровневый материал и на самом деле это не то, о чем вам придётся беспокоиться, просто знайте, что большая часть трафика, отправляемого через сокеты домена UNIX, основана на TCP или UDP или, по крайней мере, очень похожа на трафик этих транспортных протоколов, и TCP отправляется через сокеты домена UNIX быстрее, чем TCP через сетевые интерфейсы, такие как порты.
Использование сокетов на практике
Сокеты Unix обычно используются в качестве альтернативы сетевым TCP-соединениям, когда процессы выполняются на одном компьютере. Данные обычно по-прежнему отправляются по тем же протоколам; но поскольку они просто остаются на той же машине, в том же домене (отсюда и название сокеты домена UNIX), поэтому им никогда не нужно беспокоить петлевой (loopback) сетевой интерфейс для подключения к самому себе.
Самым ярким примером этого является Redis, чрезвычайно быстрое хранилище значений ключей, которое полностью работает в памяти. Redis часто используется на том же сервере, который обращается к нему, поэтому обычно можно использовать сокеты. На таких низких уровнях и с учётом того, насколько быстр Redis, сокеты обеспечивают повышение производительности на 25% в некоторых синтетических тестах.
Если вы подключаетесь к базе данных MySQL, вы также можете использовать сокет. Обычно вы подключаетесь к host:port из удалённой системы, но если вы подключаетесь к базе данных на том же сервере (например, REST API обращается к базе данных), вы можете использовать сокеты для ускорения. Это не повлияет на нормальное использование, но очень заметно при нагрузке, более 20% на 24 ядрах высокого класса со 128 одновременными пользователями и миллионом запросов в секунду. Увидите ли вы выгоду от сокетов при таких условиях — это совсем другое дело, но на этом этапе, вероятно, всё равно придётся заняться репликацией и балансировкой нагрузки.
Если вы хотите работать с сокетами вручную, вы можете использовать утилиту socat, чтобы открыть их через сетевые порты:
Это технически противоречит назначению сокетов домена Unix, но может использоваться для отладки на транспортном уровне.
Сокет линукс что такое
Вызов socket(2) создаёт сокет, connect(2) соединяет сокет с удалённым сокетным адресом, bind(2) привязывает сокет к локальному адресу, listen(2) сообщает сокету, что должны приниматься новые соединения, а accept(2) используется для получения нового сокета для нового входящего соединения. Вызов socketpair(2) возвращает два соединённых анонимных сокета (реализовано только для некоторых локальных семейств, например AF_UNIX).
Вызовы send(2), sendto(2) и sendmsg(2) отправляют данные в сокет, а recv(2), recvfrom(2) и recvmsg(2) принимают данные из сокета. Вызовы poll(2) и select(2) ожидают поступления данных или готовятся к передаче данных. Кроме того, для чтения и записи данных могут использоваться стандартные операции ввода-вывода: write(2), writev(2), sendfile(2), read(2) и readv(2).
Вызов getsockname(2) возвращает адрес локального сокета, а getpeername(2) возвращает адрес удалённого сокета. Вызовы getsockopt(2) и setsockopt(2) используются для установки или считывания параметров протокола или уровня сокетов. Вызов ioctl(2) может быть использован для установки или чтения некоторых других параметров.
Вызов close(2) используется для закрытия сокета. Вызов shutdown(2) закрывает части полнодуплексного сокетного соединения.
Перемещение (seeking), или вызовы pread(2) и pwrite(2) с ненулевой позицией, для сокетов не поддерживается.
Для сокетов возможно создание неблокирующего ввода/вывода путём установки в файловый дескриптор сокета флага O_NONBLOCK с помощью вызова fcntl(2). При этом все блокировавшие раньше операции, будут возвращать EAGAIN (операция должна быть повторена позднее); connect(2) возвратит ошибку EINPROGRESS. Пользователь может подождать наступления различных событий через poll(2) или select(2).
События ввода-вывода | ||
Событие | Флаг poll | Когда происходит |
Чтение | POLLIN | Поступили новые данные |
Чтение | POLLIN | Установка соединения выполнена (для сокетов, ориентированных на соединение) |
Чтение | POLLHUP | Другая сторона инициировала запрос на разъединение |
Чтение | POLLHUP | Соединение разорвано (только для протоколов, ориентированных на соединение). Если производится запись в сокет, то также посылается сигнал SIGPIPE |
Запись | POLLOUT | Сокет имеет достаточно места в буфере отправки для записи в него новых данных |
Чтение/Запись | POLLIN| POLLOUT | Исходящий вызов connect(2) завершён |
Чтение/Запись | POLLERR | Произошла асинхронная ошибка |
Чтение/Запись | POLLHUP | Другая сторона закрыла (shut down) одно направление |
Исключение | POLLPRI | Пришли неотложные данные. При этом посылается сигнал SIGURG |
Альтернативе poll(2) и select(2) в ядре существует возможность информировать приложение о событиях с помощью сигнала SIGIO. Для этого необходимо установить с помощью fcntl(2) в файловом дескрипторе сокета флаг O_ASYNC, а также назначить с помощью sigaction(2) корректный обработчик сигнала SIGIO. Смотрите ниже раздел Сигналы.
Структуры адреса сокета
Для передачи сокетного адреса любого типа через программный интерфейс сокетов служит тип struct sockaddr. Целью данного типа является приведение типов сокетных адресов определённого домена к «общему» типу, что позволяет избежать предупреждений компилятора о несовпадении типов в вызовах API сокетов.
Также, программный интерфейс сокетов предоставляет тип данных struct sockaddr_storage. Данный тип удобен для размещения всех поддерживаемых структур сокетных адресов определённого домена; он достаточно большой и имеет корректное выравнивание (в частности, он позволяет хранить сокетные адреса IPv6). Для определения типа сокетного адреса, который хранится в структуре, служит следующее поле:
Структура sockaddr_storage полезна для программ, которые должны работать с сокетными адресами единообразно (например, в программах, использующих одновременно сокетные адреса IPv4 и IPv6).
Параметры сокетов
До Linux 3.8, данный параметр сокета можно было устанавливать, но нельзя прочитать с помощью getsockopt(2). Начиная с Linux 3.8 он доступен для чтения. Аргумент optlen должен содержать размер буфера, способного разместить имя устройства; рекомендуемое значение — IFNAMSZ байт. Реальная длина имени устройства возвращается обратно через аргумент optlen.
SO_BROADCAST Задать или считать флаг широковещания. Если он установлен, то через датаграммные сокеты разрешено отправлять пакеты на широковещательный адрес. Этот параметр не действует на потоковые сокеты. SO_BSDCOMPAT Разрешить совместимость по ошибкам с BSD. Используется модулем протокола UDP в Linux версии 2.0 и 2.2. Если включено, то полученные UDP-сокетом ошибки ICMP не будут передаваться пользовательской программе. В последний версиях ядер поддержка этого параметра удалена: в Linux 2.4 он игнорируется, а в Linux 2.6 при использовании в программе для него генерируется предупреждение ядра (printk()). В Linux 2.0 также включён параметр совместимости по ошибкам с BSD и для неструктурированных сокетов (произвольное изменение заголовка, пропуск флага широковещательной передачи), но в Linux 2.2 это было удалено. SO_DEBUG Включить отладку сокета. Разрешено только процессам с мандатом CAP_NET_ADMIN или имеющим нулевой идентификатор эффективного пользователя. SO_DOMAIN (начиная с Linux 2.6.32) Получить доменный сокет в виде целого числа; пример возвращаемого значения: AF_INET6. Подробней смотрите в socket(2). Этот параметр сокета доступен только для чтения. SO_ERROR Получить и очистить ожидающую обработки ошибку сокета. Этот параметр сокета доступен только для чтения. Ожидает целое число. SO_DONTROUTE Не выполнять отправку через шлюз, посылать только на машины, соединенные напрямую. Тот же эффект может быть достигнут путём установки для сокета флага MSG_DONTROUTE во время вызова send(2). В качестве параметра ожидается целочисленный логический флаг. SO_KEEPALIVE Включить отправку «поддерживающих» (keep-alive) сообщений для сокетов, ориентированных на соединение. Ожидается целочисленный логический флаг. SO_LINGER Задать или считать параметр SO_LINGER. Аргументом является структура linger.
Если этот параметр установлен, то close(2) или shutdown(2) не вернут управление до тех пор, пока не будут отправлены все сообщения в очереди сокета или до истечения времени задержки (linger). В противном случае вызовы вернут управление немедленно и закрытие будет произведено в фоновом режиме. Если сокет закрывается как часть вызова exit(2), то задержка всегда происходит в фоновом режиме. SO_MARK (начиная с Linux 2.6.25) Устанавливать метку на каждый пакет, отправленный через сокет (похоже на цель netfilter MARK, но для сокетов). Изменение метки можно использовать для маршрутизации на основе меток не задействуя netfilter или для фильтрации пакетов. Для установки этого параметра требуется мандат CAP_NET_ADMIN. SO_OOBINLINE Если включён этот параметр, то внепоточные данные помещаются непосредственно во входной поток данных. В противном случае внепоточные данные передаются только, если во время приёма установлен флаг MSG_OOB. SO_PASSCRED Включить или выключить приём управляющего сообщения SCM_CREDENTIALS. Подробней смотрите в unix(7). SO_PEEK_OFF (начиная с Linux 3.4) Этот параметр, который пока поддерживается только для сокетов unix(7), устанавливает значение «смещения выборки» (peek offset) для системного вызова recv(2), когда он используется с флагом MSG_PEEK.
Если этому параметру присваивается положительное значение или ноль, то следующая выборка данных из очереди сокета произойдёт по байтовому смещению, определяемому значением этого параметра. В то же время, «смещение выборки» будет увеличено на количество байт, выбранных из очереди, то есть последовательные операции выборки возвращают следующие данные из очереди.
Если данные удалены из начала очереди с помощью вызова recv(2) (или подобного) без флага MSG_PEEK, то «смещение выборки» будет уменьшено на количество удалённых байт. Другими словами, приём данных без флага MSG_PEEK корректирует «смещение выборки» относительно поддерживаемого относительного положения данных в очереди, и последующая выборка возвратит данные, которые были бы получены, если бы данные не удалялись.
Для датаграммных сокетов, если «смещение выборки» указывает в середину пакета, то возвращаемые данные маркируются флагом MSG_TRUNC.
В следующем примере показано использование SO_PEEK_OFF. Предположим, в очереди потокового сокета есть входные данные:
Следующая последовательность вызовов recv(2) выполнила бы то, что описано в комментариях:
Для сокетов TCP данный параметр позволяет accept(2) распределить нагрузку в многонитиевом сервере, назначая разные слушатели сокета в каждой нити. Это улучшает распределение нагрузки по сравнении с обычными методами, например с одной принимающей нитью accept(2), которая распределяет соединения, или с несколькими нитями, которые конкурируют за accept(2) единого сокета.
Для сокетов UDP использование данного параметра может улучшить распределение входящих датаграмм по нескольким процессам (или нитям) по сравнении с обычным методом с несколькими процессами, которые конкурируют при приёме датаграмм из единого сокета.
SO_RXQ_OVFL (начиная с Linux 2.6.33) Указывает, что к принятым skbs должно быть прикреплено вспомогательное сообщение (cmsg) с беззнаковым 32-битным значением, которое обозначает количество пакетов, отброшенных сокетом между последним принятым пакетом и этим принятым пакетом. SO_SNDBUF Задать или считать максимальный размер буфера отправки сокета (в байтах). Ядро удваивает это значение (для пространства под учёт ресурсов (bookkeeping overhead)) при установке этого параметра с помощью setsockopt(2), и это удвоенное значение возвращается getsockopt(2). Значение по умолчанию устанавливается через файл /proc/sys/net/core/wmem_default, а максимальное возможное значение устанавливается через файл /proc/sys/net/core/wmem_max. Минимальное (удвоенное) значение для этого параметра равно 2048. SO_SNDBUFFORCE (начиная Linux 2.6.14) С помощью этого параметра сокета привилегированный (CAP_NET_ADMIN) процесс может выполнить ту же работу, что и с помощью SO_SNDBUF, но возможно превысить ограничение wmem_max. SO_TIMESTAMP Включить или выключить приём управляющего сообщения SO_TIMESTAMP. Управляющее сообщение метки времени посылается с уровнем SOL_SOCKET, а поле cmsg_data выражено структурой struct timeval, обозначающей время приёма последнего пакета, переданного пользователю в этом вызове. Подробней об управляющих сообщениях смотрите в cmsg(3). SO_TYPE Получить тип сокета в виде целого числа (например, SOCK_STREAM). Этот параметр сокета доступен только для чтения. SO_BUSY_POLL (начиная с Linux 3.11) Задаёт приблизительный интервал в микросекундах для задержки опроса при блокирующем приёме при отсутствии данных. Увеличение этого значения требует мандата CAP_NET_ADMIN. Значение по умолчанию данного параметра управляется через файл /proc/sys/net/core/busy_read.
Значение в файле /proc/sys/net/core/busy_poll определяет как долго select(2) и poll(2) задержат опрос, если они работают с сокетами с установленным SO_BUSY_POLL и отсутствуют события для извещения.
В обоих случаях опрос с задержкой (busy polling) будет завершён только, когда сокет примет все данные из сетевого устройства, которое поддерживает этот параметр.
Хотя опрос с задержкой может уменьшить время ожидания в некоторых приложениях, этим нужно пользоваться с осторожностью, так как его использование увеличит нагрузку на ЦП и энергопотребление.
Сигналы
Если был произведён вызов fcntl(2) с FIOSETOWN или ioctl(2) с SIOCSPGRP, то при появлении событий ввода/вывода посылается сигнал SIGIO. Для определения сокета, в котором произошло событие, в обработчике можно воспользоваться вызовом poll(2) или select(2). Альтернативным способом (в Linux 2.2) является установка сигнала реального времени с помощью вызова fcntl(2) с F_SETSIG; будет вызван обработчик сигнала реального времени и в его структуре siginfo_t поле si_fd будет содержать значение файлового дескриптора. Дополнительная информация приведена в fcntl(2).
В некоторых случаях (например, при наличии доступа нескольких процессов к одному сокету) условие, вызвавшее SIGIO, может исчезнуть на момент обработки процессом сигнала. Если это происходит, то процесс должен подождать сигнала ещё какое-то время, так как Linux снова пошлёт его позже.
Интерфейсы /proc
Вызовы ioctl
Возможные операции fcntl(2):
FIOGETOWN То же, что и вызов ioctl(2) SIOCGPGRP. FIOSETOWN То же, что и вызов ioctl(2) SIOCSPGRP.
ВЕРСИИ
ЗАМЕЧАНИЯ
В Linux разрешено повторное использование порта с параметром SO_REUSEADDR только, когда этот параметр установлен и в программе, уже выполнившей bind(2) и в программе, которая хочет использовать порт. Такое поведение отличается от некоторых реализаций (например, FreeBSD), в которых только последняя программа должна устанавливать параметр SO_REUSEADDR. Обычно, это отличие незаметно, так как, например, в серверных программах всегда устанавливают этот параметр.
Программирование сокетов в Linux
Автор: Александр Шаргин
Опубликовано: 16.05.2001
Исправлено: 04.02.2006
Версия текста: 1.1
Введение
Socket API был впервые реализован в операционной системе Berkley UNIX. Сейчас этот программный интерфейс доступен практически в любой модификации Unix, в том числе в Linux. Хотя все реализации чем-то отличаются друг от друга, основной набор функций в них совпадает. Изначально сокеты использовались в программах на C/C++, но в настоящее время средства для работы с ними предоставляют многие языки (Perl, Java и др.).
Сокеты предоставляют весьма мощный и гибкий механизм межпроцессного взаимодействия (IPC). Они могут использоваться для организации взаимодействия программ на одном компьютере, по локальной сети или через Internet, что позволяет вам создавать распределённые приложения различной сложности. Кроме того, с их помощью можно организовать взаимодействие с программами, работающими под управлением других операционных систем. Например, под Windows существует интерфейс Window Sockets, спроектированный на основе socket API. Ниже мы увидим, насколько легко можно адаптировать существующую Unix-программу для работы под Windows.
ПРИМЕЧАНИЕ Большая часть материала, изложенного в статье, применимо ко всему семейству ОС Unix. Тем не менее, все приводимые далее факты и демонстрационные программы проверялись только под Linux, поэтому название этой ОС и вынесено в заголовок статьи. |
Основы socket API
Понятие сокета
Атрибуты сокета
Тип сокета определяет способ передачи данных по сети. Чаще других применяются:
Наконец, последний атрибут определяет протокол, используемый для передачи данных. Как мы только что видели, часто протокол однозначно определяется по домену и типу сокета. В этом случае в качестве третьего параметра функции socket можно передать 0, что соответствует протоколу по умолчанию. Тем не менее, иногда (например, при работе с низкоуровневыми сокетами) требуется задать протокол явно. Числовые идентификаторы протоколов зависят от выбранного домена; их можно найти в документации.
Адреса
Зачем понадобилось заключать всего одно поле в структуру? Дело в том, что раньше in_addr представляла собой объединение (union), содержащее гораздо большее число полей. Сейчас, когда в ней осталось всего одно поле, она продолжает использоваться для обратной совместимости.
ПРИМЕЧАНИЕ На некоторых машинах (к PC это не относится) порядок хоста и сетевой порядок хранения байтов совпадают. Тем не менее, функции преобразования лучше применять и там, поскольку это улучшит переносимость программы. Это никак не скажется на производительности, так как препроцессор сам уберёт все «лишние» вызовы этих функций, оставив их только там, где преобразование действительно необходимо. |
Установка соединения (сервер)
Установка соединения (клиент)
Обмен данными
Функция send используется для отправки данных и имеет следующий прототип.
Закрытие сокета
Параметр how может принимать одно из следующих значений:
Обработка ошибок
Отладка программ
Для простоты я буду использовать в демонстрационных примерах интерфейс внутренней петли.
Эхо-клиент и эхо-сервер
Теперь, когда мы изучили основные функции для работы с сокетами, самое время посмотреть, как они используются на практике. Для этого я написал две небольшие демонстрационные программы. Эхо-клиент посылает сообщение «Hello there!» и выводит на экран ответ сервера. Его код приведён в листинге 1. Эхо-сервер читает всё, что передаёт ему клиент, а затем просто отправляет полученные данные обратно. Его код содержится в листинге 2.
Листинг 1. Эхо-клиент.
Листинг 2. Эхо-сервер.
Обмен датаграммами
Как уже говорилось, датаграммы используются в программах довольно редко. В большинстве случаев надёжность передачи критична для приложения, и вместо изобретения собственного надёжного протокола поверх UDP программисты предпочитают использовать TCP. Тем не менее, иногда датаграммы оказываются полезны. Например, их удобно использовать при транслировании звука или видео по сети в реальном времени, особенно при широковещательном транслировании.
Листинг 3. Программа sender.
Листинг 4. Программа receiver.
Использование низкоуровневых сокетов
Низкоуровневые сокеты открывают перед вами новые горизонты. Они предоставляют программисту полный контроль над содержимым пакетов, которые отправляются в путешествие по сети. С другой стороны, они сложнее в использовании и обладают плохой переносимостью. Вот почему использовать их следует только в случае необходимости. Например, без них не обойтись при разработке системных утилит типа ping и traceroute.
Рисунок 1
Низкоуровневые сокеты позволяют вам включать в буфер с данными заголовки некоторых протоколов. Например, вы можете включить в ваше сообщение TCP- или UDP-заголовок, предоставив системе сформировать для вас IP-заголовок, а можете вообще сформировать все заголовки самостоятельно. Разумеется, при этом вам придётся изучить работу соответствующих протоколов и строго соблюсти формат их заголовков, иначе программа работать не будет.
Чтобы проиллюстрировать всё это примером, я переписал программу sender из предыдущего раздела с использованием низкоуровневых UDP-сокетов. При этом мне пришлось вручную формировать UDP-заголовок отправляемого сообщения. Я выбрал для примера UDP, потому что у этого протокола заголовок выглядит совсем просто (рисунок 2).
Рисунок 2
Листинг 5. Программа sender с использованием низкоуровневых сокетов.
Функции для работы с адресами и DNS
В этом разделе мы обсудим несколько функций, без которых можно написать учебный пример, но без которых вряд ли обойдётся реальная программа. Поскольку для идентификации хостов в Internet широко используются доменные имена, мы должны изучить механизм преобразования их в IP-адреса. Кроме того мы изучим несколько удобных вспомогательных функций.
Эта функция получает имя хоста и возвращает указатель на структуру с его описанием. Рассмотрим эту структуру более подробно.
Следует иметь в виду, что функции gethostbyname и gethostbyaddr возвращают указатель на статическую область памяти. Это означает, что каждое новое обращение к одной из этих функций приведёт к перезаписи данных, полученных при преыдущем обращении. |
Параллельное обслуживание клиентов
Способ 1
Этот способ подразумевает создание дочернего процесса для обслуживания каждого нового клиента. При этом родительский процесс занимается только прослушиванием порта и приёмом соединений. Чтобы добиться такого поведения, сразу после accept сервер вызывает функцию fork для создания дочернего процесса (я предполагаю, что вам знакома функция fork ; если нет, обратитесь к документации). Далее анализируется значение, которое вернула эта функция. В родительском процессе оно содержит идентификатор дочернего, а в дочернем процессе равно нулю. Используя этот признак, мы переходим к очередному вызову accept в родительском процессе, а дочерний процесс обслуживает клиента и завершается ( _exit ).
С использованием этой методики наш эхо-сервер перепишется, как показано в листинге 6.
Листинг 6. Эхо-сервер (версия 2, fork)
Способ 2
Листинг 7. Эхо-сервер (версия 3, неблокирующие сокеты и select).
Работа по стандартным протоколам
Как я уже говорил, сокеты могут использоваться при написании приложений, работающих по протоколам прикладного уровня Internet (HTTP, FTP, SMTP и т. д.). При этом взаимодействие клиента и сервера происходит по той же самой схеме, что и взаимодействие эхо-клиента и эхо-сервера в нашем примере. Разница в том, что данные, которыми обмениваются клиент и сервер, интерпретируются в соответствии с предписаниями соответствующего протокола.
Например, веб-сервер может работать по следующему алгоритму.
Веб-броузер, который является клиентом по отношению к веб-серверу, может использовать похожий алгоритм.
Как видим, в работе по стандартным протоколам нет ничего сложного или принципиально нового.
Прорыв за пределы платформы
В мире Internet взаимодействие программ, работающих на разных платформах, встречается сплошь и рядом. Так, практически ежесекундно очередной Internet Explorer подсоединяется к веб-серверу Apache, а очередной Netscape Navigator совершенно спокойно подключается к IIS. Вот почему весьма полезно писать программы так, чтобы их можно было без труда переносить на другие платформы. В этом разделе мы посмотрим, как переносить Linux-программы, использующие сокеты, на платформу Windows.
Список основных отличий socket API и Winsock API выглядит примерно так.
Если переписать наш эхо-клиент с учётом приведённых особенностей Winsock API, а затем скомпилировать его под Windows (например, с помощью Visual C++), он вполне сможет взаимодействовать с эхо-сервером, работающим под Linux. Таким образом, сокеты позволяют решить проблему кроссплатформенного взаимодействия двух приложений.
Заключение
В этой статье мы рассмотрели целый ряд важных аспектов программирования сокетов. Тем самым мы заложили прочную основу для дальнейших исследований в этой области. Разумеется, большое количество деталей осталось за рамками нашей беседы. Но теперь вы сможете самостоятельно почерпнуть недостающую информацию из man-страниц Linux и из собственного практического опыта. Желаю удачи.