Книга: UNIX: разработка сетевых приложений
8.9. Запуск клиента без запуска сервера
8.9. Запуск клиента без запуска сервера
Следующий сценарий, который мы рассмотрим, — это запуск клиента без запуска сервера. Если мы сделаем так и введем одну строку на стороне клиента, ничего не будет происходить. Клиент навсегда блокируется в своем вызове функции recvfrom
, ожидая ответа сервера, который никогда не придет. Но в данном примере это не имеет значения, поскольку сейчас мы стремимся глубже понять протоколы и выяснить, что происходит с нашим сетевым приложением.
Сначала мы запускаем программу tcpdump
на узле macosx
, а затем — клиент на том же узле, задав в качестве узла сервера freebsd4. Потом мы вводим одну строку, но эта строка не отражается сервером.
macosx % udpcli01 172.24.37.94
hello, world мы вводим эту строку,
но ничего не получаем в ответ
В листинге 8.6 показан вывод программы tcpdump
.
Листинг 8.6. Вывод программы tcpdump, когда процесс сервера не запускается на узле сервера
01 0.0 arp who-has freebsd4 tell macosx
02 0.003576 (0.0036) arp reply freebsd4 is-at 0:40:5:42:d6:de
03 0.003601 (0.0000) macosx.51139 > freebsd4.9877: udp 13
04 0.009781 (0.0062) freebsd4 > macosx: icmp: freebsd4 udp port 9877 unreachable
В первую очередь мы замечаем, что запрос и ответ ARP получены до того, как узел клиента смог отправить дейтаграмму UDP узлу сервера. (Мы оставили этот обмен в выводе программы, чтобы еще раз подчеркнуть, что до отправки IP-дейтаграммы всегда следует отправка запроса и получение ответа по протоколу ARP.)
В строке 3 мы видим, что дейтаграмма клиента отправлена, но узел сервера отвечает в строке 4 сообщением ICMP о недоступности порта. (Длина 13 включает 12 символов плюс символ новой строки.) Однако эта ошибка ICMP не возвращается клиентскому процессу по причинам, которые мы кратко перечислим чуть ниже. Вместо этого клиент навсегда блокируется в вызове функции recvfrom
в листинге 8.4. Мы также отмечаем, что в ICMPv6 имеется ошибка «Порт недоступен», аналогичная ошибке ICMPv4 (см. табл. А.5 и А.6), поэтому результаты, представленные здесь, аналогичны результатам для IPv6.
Эта ошибка ICMP является асинхронной ошибкой. Ошибка была вызвана функцией sendto
, но функция sendto
завершилась нормально. Вспомните из раздела 2.9, что нормальное возвращение из операции вывода UDP означает только то, что дейтаграмма была добавлена к очереди вывода канального уровня. Ошибка ICMP не возвращается, пока не пройдет определенное количество времени (4 мс для листинга 8.6), поэтому она и называется асинхронной.
Основное правило состоит в том, что асинхронные ошибки не возвращаются для сокета UDP, если сокет не был присоединен. Мы показываем, как вызвать функцию connect
для сокета UDP, в разделе 8.11. Не все понимают, почему было принято это решение, когда сокеты были впервые реализованы. (Соображения о реализациях обсуждаются на с. 748-749 [128].) Рассмотрим клиент UDP, последовательно отправляющий три дейтаграммы трем различным серверам (то есть на три различных IP-адреса) через один сокет UDP. Клиент входит в цикл, вызывающий функцию recvfrom
для чтения ответов. Две дейтаграммы доставляются корректно (то есть сервер был запущен на двух из трех узлов), но на третьем узле не был запущен сервер, и третий узел отвечает сообщением ICMP о недоступности порта. Это сообщение об ошибке ICMP содержит IP-заголовок и UDP-заголовок дейтаграммы, вызвавшей ошибку. (Сообщения об ошибках ICMPv4 и ICMPv6 всегда содержат заголовок IP и весь заголовок UDP или часть заголовка TCP, чтобы дать возможность получателю сообщения определить, какой сокет вызвал ошибку. Это показано на рис. 28.5 и 28.6.) Клиент, отправивший три дейтаграммы, должен знать получателя дейтаграммы, вызвавшей ошибку, чтобы точно определить, какая из трех дейтаграмм вызвала ошибку. Но как ядро может сообщить эту информацию процессу? Единственное, что может возвратить функция recvfrom
, — это значение переменной errno
. Но функция recvfrom
не может вернуть в ошибке IP-адрес и номер порта получателя UDP-дейтаграммы. Следовательно, было принято решение, что эти асинхронные ошибки возвращаются процессу, только если процесс присоединил сокет UDP лишь к одному определенному собеседнику.
ПРИМЕЧАНИЕ
Linux возвращает большинство ошибок ICMP о недоступности порта даже для неприсоединенного сокета, если не включен параметр сокета SO_DSBCOMPAT. Возвращаются все ошибки о недоступности получателя, показанные в табл. А.5, за исключением ошибок с кодами 0, 1, 4, 5, 11 и 12.
Мы вернемся к проблеме асинхронных ошибок с сокетами UDP в разделе 28.7 и покажем простой способ получения этих ошибок на неприсоединенном сокете при помощи нашего собственного демона.
- 8.1. Введение
- 8.2. Функции recvfrom и sendto
- 8.3. Эхо-сервер UDP: функция main
- 8.4. Эхо-сервер UDP: функция dg_echo
- 8.5. Эхо-клиент UDP: функция main
- 8.6. Эхо-клиент UDP: функция dg_cli
- 8.7. Потерянные дейтаграммы
- 8.8. Проверка полученного ответа
- 8.9. Запуск клиента без запуска сервера
- 8.10. Итоговый пример клиент-сервера UDP
- 8.11. Функция connect для UDP
- 8.12. Функция dg_cli (продолжение)
- 8.13. Отсутствие управления потоком в UDP
- 8.14. Определение исходящего интерфейса для UDP
- 8.15. Эхо-сервер TCP и UDP, использующий функцию select
- 8.16. Резюме
- Упражнения
- Запуск InterBase-сервера
- Расширенная установка InterBase-сервера
- Практическая работа 53. Запуск Access. Работа с объектами базы данных
- Восстановление "безнадежных" баз данных. InterBase Surgeon
- Статистика InterBase-сервера
- Аватар идеального клиента
- Система безопасности InterBase
- Общие рекомендации по безопасности
- Минимальный состав сервера InterBase SuperServer
- Надежность и безопасность
- Отличительные особенности сервера Yaffil
- Безопасная работа с внешними таблицами