Системные вызовы getsockname() и getpeername() извлекают локальный адрес, к которому привязан сокет, и, соответственно, адрес удаленной стороны, к которой подключен этот сокет.

Мы рассмотрели ряд подробностей работы протокола TCP, включая его состояния и диаграмму их переходов, а также процедуры установки и разрыва соединений. Одновременно было показано, почему состояние TIME_WAIT играет важную роль в обеспечении надежности протокола TCP. И хотя в случае перезапуска сервера TIME_WAIT может привести к ошибке «этот адрес уже занят», мы объяснили, как избежать такой ситуации с помощью параметра сокета SO_REUSEADDR и в то же время позволить данному состоянию выполнять свою функцию.

Команды netstat и tcpdump могут пригодиться для мониторинга и отладки приложений, которые используют сокеты.

Системные вызовы getsockopt() и setsockopt() позволяют извлекать и изменять параметры, влияющие на работу сокетов.

В Linux новый сокет, создаваемый с помощью вызова accept(), не наследует флаги состояния открытого файла слушающего сокета, а также флаги и атрибуты файлового дескриптора, связанные с вводом/выводом, основанным на сигналах. Хотя параметры сокета все же наследуются. Мы отметили, что стандарт SUSv3 никак не оговаривает это поведение, вследствие чего оно может варьироваться в зависимости от реализации.

В отличие от TCP, протокол UDP не обладает механизмами обеспечения надежности, но, как мы могли убедиться, имеет ряд достоинств, которые делают его более подходящим для отдельных приложений.

В завершение мы кратко рассмотрели несколько продвинутых возможностей сокетов.

Дополнительная информация

Ознакомьтесь с источниками, приведенными в разделе 55.14.

57.15. Упражнения

57.1. Представьте, что программа из листинга 57.2 (is_echo_cl.c) была модифицирована и теперь вместо применения вызова fork() для создания потомков, работающих параллельно, используется всего один процесс, который сначала копирует свой стандартный ввод в сокет, а затем считывает ответ сервера. С какой проблемой можно столкнуться при работе с этим клиентом (см. рис. 54.8)?

57.2. Реализуйте вызов pipe() по примеру socketpair(). Задействуйте вызов shutdown(), чтобы сделать итоговый канал однонаправленным.

57.3. Реализуйте замену sendfile() с применением вызовов read(), write() и lseek().

57.4. Напишите программу на основе вызова getsockname(), демонстрирующую, что сокет, для которого операция listen() выполняется без предварительного вызова bind(), привязывается к динамическому порту.

57.5. Напишите клиентскую и серверную программы, позволяющие выполнять произвольные консольные команды на удаленном компьютере. (Если вы не собираетесь реализовывать в этом приложении никаких механизмов безопасности, то следует сделать так, чтобы сервер работал от имени обычной учетной записи и не мог причинить существенного вреда в случае использования злоумышленниками.) Клиент должен запускаться с помощью двух аргументов командной строки:

$./is_shell_cl server-host 'some-shell-command'

Подключившись к серверу, клиент отправляет ему заданную команду, после чего закрывает свой записывающий канал сокета путем вызова shutdown(), чтобы на другом конце можно было увидеть конец файла. Серверу следует обрабатывать каждое входящее соединение с помощью отдельного дочернего процесса (то есть параллельно). Каждый потомок должен прочитать данные из своего сокета (пока не столкнется с завершением файла) и затем запустить командную оболочку для выполнения соответствующей команды. Несколько подсказок:

• за пример запуска консольных команд возьмите реализацию вызова system() из раздела 27.7;

• используйте вызов dup2(), чтобы продублировать дескриптор сокета для стандартных потоков stdout и stderr — благодаря этому запущенная команда автоматически будет записывать свой вывод в сокет.

57.6. В подразделе 57.13.1 отмечалось: в качестве альтернативы внеканальным данным между клиентом и сервером можно было бы создать два TCP-соединения: одно — для обмена обычной информацией, а другое — для высокоприоритетных данных. Напишите клиентскую и серверную программы, которые реализуют этот принцип. Вот несколько подсказок.

• Сервер должен каким-то образом знать, какие два сокета принадлежат клиенту. Этого можно добиться, создав на клиентской стороне слушающий сокет и привязав его к динамическому порту (с номером 0). Получив номер своего динамического порта (с помощью вызова getsockname()), клиент соединяет другой свой сокет со слушающим сокетом сервера и отправляет сообщение, содержащее номер динамического порта клиента. Затем клиент ждет, чтобы сервер мог подключиться к его слушающему сокету и установить соединение для «приоритетных» данных, направленное в обратную сторону (сервер может получить IP-адреса клиента во время выполнения вызова accept() для обычного соединения).

Перейти на страницу:

Похожие книги