Хотя обработка ввода TCP более сложна, чем обработка ввода UDP, предшествующие разделы предоставили нам основу, которая нужна для исследования реальной работы. Как всегда, входная процедура вызывается с параметрами
void tcp_input( struct mbuf *msg, int offO);
Первые несколько шагов, возможно, начинают казаться знакомыми
Найти TCP-заголовок в полученной IP дейтаграмме. Убедиться, что пакет вмещает по крайней мере TCP-заголовок, и использовать при необходимости т_pullup(), чтобы сделать его непрерывным.
- Вычислить размер пакета, установить псевдозаголовок IP и контрольную сумму заголовка и данных TCP. Уничтожить пакет, если контрольная сумма неправильная.
- Проверить размер TCP-заголовка; если он больше минимального заголовка, убедиться, что весь заголовок является непрерывным.
- Найти управляющий блок протокола для соединения с указанным номером порта. Если его нет, отправить пакет, содержащий флаг сброса RST, и уничтожить пакет.
- Проверить, прослушивает ли сокет соединения; если да, следовать процедуре, описанной для установления пассивного соединения.
- Обработать все опции TCP из заголовка пакета.
- Очистить время бездействия для соединения и установить обычное значение сторожевого таймера.
В этот момент были сделаны обычные проверки, и мы готовы иметь дело с данными и управляющими флагами в полученном пакете. Имеется по-прежнему много проверок целостности, которые нужно сделать в ходе обычной обработки; например, флаг SYN должен присутствовать, если мы все еще устанавливаем соединение, и не должен присутствовать, если соединение было установлено. Мы опустим большинство этих проверок из нашего обсуждения, но тесты важны для предотвращения создания путаницы и возможной порчи данных неверными пакетами.
Следующим шагом является проверка того, является ли TCP-пакет приемлемым в соответствии с окном приема. Важно выполнить этот шаг до проверки управляющих флагов (в частности, RST) поскольку старые или посторонние пакеты не должны влиять на текущее соединение, если они несомненно не являются относящимися к текущему контексту. Сегмент приемлем, если приемное окно имеет ненулевой размер и если по крайней мере некоторая часть пространства последовательностей, занимаемых пакетом, попадает в пределы приемного окна. Если пакет содержит данные, некоторая часть данных должна попасть в пределы окна. Часть данных, предшествующих окну, обрезается, поскольку она была уже получена, а часть, выходящая за пределы окна, также уничтожается, поскольку она была отправлена преждевременно. Если приемное окно закрыто (rcvjwnd равно нулю), приемлемыми являются лишь сегменты без данных с номером последовательности, равным rcvnxt. Если входящий пакет является неприемлемым, он сбрасывается после отправки подтверждения.
Обработка входящих TCP-пакетов должна быть полностью общей, принимающей во внимание все возможные входящие пакеты и все возможные состояния принимающих конечных точек. Однако большая часть обрабатываемых пакетов попадает в две основные категории. Типичные пакеты содержат либо следующий ожидающийся сегмент данных для существующего соединения, либо подтверждение плюс обновление окна для одного или более сегментов данных без дополнительных флагов или указаний состояния. Вместо рассмотрения каждого входящего сегмента, основываясь на первых принципах, tcpinput() сначала проверяет эти общие случаи. Этот алгоритм известен как предсказание заголовка. Если входящий сегмент подходит к соединению с состоянии ESTABLISHED, если он содержит флаг АСК, но не содержит других флагов, если номер последовательности является следующим ожидавшимся значением (и отметка времени, если она есть, не уменьшается), если поле окна является тем же самым, как в предыдущем сегменте, и если соединение не находится в состоянии повторной отправки, тогда входящий сегмент относится к одному из двух общих типов.
Система обрабатывает все опции отметки времени, которые содержит сегмент, записывая значение, полученное для включения в следующее подтверждение. Если сегмент не содержит данных, это чистое подтверждение с обновлением окна. В обычном случае определяется информация о времени обращения, если она доступна, подтвержденные данные удаляются из очереди отправки буфера, а значения последовательности обновляются. После проверки значений заголовка пакет уничтожается. Таймер повторной передачи отменяется, если все ожидающие данные были подтверждены; в противном случае он запускается повторно. Уровень сокетов уведомляется, если какой-нибудь процесс мог ожидать вывода. Наконец, вызывается tcpoutput(), поскольку окно передвинулось вперед, и эта операция завершает обработку чистого подтверждения.
Если пакет, удовлетворяющий тестам для предсказания заголовка, содержит следующие ожидавшиеся данные, если для соединения в очередь не помещены внеполос-ные данные и если в приемном буфере сокета есть место для входящих данных, этот пакет является чистым порядковым сегментом данных. Переменные последовательности обновляются, из пакета удаляются заголовки пакета, а оставшиеся данные добавляются в приемный буфер сокета. Уровень сокетов уведомляется так, что он может уведомить любой заинтересованный поток, а управляющий блок помечается флагом, указывающим, что необходимо подтверждение. Для чистого пакета данных дополнительной обработки не требуется.
Для пакетов, которые не обрабатываются алгоритмом предсказания заголовка, шаги обработки следующие.
- Обработать опции отметки времени, если она присутствует, с отвержением всех пакетов, для которых отметка времени уменьшилась, отправив сначала текущее подтверждение.
- Проверить, начинается ли пакет до rcvnxt. Если да, игнорировать в пакете любые S YN и обрезать любые данные, оказавшиеся перед rcvnxt. Если больше данных не остается, отправить текущее подтверждение и уничтожить пакет. (Предполагается, что пакет является переданным дубликатом.)
- Если пакет после обрезания по-прежнему содержит данные, а процесс, создавший сокет, уже закрыл его, отправить сброс (RST) и удалить соединение. Этот сброс необходим для разрыва соединений, которые не могут завершиться; обычно он отправляется, когда во время приема данных отключается клиент удаленной регистрации.
- Если конец сегмента выходит за пределы окна, обрезать любые выходящие за пределы окна данные. Если окно было закрыто, а номером последовательности пакета является rcvjixt, пакет интерпретируется как обновление окна и обрабатывается оставшаяся часть пакета. Если установлен SYN, а соединение было в состоянии TIME_WAIT, этот пакет является в действительности запросом нового соединения и старое соединение удаляется; эта процедура называется быстрым повторным использованием соединения. В противном случае, если не остается данных, необходимо отправить подтверждение и удалить пакет.Оставшиеся шаги обработки ввода TCP проверяют следующие флаги и поля и предпринимают соответствующие действия: RST, АСК, окно, URG, данные и FIN. Поскольку приемлемость пакета была уже подтверждена, эти действия просты.
- Если присутствует опция отметки времени и пакет включает ожидавшийся следующий номер последовательности, записать полученное значение для включения в следующее подтверждение.
- Если установлен RST, закрыть соединение и удалить пакет.
- Если АСК не установлен, удалить пакет.
- Если значение поля подтверждения больше, чем значение предыдущих подтверждений, то были подтверждены новые данные. Если соединение было в состоянии SYN_RECEIVED и пакет подтверждает SYN, отправленное для этого соединения, войти в состояние ESTABLISHED. Если пакет включает опцию отметки времени, использовать ее для вычисления пробы времени обращения; в противном случае, если диапазон вновь подтвержденных последовательностей включает номер последовательности, для которого измеряется время обращения, этот пакет предоставляет пробу. Усреднить время в сглаженную оценку времени обращения для соединения. Если были подтверждены все ожидающие данные, остановить таймер повторной передачи; в противном случае установить для него значение текущего тайм-аута. Наконец, удалить из очереди отправки в сокете подтвержденные данные. Если был отправлен и подтвержден FIN, изменить состояние конечного автомата.
- Проверить по полю окна, перемещается ли известное окно отправки. Сначала проверить, содержит ли данный пакет новое обновление окна. Если номер последовательности пакета больше, чем у предьщущего обновления окна, или если номер последовательности тот же самый, но поле подтверждения больше, или если как последовательность, так и подтверждение те же самые, но окно больше, записать новое окно.
- Если установлен флаг срочных данных URG, сравнить указатель срочных данных в пакете с последним полученным указателем на срочные данные. Если он отличается, были отправлены новые срочные данные. Использовать указатель на срочные данные для вычисления so_oobmark, смещения от начала приемного буфера сокета до отметки срочных данных, и уведомить сокет с помощью sohasoutojband(). Если указатель на срочные данные меньше, чем размер пакета, все срочные данные были получены. TCP обычно удаляет последний октет данных, посланный в срочном режиме (последний октет перед указателем на срочные данные), и помещает этот октет в управляющий блок протокола до тех пор, пока он не будет запрошен с использованием PRURCVOOB. (Окончание срочных данных является предметом разногласий; интерпретация BSD следует оригинальной спецификации TCP.) Опция сокета SO_OOBINLINE может запросить, чтобы срочные данные были оставлены в очереди с обычными данными, хотя отметка в потоке данных по-прежнему сохраняется.
- Наконец, исследовать поле данных в полученном пакете. Если данные начинаются с rcvnxt, они могут быть помещены непосредственно в приемный буфер сокета с помощью sbappendstream(). В управляющем блоке протокола устанавливается флаг TFDELACK, чтобы указать на необходимость подтверждения, но последнее не отправляется немедленно в надежде, что его можно будет объединить с отправляемыми вскоре пакетами (преимущественно в ответ на входящие данные) или объединить с подтверждением других полученных вскоре данных; см. раздел об отложенных подтверждениях и обновлениях окон. Если никакая деятельность не вызывает возвращение пакета до следующего запуска процедуры tcpdelack(), она изменит флаг на TFACKNOW и вызовет процедуру tcp_output() для отправки подтверждения. Подтверждения могут таким образом задерживаться не более чем на 200 миллисекунд. Если данные не начинаются с rcvnxt, пакет сохраняется в очереди соединения до поступления промежуточных данных и подт-верждение отправляется немедленно.
- В качестве последнего шага в обработке полученного пакета проверить флаг FIN. Если он присутствует, возможно, придется изменить конечный автомат соединения, и сокет помечается с помощью socantrcvmore() для передачи указания конца файла. Если сторона отправки уже закрылась (был отправлен и подтвержден FIN), сокет считается теперь закрытым, и он помечен таким образом soisdisconnected(). Для форсирования немедленного подтверждения устанавливается флаг TF_ACKNOW.
- Шаг 10 завершает действия, предпринимаемые при получении tcp_input() нового пакета. Однако, как отмечено ранее в данном разделе, прием ввода может потребовать нового вывода. В частности, подтверждение всех ожидающих данных или нового обновления окна требует либо нового вывода, либо изменения состояния модулем вывода. Несколько особых случаев устанавливают также флаг TF_ACKNOW. В этих случаях в заключение обработки ввода вызывается tcp_output().
