Эффективное программирование TCP-IP

       

В этой главе приведен краткий


В этой главе приведен краткий обзор последующих глав и рассмотрены эле­менты API сокетов. Теперь можно перейти к более сложному материалу.
| | |


В этом разделе обсуждены различия между протоколами, которые требуют и не требуют установления логического соединения. Вы узнали, что ненадежные протоколы, в которых происходит обмен датаграммами без установления соединения, - это фундамент, на котором строятся надежные протоколы на базе соединений. Попутно было кратко изложено, как надежный протокол TCP строится на основе ненадежного протокола IP.
Также отмечалось, что понятие «соединение» в TCP носит умозрительный характер. Оно состоит из хранящейся информации о состоянии на обоих концах; никакого «физического» соединения, как при телефонном разговоре, не существует.
| | |


В этом разделе рассмотрены подсети и бесклассовая междоменная маршрутизация (CIDR). Вы узнали, как они применяются для решения двух проблем, свойственных адресации на основе классов. Подсети позволяют предотвратить рост маршрутных таблиц, обеспечивая в то же время гибкую адресацию. CIDR служит эффективного выделения IP-адресов и способствует их иерархическому назначению.
| | |


В этом разделе показано, как схема NAT позволяет использовать один из блоков частных сетевых адресов для внутренних хостов, сохраняя при этом возможность выхода в Internet. Метод PAT, в частности, особенно полезен для небольших сетей, у которых есть только один глобально выделенный IP-адрес. К сожалению, поскольку PAT изменяет номер порта в исходящих пакетах, он может оказаться несовместимым с нестандартными протоколами, которые передают информацию о номерах портов в теле сообщения.
| | |




Прочитав данный раздел, вы узнали, как просто создать целый арсенал каркасов и библиотечных функций. Все построенные каркасы очень похожи и различаются только несколькими строками в стартовом коде внутри функции main. Таким образом, после написания первого каркаса пришлось лишь скопировать код и подправить эти несколько строк. Эта методика очень проста. Поэтому, чтобы со­здать несколько элементарных клиентов и серверов, потребовалось только вставить содержательный код вместо заглушек.
Использование каркасов и написание библиотечных функций закладывает тот фундамент, на котором далее легко строить приложения и небольшие тестовые программки для их проверки.
| | |


В обычных ситуациях нет смысла использовать интерфейс XTI/TLI при программировании TCP/IP. Интерфейс сокетов проще и обладает большей переносимостью, а возможности обоих интерфейсов почти одинаковы.
| | |


Типичная ошибка, допускаемая начинающими сетевыми программистами, - в непонимании того, что TCP доставляет поток байтов, в котором нет понятия «границы записей». В TCP нет видимой пользователю концепции «пакета». Он просто передает поток байтов, и нельзя точно предсказать, сколько байтов будет возвращено при очередном чтении. В этом разделе рассмотрено несколько способов работы в таких условиях.
| | |


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


Здесь рассмотрены шаги, необходимые для построения надежного протокола поверх UDP. Хотя и существуют приложения, например, DNS, в которых это сделано, но для корректного решения такой задачи необходимо практически заново реализовать TCP. Поскольку маловероятно, что реализованный на базе UDP протокол будет так же эффективен, как TCP, смысла в этом, как правило, нет.
В этом разделе также кратко обсуждается протокол Т/ТСР - модификация TCP для оптимизации транзакционных приложений. Хотя Т/ТСР решает многие проблемы, возникающие при использовании TCP для реализации транзакций, он пока не получил широкого распространения.
| | |


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


Хотя TCP и не предоставляет средств для немедленного уведомления клиента о потере связи, тем не менее несложно самостоятельно встроить такой механизм в приложение. Здесь рассмотрены две модели реализации контрольных сообщений-пульсов. На первый взгляд, это может показаться избыточным, но одна модель не подходит для всех случаев.
Первый способ применяется, когда приложения обмениваются между собой сообщениями, содержащими поле идентификатора типа. В этом случае все очень просто: достаточно добавить еще один тип для сообщений-пульсов. «Родители» могут спокойно работать - их «дети» под надежным присмотром.
Второй способ применим в ситуации, когда приложения обмениваются потоком байтов без явно выраженных границ сообщений. В качестве примера можно назвать передачу последовательности нажатий клавиш. В данном примере использовано отдельное соединение для приема и передачи пульсов. Разумеется, тот метод можно было бы применить и в первом случае, но он несколько сложнее, чем простое добавление нового типа сообщения.
В книге «UNIX Network Programming» [Stevens 1998] описан еще один метод организации пульсации с помощью механизма срочных данных, имеющегося в TCP. Это лишний раз демонстрирует, какие разнообразные возможности иметь в распоряжении прикладного программиста для организации уведомления приложения о потере связи.
Наконец, следует напомнить, что хотя было сказано только о протоколе TCP, то же самое верно и в отношении UDP. Представим сервер, который посылает широковещательные сообщения нескольким клиентам в локальной сети или организует групповое вещание на глобальную сеть. Поскольку соединения нет, клиенты имеют информации о крахе сервера, хоста или сбое в сети. Если в датаграммах есть поле типа, то серверу нужно лишь определить тип для датаграммы-пульса и посылать ее, когда в сети какое-то время не было других сообщений. Вместо этого он мог бы рассылать широковещательные датаграммы на отдельный порт, который клиенты прослушивают.
| | |


Вы всегда должны быть готовы к неожиданным действиям со стороны пользователей и хостов на другом конце соединения. В этом разделе рассмотрено два примера некорректного поведения другой стороны. Во-первых, нельзя надеяться на то, что партнер обязательно сообщит вам о прекращении передачи данных. Во-вторых, продемонстрирована важность проверки правильности входных данных и разработана функция readline, устойчивая к ошибкам.
| | |


Локальная сеть, которая представляет собой почти идеальную среду, может маскировать проблемы производительности и даже ошибки. Не думайте, что приложение, работающее в локальной сети, будет также хорошо работать и в глобальной.
Из-за сетевых задержек приложение, производительность которого в локальной сети была удовлетворительной, в глобальной сети может работать неприемлемо медленно. В результате иногда приходится перепроектировать программу.
Из-за перегрузок в интенсивно используемой глобальной сети, особенно в Internet, данные могут доставляться как внезапно, так и пакетами неожиданного размера. Это требует от вас особой осторожности в допущениях о том, сколько данных может прийти в определенный момент и с какой частотой они поступают.
Хотя в этом разделе говорилось исключительно о протоколе TCP, то же относится и к UDP, поскольку он не обладает встроенной надежностью, чтобы противостоять тяжелым условиям в Internet.
| | |


В этом разделе обсуждалось, насколько важно разбираться в функционировании протоколов. Отмечено, что официальной спецификацией TCP/IP являются RFC и рекомендованы книги Комера и Стивенса в качестве дополнительного источника информации о протоколах и их работе.
| | |


В этом разделе дано сравнение моделей OSI и TCP/IP. Вы узнали, что семиуровневая модель OSI нужна как средство описания сетевой архитектуры, но созданные на ее базе реализации почти не имеют успеха.
| | |


В этом разделе подробно рассмотрена операция записи. С точки зрения прило­жения операцию записи проще всего представлять как копирование из адресного пространства пользователя в буферы ядра и последующий возврат. Срок передачи данных TCP и их объем зависят от состояния соединения, а приложение не умеет подобной информации.
Проанализированы стратегия отправки, принятая в BSD TCP, а также влия­ние на нее объема буферной памяти у получателя (представлен окном передачи), оценки загрузки сети (представлена окном перегрузки), объема данных, готовых для передачи, попытки избежать синдрома безумного окна и стратегии повторной передачи.
| | |


Вы изучили системный вызов shutdown и сравнили его с вызовом close. Так­же рассказывалось, что с помощью shutdown можно закрыть только отправляю­щую, принимающую или обе стороны соединения; и счетчик ссылок на сокет при этом изменяется иначе, чем при закрытии с помощью close.
Затем было показано, как использовать shutdown для аккуратного размыка­ния соединения. Аккуратное размыкание - это последовательность разрыва соеди­нения, при которой данные не теряются.
| | |


В этом разделе показано, как заставить приложение работать в сети, приложив совсем немного усилий. Демон inetd берет на себя ожидание соединений или датаграмм, дублирует дескриптор сокета на stdin, stdout и stderr и запускает приложение. После этого приложение может просто читать из stdin или писать в stdout либо stderr, не имея информации о том, что оно работает в сети. Рассмотрен пример простого фильтра, в котором вообще нет кода, имеющего отноше­ние к сети. Но этот фильтр тем не менее прекрасно работает в качестве сетевого сервиса, если запустить его через inetd.
Здесь также приведен пример UDP-сервера, который способен вести продолжительный диалог с клиентами. Для этого серверу пришлось получить новый сокет и номер порта, а затем создать новый процесс и выйти.
| | |


Сервис TCPMUX, имеющийся на очень многих системах, помогает решить проблему выбора хорошо известного номера порта сервера. Здесь реализована собственная версия демона tcpmux, так что если в какой-то системе его нет, то им можно воспользоваться.
| | |


В этом разделе обсуждалась идея об использовании двух соединений между приложениями. Это позволяет отслеживать состояние соединения даже тогда, когда чтение и запись производятся в разных процессах.
| | |


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


В этом и предыдущем разделах говорилось о событийно-управляемом программировании и о том, как использовать вызов select для реагирования на события по мере их поступления. В совете 20 разработана функция tselect, позволившая получить несколько логических таймеров из одного физического. Эта Функция и используемые с ней функции timeout и untimeout дают возможность задавать тайм-ауты сразу для нескольких событий, инкапсулируя внутри себя все сопутствующие этому детали.
Здесь была использована функция tselect, чтобы усовершенствовать пример совета 19. Применение tselect позволило задавать отдельные таймеры ретрансмиссии для каждого сообщения, посланного ненадежному удаленному хосту через сервер-шлюз xout3.
| | |


В этом разделе обсуждено состояние TIME-WAIT, которое часто понимают неправильно. Это состояние - важная часть механизма обеспечения надежности протокола TCP, и попытки обойти его неверны. Преждевременный выход из состояния TIME-WAIT может быть обусловлен «естественным» стечением обстоятельств в сети или программой, которая манипулирует опцией SO_LINGER.
| | |


В этом разделе рассмотрена опция сокета SO_REUSEADDR. Ее установка позволяет перезапустить сервер, от предыдущего «воплощения» которого еще осталось соединение в состоянии TIME-WAIT. Серверы должны всегда устанавливать эту опцию, которая не влечет угрозу безопасности.
| | |


В этом разделе разобран алгоритм Нейгла и его взаимодействие с алгоритмом отложенного подтверждения. Приложения, записывающие в сеть несколько маленьких блоков вместо одного большого, могут заметно снизить производительность.
Поскольку алгоритм Нейгла помогает предотвратить действительно серьезную проблему - переполнение сети крохотными пакетами, не следует отключать его для повышения производительности приложений, выполняющих запись мелкими блока­ми. Вместо этого следует переписать приложение так, чтобы все логические связан­ные данные выводились сразу. Здесь был рассмотрен удобный способ решения этой задачи с помощью системного вызова writev в UNIX или WSASend в Winsock.
| | |


Как видите, для переноса на разные платформы прерывать вызов connect с помощью тайм-аута более сложно, чем обычно. Поэтому при выполнении такого действия надо уделить особое внимание платформе.
Наконец, следует понимать, что сократить время ожидания connect можно, а увеличить - нет. Все вышерассмотренные методы направлены на то, чтобы прервать вызов connect раньше, чем это сделает TCP. He существует переносимого механизма для изменения значения тайм-аута TCP на уровне одного сокета.
| | |


В этом разделе описано, как избежать ненужного копирования данных. Во многих сетевых приложениях на копирование данных из одного буфера в другой тратится большая часть времени процессора.
Разработана схема взаимодействия между процессами, в которой использую система буферов в разделяемой памяти. Это позволило передавать единственнь экземпляр данных от одного процесса другому. Такая схема работает и в UNIX и в Windows.
| | |


В этом разделе рассказывалось, что в TCP/IP применяется стандартное представление в сетевом порядке байт для целых чисел, входящих в заголовки прото колов. Здесь также приведены функции htonl, htons, ntohl и ntohs, которь преобразуют целые из машинного порядка байт в сетевой и обратно. Кроме того. было отмечено, что в общем случае для преобразования форматов данных между машинами полезно средство XDR.
| | |


В этом разделе рекомендовано не «зашивать» адреса и номера портов в про­грамму. Также рассмотрено несколько стандартных схем получения этой информации и обсуждены их достоинства и недостатки.
| | |


В этом разделе рассмотрено использование вызова connect в протоколе UDP. Хотя на первый взгляд может показаться, что для протокола без установления со­единения это не имеет смысла, но, как вы видели, такое действие, во-первых, повышает производительность, а во-вторых, оно необходимо при желании получать некоторые сообщения об ошибках при отправке UDP-датаграмм. Здесь также описано, как использовать connect для приема датаграмм только от одного хоста.
| | |


В этом разделе говорилось об использовании языков сценариев в сетевом программировании. Нередко их применение имеет смысл при написании небольших утилит и тестовых программ.
| | |


Производительность TCP в значительной степени зависит от размеров буферов приема и передачи (совет 36). В этом разделе вы узнали, что оптимальный размер буфера для эффективной передачи больших объемов данных равен произведению полосы пропускания на задержку, но на практике это наблюдение не особенно полезно.
Хотя правило произведения применять трудно, есть другое, намного проще. Ему и рекомендуется всегда следовать: размер буфера передачи должен быть, по крайней мере, в три раза больше, чем MSS.
| | |


Утилита ping - это один из важнейших инструментов тестирования связи в сети. Поскольку для ее работы требуется лишь функционирование самых нижних уровней сетевых служб, она полезна для проверки связи в условиях, когда сервисы более высокого уровня, такие как TCP, или программы прикладного уровня типа telnet не работают.
С помощью ping часто удается сделать выводы об условиях в сети, наблюдая за значениями и дисперсией RTT и за числом потерянных ответов.
| | |


Программа tcpdump - это незаменимый инструмент для изучения того, что происходит в сети. Если знать, что в действительности посылается или принимается «по проводам», то трудные, на первый взгляд, ошибки удается легко найти и исправить. Эта программа представляет собой также важный инструмент для исследований динамики сети, а равно средство обучения. В последнем качестве она широко при­меняется в книгах серии «TCP/IP Illustrated», написанных Стивенсом.
| | |


Утилита traceroute - очень полезный инструмент для диагностики сетевых ошибок, изучения маршрутизации и исследования топологии сети. Топология Internet нередко достаточно запутанна, и это может быть причиной неожиданного поведения приложений. С помощью traceroute зачастую удается обнаружить аномалии в сети, из-за которых программа ведет себя странно.
Программы traceroute и tracert работают путем отправки хосту назначения датаграммы с последовательно увеличивающимся значением в поле TTL. Затем они отслеживают приходящие от промежуточных маршрутизаторов ICMP-сообщения «истекло время в пути». Разница в том, что traceroute посылает UDP-датаграммы, a tracert - эхо-запросы ICMP.
| | |


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


Здесь показано, как можно воспользоваться утилитой lsof для получения ответа на разнообразные вопросы об открытых файлах. К сожалению, нет версии lsof для Windows.
| | |


Здесь приведены утилита netstat и те сведения о системе, которые можно получить с ее помощью, netstat сообщает об активных сокетах, о сконфигурированных сетевых интерфейсах, о маршрутной таблице и о статистике протоколов. Иными словами, она выдает отчеты о самых разнообразных аспектах сетевой подсистемы, причем в различных форматах.
| | |


Здесь описано два способа применения утилиты трассировки системных вызовов. В первом примере ошибку удалось обнаружить путем анализа системных вызовов, выполненных приложением. Во втором примере надо было отслеживать не очередность системных вызовов, а время выполнения некоторых из них.
Ранее уже говорилось о том, что для выяснения причин аномального поведения программы часто бывает необходимо сопоставить результаты, полученные от различных утилит. Программы трассировки системных вызовов, такие как ktrace, truss и strace, - это еще одно средство анализа в арсенале сетевого программиста.
| | |


В этом разделе разработан инструмент для перехвата и печати ICMP-сообщений. Такая программа помогает при диагностике ошибок сети и маршрутизации.
В ходе разработки программы icmp использованы простые сокеты. Здесь вы познакомились с форматами IP- и UDP-датаграмм, а также со структурой ICMP-сообщений.
| | |


Один из лучших способов изучения сетевого программирования (да и любого другого) - это чтение программ, написанных людьми, уже достигшими верши] мастерства. До недавнего времени было нелегко получить доступ к исходным текстам операционных систем и их сетевых подсистем. Но в связи с распространением движения за открытость исходных текстов ситуация изменилась. Ко, нескольких реализаций стека TCP/IP и соответствующих утилит (telnet, FTI inetd и т.д.) доступен для проектов FreeBSD и Linux. Здесь приведены лишь некоторые источники, в Internet можно найти множество других.
Особенно полезны книги, в которых есть не только код, но и подробные комментарии к нему.
| | |

Содержание раздела