Другой важный момент, отмеченный в [52], заключается в том, что по истечении времени таймера повторной передачи для следующего RTO должно использоваться
Алгоритмы Джекобсона (Jacobson) реализуют вычисление RTO при измерении RTT и увеличение RTO при повторной передаче. Однако, когда клиент выполняет повторную передачу и получает ответ, возникает
■ запрос потерян;
■ ответ потерян;
■ значение RTO слишком мало.
Рис. 22.2. Три сценария, возможные при истечении времени таймера повторной передачи
Когда клиент получает ответ на запрос, отправленный повторно, он не может сказать, какому из запросов соответствует ответ. На рисунке, изображенном справа, ответ соответствует начальному запросу, в то время как на двух других рисунках ответ соответствуют второму запросу.
Алгоритм Карна (Karn) [58] обрабатывает этот сценарий в соответствии со следующими правилами, применяемыми в любом случае, когда ответ получен на запрос, отправленный более одного раза:
■ Если для запроса и ответа было измерено значение RTT, не следует использовать его для обновления оценочных значений, так как мы не знаем, какому запросу соответствует ответ.
■ Поскольку ответ пришел до того, как истекло время нашего таймера повторной передачи, используйте для следующего пакета текущее значение RTO. Только когда мы получим ответ на запрос, который не был передан повторно, мы изменяем значение RTT и снова вычисляем RTO.
При написании наших функций RTT применить алгоритм Карна несложно, но оказывается, что существует и более изящное решение. Оно используется в расширениях TCP для сетей с высокой пропускной способностью, то есть сетей, обладающих либо широкой полосой пропускания, либо большим значением RTT, либо обоими этими свойствами (RFC 1323 [53]). Кроме добавления порядкового номера к началу каждого запроса, который сервер должен отразить, мы добавляем
Пример
Свяжем теперь всю эту информацию воедино в примере. Мы начнем с функции main
нашего клиента UDP, представленного в листинге 8.3, и изменим в ней только номер порта с SERV_PORT
на 7 (стандартный эхо-сервер, см. табл. 2.1).
В листинге 22.4 показана функция dg_cli
. Единственное изменение по сравнению с листингом 8.4 состоит в замене вызовов функций sendto
и recvfrom
вызовом нашей новой функции dg_send_recv
.
Перед тем как представить функцию dg_send_recv
и наши функции RTT, которые она вызывает, мы показываем в листинге 22.5 нашу схему реализации функциональных свойств, повышающих надежность клиента UDP. Все функции, имена которых начинаются с rtt_
, описаны далее.
Листинг 22.4. Функция dg_cli, вызывающая нашу функцию dg_send_recv
//rtt/dg_cli.c
1 #include "unp.h"
2 ssize_t Dg_send_recv(int, const void*, size_t, void*, size_t,
3 const SA*, socklen_t);
4 void
5 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
6 {
7 ssize_t n;
8 char sendline[MAXLINE], recvline[MAXLINE + 1];
9 while (Fgets(sendline, MAXLINE, fp) != NULL) {
10 n = Dg_send_recv(sockfd, sendline, strlen(sendline),
11 recvline, MAXLINE, pservaddr, servlen);
12 recvline[n] = 0; /* завершающий нуль */
13 Fputs(recvline, stdout);
14 }
15 }
Листинг 22.5. Схема функций RTT и последовательность их вызова
static sigjmp_buf jmpbuf;
{