Kest
1. TCP протокол семейства TCP/IP
TCP - это один из самых широко используемых протоколов транспортного уровня. IP не предполагает установление соединения. Он просто передает дейтаграмму от узла к узлу, а при каком-либо нарушении она просто отбрасывается, о чем отправитель уведомляется ICMP-сообщением. Проверка принятых данных и повторная передача данных, не дошедших до получателя, ложится на TCP. Он следит за доставкой данных протоколом IP.
Главная функция TCP заключается в доставке сообщений без потерь. Для этого предварительно устанавливается соединение между приложением-отправителем и приложением-получателем, осуществляя надежную доставку дейтаграмм. Именно TCP производит повторную передачу искаженного или утерянного пакета.
TCP регламентирует передачу данных с прикладного уровня на уровень межсетевого взаимодействия и обратно. TCP должен отвечать за соблюдение приоритетов и защиту данных, за завершение приложения на более высоком уровне, ожидающего дейтаграммы, за обработку ошибок нижних уровней. за ведение таблиц состояний для всех потоков как в самом TCP, так и на других уровнях. Выделение всех этих функций в отдельный уровень освобождает разработчиков прикладных программ от решения задач управления потоком и обеспечения надежности передачи данных. Без TCP перечисленные функции пришлось бы реализовывать в каждой прикладной программе.
TCP является протоколом, ориентированным на соединение, обеспечивая сквозную передачу данных от машины-отправителя машине-получателю. Поскольку в нем применяется соединение, адресат, получивший дейтаграмму, должен уведомить отправителя об этом. Обычно используется термин виртуальный канал, чтобы указать, что машина-отправитель и машина-получатель обмениваются сообщениями, большинство из которых являются подтверждениями о получении или кодами ошибок.
TCP получает поток байтов и собирает его в пакеты, называемые также сегментами, добавляя заголовки в начало сегментов. В заголовок записывается контрольная сумма и порядковый номер пакета данных. Длина сегмента обычно определяется TCP или выбирается администратором системы. (В большинстве случаев длины сегмента TCP и дейтаграммы IP никак не связаны друг с другом.)
Процесс установления соединения начинается с передачи запроса на установления соединения от машины-отправителя машине-получателю. В запросе содержится число, называемое номером сокета. В ответ приложение-получатель посылает номер своего сокета. Номера сокетов отправителя и получателя однозначно определяют соединение между приложениями.
После установления соединения TCP начинает передавать сегменты сообщения IP-модулю, который преобразует каждый из них в одну или несколько дейтаграмм. Эти операции производятся без какого-либо участия TCP. Пройдя через сеть от машины-отправителя к машине-получателю, дейтаграммы поступают к IP-уровню последней. Он собирает из них отправленный сегмент и передает его TCP. В свою очередь сообщение от TCP поступает к приложению-получателю через используемый протокол прикладного уровня.
Если сообщение состоит из нескольких ТСР-сегментов (не путайте с IP-дейтаграммами), TCP на машине-получателе собирает его, исходя из порядковых номеров сегментов, хранящихся в заголовке. Если сегмент утерян или поврежден (последнее обнаруживается с помощью контрольной суммы в заголовке сегмента), отправителю посылается сообщение, содержащее порядковый номер ошибочного или потерянного сегмента. В этом случае отправитель повторно передает сегмент.
Если сообщение состоит только из одного сегмента TCP, то после сравнению полученной и вычисленной контрольных сумм программное обеспечение TCP на машине-получателе посылает отправителю подтверждение-квитанцию (АСК - acknowledgement). Выдача квитанции в ответ на принятый сегмент называется квитированием.
Как и для большинства протоколов с установлением соединения, важную роль в TCP играют таймеры. Сообщение считается переданным не полностью, если квитанция не поступила в течение заданного периода ожидания. Тем самым сокращаются затраты времени на бесполезное ожидание подтверждения в случае потери данных. При этом обычно производится повторная передача соответствующих сегментов.
В TCP не предусмотрена передача квитанции, если сегмент доставлен искаженным. Пропажу сегмента обнаруживают с помощью таймера повторной передачи: если квитанция на сегмент не поступила вовремя, он считается потерянным и передается повторно.
Вместе с тем применение таймеров обусловливает ряд проблем. Согласно протоколу TCP, квитанция на принятый сегмент выдается только после поступления всех отправленных ранее сегментов того же сообщения. Иначе говоря, квитанция подтверждает получение всех ранее отправленных сегментов сообщения. Так как сегменты доставляются машине-получателю в произвольном порядке, выдача квитанции может задержаться до получения всего сообщения, а это в свою очередь может привести к повторной передаче правильно принятых сегментов.
Повторно доставленные копии сегментов, отправленные из-за истечения установленного времени ожидания квитанций, отбрасываются программным обеспечением машины-получателя. При этом уведомление отправителю не высылается, так как ему важно лишь знать, что сегмент получен.
В передающей машине данные, получаемые программным обеспечением TCP от прикладного уровня, накапливаются в буфере. Момент их упаковки в сегмент обычно определяется на уровне TCP. Данные из буфера могут быть также отправлены в срочном порядке по требованию обслуживаемого процесса прикладного уровня. Эта операция называется выталкиванием. Ее применение вызывается установкой специального флажка в заголовке протокола прикладного уровня.
2. Порты и сокеты
Приложение, использующие TCP (или UDP), однозначно определяется некоторым числом, которое называется номером порта. В принципе номера портов можно выбирать произвольно, но для облегчения взаимодействия между различными реализациями программного обеспечения TCP, приняты соглашения о номерах портов, закрепленных за определенными службам.
Как правило, порты между 0 и 255 отводят системным процессам, а порты выше 255 - пользовательским. В Internet распределением портов занимается Управление Internet по нумерации (Internet Assigned Numbers Authority, IANA). Список номеров портов для служб Internet может быть найден в соответствующем рабочем предложении (RFC). Выдержки из него приведены в таблице. Порты 0 и 255 зарезервированы.
Любой канал связи в слое TCP однозначно определяется двумя числами, эта комбинация называется сокетом. Сокет определяется IP-адресом машины и номером порта, используемым программным обеспечением TCP. При соединении любая машина однозначно определена IP-адресом, а каждый выполняющийся процесс - портом, таким образом, соединение между двумя / процессами однозначно определяется сокетом.
3. Таймеры TCP
Таймеры нужны для того, чтобы избежать чрезмерных задержек и состояний ожидания. Они позволяют легко обойти некоторые подводные камни при передаче данных. Роль различных таймеров, используемых TCP, в передаче данных рассматривается в последующих разделах.
Таймер повторной передачи. Таймер повторной передачи отмеряет время ожидания квитанции на отправленный сегмент (retransmission timeout, RTO). Этот параметр устанавливается с учетом типа сети и скоростей доставки сообщений. Если квитанция не поступает вовремя, сегмент отправляется вновь, а период повторной передачи увеличивается по экспоненциальному закону. Так повторяется несколько раз пока период повторной передачи не достигнет некоторого заданного предела, после чего обслуживаемому процессу выдается сообщение об ошибке.
Начальное время ожидания квитанции путем устанавливается измерения времени между отправлением сегмента и получением квитанции на него. Эта величина называется временем двойного прохода. Математическое ожидание (то есть среднее значение) измеренных значений называется сглаженным временем двойного прохода. Оно вычисляется по формуле и может быть увеличено, чтобы учесть непредвиденные задержки.
Таймер задержки. Получателю могут поступать сегменты и после того, как соединение было им закрыто. Таймер задержки (quiet timer) исключает повторное открытие только что закрытого порта, вызываемое прибывшими сегментами. Длительность задержки обычно выбирают равной удвоенному значению максимального времени жизни сегмента (оно совпадает со значением поля времени жизни в заголовке IP-дейтаграммы). Задержка может достигать 30 секунд. В ответ на каждый пришедший в этот период сегмент, отправляется сообщение об ошибке.
Таймер запросов. Таймер запросов (persistence timer) предусмотрен для такого довольно редкого случая, когда получатель, приостановивший передачу данных путем посылки сегмента с нулевым размером окна, отправляет своему партнеру сообщение о возобновлении работы, но тот его не получает. Чтобы продолжить передачу, отправитель с периодом, задаваемым таймером, посылает запросы с одним байтом данных. В ответ на них он получает сегменты, где указан размер окна. Если размер окна нулевой, адресат по-прежнему занят, а если нет, то он готов принимать полезную информацию, после чего передача данных возобновляется. Период повторения запросов определяются таймером запросов.
Таймер контроля и таймер разъединения. Эти таймеры предназначены для проверки соединения. Таймер контроля (keep-alive timer) вызывает периодическую выдачу сегментов без поля данных, а таймер разъединения (idle timer) задает максимальное время ожидания ответа. По истечении этого срока соединение считается разорванным.
Как правило период таймера контроля устанавливается на прикладном уровне, его значения лежат между 5 и 45 секундами. Максимальное время ожидания обычно принимается равным 360 секундам.
4. Сегменты данных протокола TCP
TCP взаимодействует с нижележащим уровнем по протоколу IP и с прикладным уровнем, расположенным выше, с помощью сервисных примитивов. Кроме того он должен взаимодействовать с программным обеспечением TCP на других машинах. Для последней задачи используются протокольные блоки данных, которые на языке TCP называются сегментами. Заголовок сегмента состоит из следующих полей:
Порт отправителя (Source Port), 16-разрядное поле, идентифицирующее локальный порт-источник (обычно это пользовательский процесс прикладного уровня).
Порт получателя (Destination Port), 16-разрядное поле с номером порта-получателя
Позиция сегмента (Sequence Number). Поле содержит порядковый номер j первого байта данных сегмента в сообщении.
Первый ожидаемый байт (Acknowledgement Number). Используется 1 тогда, когда сегмент служит квитанцией (флаг АСК=1). Содержит по- / рядковый номер первого ожидаемого байта. Все байты сообщения с меньшими порядковыми номерами считаются квитированными.
Смещение данных (Data Offset). Длина заголовка, измеренная в 32-раз- / рядных словах. Служит указателем на начало поля данных.
Резерв (Reserved). Пока не используется и должно быть обнулено. Размер поля 6 бит.
Флаги. В состоянии 1 они означают следующее.
Флаг URG. Поле срочности данных подлежит обработке. Флаг АСК. Сегмент служит квитанцией.
Флаг PSH. Сегмент должен быть "вытолкнут" - послан в первую очередь.
Флаг RST. Сегмент служит запросом на установку первоначальных параметров соединения.
Флаг SYN. Сегмент служит для синхронизации счетчиков переданных данных при установлении соединения.
Флаг FIN. Означает, что отправлен последний байт сообщения. Эквивалент маркера конца передачи (EOT) в кодировке ASCII.
Размер окна (Window). Указывает, сколько байтов готов принять получатель.
Контрольная сумма (Checksum), 16-разрядная контрольная сумма определяется для блока данных, состоящего из псевдозаголовка и самого сегмента, 96-разрядный псевдозаголовок, предшествует заголовку TCP и создается в процедуре вычисления контрольной суммы. Псевдозаголовок содержит IP-адрес отправителя, IP-адрес получателя, идентификатор протокола и длину сегмента. Эти параметры передаются IP при отправке сегмента и используются протоколом IP при его получении. Процедура подсчета контрольной суммы занимает довольно много времени.
Указатель срочности данных (Urgent Pointer). Используется, когда флаг URG=1. Представляет собой смещение относительно номера последовательности в заголовке. Специальная обработка срочных данных производится на прикладном уровне, а не на уровне TCP.
Опции (Options). Подобно одноименному полю в IP-заголовке каждая опция содержит свой номер (один байт), свою длину в байтах и значение. В настоящее время имеется только три опции:
О - Конец списка опций (End of Option List);
1 - Отсутствие операции (No Operations);
2 - Максимальный размер сегмента (Maximum Segment Size).
Заполнитель (Padding). Дополняет заголовок до целого числа 32-разрядных слов.
За заголовком следует поле данных, длина которого не фиксирована. Благодаря опции Максимальный размер сегмента программное обеспечение TCP получателя может выбрать подходящий размер буфера данных.
5. Соединение по протоколу TCP
Обмен данными по протоколу TCP регулируется многочисленными правилами. Процедуры установления соединения, передачи полезной информации и разрыва соединения обычно представляют конечными автоматами. (TCP - это протокол, управляемый состояниями, и потому его операции зависят от состояний флагов или аналогичных структур). Вместо них я буду использовать простые схемы взаимодействия.
Существует два варианта установления соединений: собственно соединение и принятие соединения. Перед запуском соединения необходимо инициализировать и запустить WSA - Windows Socket Architecture. Выполним для этого функцию
1. WSAStartup ( wVersionRequested, lpWSAData ): Integer;
где:
wVersionRequested - необходимый номер версии для запуска приложения. В нашем случае этот параметр должен быть равен $0101.
lpWSAData - сруктура, в которой разъём WSA возвращает: номер версии, описание, статус, максимальное количество разъёмовсокетов, информация разработчика и т. д. Эта структура значения для нас не имеет.
Код ошибки, прозошедшей в случае неудачного выполнения любой функции, связанной с разъёмами сокетами можно получить с помощью функции WSAGetLastError. Расшифровка этих кодов находится в файле sock.hlp, входящем в поставку Delphi.
Вариант "Соединение"
Прежде чем соединять разъёмы,сокеты, нужно создать разъём сокет функцией
2. Socket (af, type, protocol) : Integer;
где:
af - семейство адресов, в нашем случае af:= PF_INET.
type - тип разъёма сокета (SOCK_STREAM, базирующийся на семействе адресов TCP и ориенти рованный на соединения и SOCK_DGRAM, базирующийся на семействе адресов UDP и работающий без соединений (используем SOCK_STREAM, т. к., он более надежный)).
protocol - параметр, определяющий некоторые особенности протокола, в нашем случае - 0.
После выполнения функция возвращает значение типа Integer, который является указателем на новый разъёмсокет. Далее, следует становить опции структуры TSockAddrIn, в которой содержится информация о соединении. У этой структуры много полей, нас же интересуют только три из них:
sin_family - содержит имя семейства адресов. Этот параметр аналогичен af.
sin_port - адрес номер порта, через который идет обмен с разъёмомсокетом. Адрес порта должен быть больше 1024, т. к. меньшие значения зарезервированы для существующих приложений. Номер порта должен быть задан в так называемом сетевом формате, когда старшие байты идут по младшим адресам. Это отличается от принятом в x86 (Intel) архитектуре хранении, поэтому необходимо выполнить преобразование с помощью функции htons() из формата данной архитектуры в сетевой (htons расшифровывается, как Host To Network, Short (т.е. 16-бит)). Обратный перевод можно выполнить с помощью функции ntohs().
sin_addr - у этого поля есть подполе s_addr: integer, которому мы должны присвоить значение функции inet_addr(iAddr). Эта функция преобразует строковый адрес IP в 4-байтовое число, т. е., iAddr - адрес компьютера, с которым мы хотим связаться. Получить адрес можно, выполнив на искомом компьютере команду NETSTAT -r. Адрес также должен быть представлен в сетевом формате, однако функция inet_addr() уже возвращает результат в сетевом формате и выполнять преобразование в этом случае не нужно. Если это необходимо выполнить, то можно использовать функции htonl() и ntohl(). Примечание: при передаче по сети данных в двоичном формате следует помнить о разной интерпретации порядка байт в слове на различных архитектурах и преобразовывать данные в сетевой формат.
Теперь, когда мы заполнили структуру адреса, выполняем функцию
3. Connect(Sock, SA, SASize ): Integer;
где:
Sock - результат выполнения функции Socket.
SA - заполненная нами структура TSockAddrIn.
SASize - размер SA.
Если в результате выполнения Connect'a возвратился SOCKET_ERROR, то функция выполнилась неправильно. ( см. Пример ) В противном случае, возвращается 0.
После установления соединения можно применять функции приема и пересылки данных, такие, как recv, send.
Функция коннект через API посылает сообщение к драйверу протокола ТСР, а тот, в свою очередь, через протоколы нижнего уровня посылает инициализационный пакет компьютеру-адресату для установления соединения. Функция завершается, когда соединение установлено.
4. Recv( Sock, Buf, BufSize, Flags ) : Integer;
где:
Sock - результат выполнения функции Socket.
Buf - буфер, куда попадут принятые данные.
BufSize - размер буфера.
Flags - флаги. Может иметь значения MSG_PEEK или MSG_OOB. Установим это поле в 0.
Если не произошло ошибки, возвращается количество принятых байт, если произошло корректное закрытие соединения - 0, иначе в случае разрыва - возвращается отрицательное число.
5. Send( Sock, Buf, BufSize, Flags ) : Integer;
где:
Sock - результат выполнения функции Socket.
Buf - буфер посылаемых данных.
BufSize - размер буфера.
Flags - флаги. Равно 0.
Если не произошло ошибки, возвращается количество принятых байт, если произошло корректное закрытие соединения - 0, иначе в случае разрыва - возвращается отрицательное число.
Для завершения работы соединения применим функцию
6. CloseSocket( Sock ) : Integer;
где:
Sock - результат выполнения функции Socket.
Если не произошло ошибок, возвращается 0, иначе - SOCKET_ERROR.
Вариант "Принятие cоединения"
Другим вариантом установления соединения является его принятие. Вообще говоря, в любом соединении должны присутствовать оба варианта - соединения и приема соединения ( один компьютер является сервером, он выполняет прием, друго клиентом, он инициирует прием ) . Порядок действий по инициализации сервера такой же, как и у клиента, с той разницей, что:
вместо адреса другого компьютера в структуру TSockAddrIn мы заносим свой адрес или '0.0.0.0' для принятия всех приходящих соединений.
вместо Connect'a выполняем функци :
7. Bind( Sock, SA, SASize ) : Integer;
где:
Sock - результат выполнения функции Socket.
SA - заполненная нами структура TSockAddrIn.
SASize - ее размер.
Если не произошло ошибок, возвращается 0, иначе - SOCKET_ERROR.
Эта функция привязывает наш разъём сокет к указанному нами адресу.
8. Listen( Sock, Backlog ) : Integer;
где:
Sock - результат выполнения функции Socket.
Backlog - максимальный размер очереди ожидающих соединений.
Если не произошло ошибок, возвращается 0, иначе - SOCKET_ERROR.
Эта функция устанавливает разъём сокет в режим прослушивания канала.
9. Accept( Sock, SA, SASize ) : Integer;
где:
Sock - результат выполнения функции Socket.
SA - заполненная нами структура TSockAddrIn.
SASize - размер SA.
Если в результате выполнения Accept'a возвратился SOCKET_ERROR, то функция выполнилась неправильно. ( см. Пример ) В противном случае, возвращается 0.
После установления соединения можно применять функции приема и пересылки данных, такие, как recv, send. Функция завершается, когда устанавливается соединение.
После соединения
После соединения можно приступать к обмену информацией с помощью вышеуказанных функций recv и send. Можно также добавить, что разъёмы сокеты классифицируются, как блокирующиеся и неблокирующиеся. Первые ждут окончания операции, вторые - не ждут.
Для установки разъёмов сокетов в неблокируемое состояние используют функцию:
ioctlsocket( Sock, CMD, Value) : Integer;
где:
Sock - результат выполнения функции Socket.
CMD - команда управления разъёмом сокетом ( в нашем сучае - константа FIONBIO ).
Value - ее значение 0 - вкл, 1 - выкл.
Если не произошло ошибок, возвращается 0, иначе - SOCKET_ERROR.
При непосредственной работе с разъемами сокетами вы далеко не всегда получите на приёмнике именно то количество байт, которое посылали, и вы должны через некоторое время "дочитать" байты из разъёмасокета. Это объясняется принципом организации потоковых разъемовсокетов, которые воспринимаются в данном случае как файлы. Можно, однако отключить алгоритм NAGLE, который управляет разбиением сообщений на дейтаграммы с помощью следующей функции:
setsockopt( Sock, Level, Parameter, PChar( Value ), ValueSize ) : Integer;
где:
Sock - результат выполнения функции Socket.
Level - уровень команды.
Parameter - команда управления разъёмом сокетом ( в нашем сучае - константа TCP_NODELAY ).
Value - ее значение (0 - вкл, 1 - выкл).
ValueSize - размер Value.
Если не произошло ошибок, возвращается 0, иначе - SOCKET_ERROR.
Определить адрес корреспондента можно из структуры TSockAddrIn после выполнения команды accept (см. Выше) либо с помощью функции getpeername ( см. Delphi sock.hlp ).
Определить имя компьютера по адресу можно с помощью функции GetHostByAddr ( см. Delphi sock.hlp ).
Ссылки по теме