Строго говоря, обеспечение правильного функционирования протоколов сетевого уровня — задача администратора системы, а не программиста. В своей работе программист чаще всего сталкивается с более высокоуровневыми протоколами и не интересуется деталями реализации сетевого уровня.
Протоколами транспортного уровня в стеке TCP/IP являются протоколы TCP и UDP. Строго говоря, они решают не только задачи транспортного уровня, но и небольшую часть задач уровня сессии. Тем не менее они традиционно называются транспортными. Эти протоколы мы рассмотрим детально в следующих разделах.
Уровни сессии, представлений и приложений в стеке TCP/IP не разделены: протоколы HTTP, FTP, SMTP и т.д., входящие в этот стек, решают задачи всех трех уровней. Мы здесь не будем рассматривать эти протоколы, потому что при использовании сокетов они в общем случае не нужны: программист сам определяет формат пакетов, отправляемых с помощью TCP или UDP.
Новички нередко думают, что фраза "программа поддерживает соединение через TCP/IP" полностью описывает то, как можно связаться с программой и получить данные. На самом деле необходимо знать формат пакетов, которые эта программа может принимать и отправлять, т.е. должны быть согласованы протоколы уровня сессии и представлений. Гибкость сокетов дает программисту возможность самостоятельно определить этот формат, т.е., по сути дела, придумать и реализовать собственный протокол поверх TCP или UDP. И без описания этого протокола организовать обмен данными с программой невозможно.
Протокол UDP (User Datagram Protocol — протокол пользовательских дейтаграмм) встречается реже, чем его "одноклассник" TCP, но он проще для понимания, поэтому мы начнем изучение транспортных протоколов с него. Коротко UDP можно описать как ненадежный протокол без соединения, основанный на дейтаграммах. Теперь рассмотрим каждую из этих характеристик подробнее.
UDP не имеет никаких исполнительных средств управления пакетами по сравнению с IP. Это значит, что пакеты, отправленные с помощью UDP, могут теряться, дублироваться и менять порядок следования. В сети без маршрутизаторов ничего этого с пакетами почти никогда не происходит, и UDP может условно считаться надежным протоколом. Сети с маршрутизаторами строятся, конечно же, таким образом, чтобы подобные случаи происходили как можно реже, но полностью исключить их, тем не менее, нельзя. Происходит это из-за того, что передача данных может идти несколькими путями через разные маршрутизаторы. Например, пакет может пропасть, если короткий путь к удаленному узлу временно недоступен, а в длинном приходится пройти больше маршрутизаторов, чем это разрешено. Дублироваться пакеты могут, если они ошибочно передаются двумя путями, а порядок следования может изменяться, если пакет, посланный первым, идет по более длинному пути, чем пакет, посланный вторым.
Все сказанное отнюдь не означает, что на основе UDP нельзя построить надежный обмен данными, просто заботу об этом должно взять на себя само приложение. Каждый исходящий пакет должен содержать порядковый номер, и в ответ на него должен приходить специальный пакет — квитанция, которая уведомляет отправителя, что пакет доставлен. При отсутствии квитанции пакет высылается повторно (для этого необходимо ввести тайм-ауты на получение квитанции). Принимающая сторона по номерам пакетов восстанавливает их исходный порядок.
UDP не поддерживает соединение. Это означает, что при использовании этого протокола можно в любой момент отправить данные по любому адресу без необходимости каких-либо предварительных действий, направленных на установление связи с адресатом. Это напоминает процесс отправки обычного письма: на нем пишется адрес, и оно опускается в почтовый ящик без каких-либо предварительных действий. Такой подход обеспечивает большую гибкость, но лишает систему возможности автоматической проверки исправности канала связи.
Дейтаграммами называются пакеты, которые передаются как единое целое. Каждый пакет, отправленный с помощью UDP, составляет одну дейтаграмму. Принятые дейтаграммы складываются в буфер принимающего сокета и могут быть получены только раздельно: за одну операцию чтения из буфера программа, использующая сокет, может получить только одну дейтаграмму. Если в буфере лежит несколько дейтаграмм, потребуется несколько операций чтения, чтобы прочитать все. Кроме того, одну дейтаграмму нельзя получить из буфера по частям: она должна быть прочитана целиком за одну операцию. Чтобы данные, передаваемые разным сокетам, не перемешивались, каждый сокет должен получить уникальный в пределах узла номер от 0 до 65 535, называемый номером порта. При отправке дейтаграммы отправитель указывает IP-адрес и порт получателя, и система принимающей стороны находит сокет, привязанный к указанному порту, и помещает данные в его буфер. По сути дела, UDP является очень простой надстройкой над IP, все функции которой заключаются в том, что физический поток разделяется на несколько логических с помощью портов, и добавляется проверка целостности данных с помощью контрольной суммы (сам по себе протокол IP не гарантирует отсутствия искажений данных при передаче).
Максимальный размер одной дейтаграммы IP равен 65 535 байтам. Из них не менее 20 байтов занимает заголовок IP. Заголовок UDP имеет размер 8 байтов. Таким образом, максимальный размер одной дейтаграммы UDP составляет 65 507 байтов.
Типичная область применения UDP — программы, для которых потеря пакетов некритична. Например, некоторые сетевые 3D-игры в локальной сети используют UDP, т.к. очень часто посылают пакеты, информирующие о действиях игрока, и потеря одного пакета не приведет к существенным проблемам: следующий пакет доставит необходимые данные. Достоинства UDP — простота установления связи, возможность обмена данными с несколькими адресами через один сокет и отсутствие необходимости возобновлять соединение после разрыва связи. В некоторых задачах также очень удобно то, что дейтаграммы не смешиваются, и получатель всегда знает, какие данные были отправлены одной дейтаграммой, а какие — разными.
Еще одно преимущество UDP — возможность отправки широковещательных дейтаграмм. Для этого нужно указать широковещательный IP-адрес (обычно 255.255.255.255, но в некоторых случаях допустимы адреса типа 192.168.100.225 для вещания в пределах сети 192.168.100.XX и т.п.), и такую дейтаграмму получат все сокеты в локальной сети, привязанные к заданному порту. Эту возможность нередко используют программы, которые заранее не знают, с какими компьютерами они должны связываться. Они посылают широковещательное сообщение и связываются со всеми узлами, которые распознали это сообщение и прислали на него соответствующий ответ. По умолчанию для широковещательных пакетов число маршрутизаторов, через которые они могут пройти (TTL), устанавливается равным нулю, поэтому такие пакеты не выходят за пределы подсети.
Протокол TCP (Transmission Control Protocol — протокол управления передачей) является надежным потоковым протоколом с соединением, т.е. полной противоположностью UDP. Единственное, что у этих протоколов общее, — это способ адресации: в TCР каждому сокету также назначается уникальный номер порта. Уникальность номера порта требуется только в пределах протокола: два сокета могут иметь одинаковые номера портов, если один из них работает через TCP, а другой через UDP.
В TCP предусмотрены так называемые хорошо известные (well-known) порты, которые зарезервированы для нужд системы и не должны использоваться программами. Стандарт TCP определяет диапазон хорошо известных портов от 0 до 255, в Windows и в некоторых других системах этот диапазон расширен до 0–1023. Часть портов UDP тоже выделена для системных нужд, но зарезервированного диапазона в UDP нет. Кроме того, некоторые системные утилиты используют порты за пределами диапазона 0–1023. Полный список системных портов для TCP и UDP содержится в MSDN, в разделе Resource Kits/Windows 2000 Server Resource Kit/TCP/IP Core Networking Appendixes/TCP and UDP Port Assignment.
Для отправки пакета с помощью TCP отправителю необходимо сначала установить соединение с получателем. После выполнения этого действия соединенные таким образом сокеты могут использоваться только для отправки сообщений друг другу. Если соединение разрывается (самой программой из-за проблем в сети), эти сокеты уже непригодны для установления нового соединения: они должны быть уничтожены, а вместо них созданы новые сокеты.
Механизм соединения, принятый в TCP, подразумевает разделение ролей соединяемых сторон: одна из них пассивно ждет, когда кто-то установит с ней соединение, и называется сервером, другая самостоятельно устанавливает соединение и называется клиентом. Действия клиента по установке связи заключаются в следующем: создать сокет, привязать его к адресу и порту вызвать функцию для установки соединения, передав ей адрес сервера. Если все эти операции выполнены успешно, то связь установлена, и можно начинать обмен данными. Действия сервера выглядят следующим образом: создать сокет, привязать его к адресу и порту, перевести в режим ожидания соединения и дождаться соединения. При соединении система создаст на стороне сервера специальный сокет, который будет связан с соединившимся клиентом, и обмениваться данными с подключившимся клиентом сервер будет через этот новый сокет. Старый сокет останется в режиме ожидания соединения. и другой клиент сможет к нему подключиться. Для каждого нового подключения будет создаваться новый сокет, обслуживающий только данное соединение, а исходный сокет будет по-прежнему ожидать соединения. Это позволяет нескольким клиентам одновременно соединяться с одним сервером, а серверу — не путаться в своих клиентах. Точное число клиентов, которые могут одновременно работать с сервером, в документации не приводится, но оно достаточно велико.