UNIX: разработка сетевых приложений
44 rtt_newpack(struct rtt_info *ptr)45 {46 ptr->rtt_nrexmt = 0;47 }48 int49 rtt_start(struct rtt_info *ptr)50 {51 return ((int)(ptr->rtt_rto + 0.5)); /* округляем float до int */52 /* возвращенное значение может быть использовано как аргументalarm(rtt_start(&fоо)) */53 }Функция34-42возвращает текущую отметку времени для вызывающего процесса, которая должна содержаться в отправляемой дейтаграмме в виде 32-разрядного целого числа без знака. Мы получаем текущее время и дату из функцииrtt_tsи затем вычитаем число секунд в момент вызова функцииgettimeofday(значение, хранящееся в элементеrtt_initструктурыrtt_base). Мы преобразуем это значение в миллисекунды, а также преобразуем в миллисекунды значение, возвращаемое функциейrtt_infoв микросекундах. Тогда отметка времени является суммой этих двух значений в миллисекундах.gettimeofdayРазница во времени между двумя вызовами функции
представляется количеством миллисекунд между этими двумя вызовами. Но мы храним отметки времени в 32-разрядном целом числе без знака, а не в структуреrtt_ts.timevalФункция43-47просто обнуляет счетчик повторных передач. Эта функция должна вызываться всегда, когда новый пакет отправляется в первый раз.rtt_newpackФункция48-53возвращает текущее значение RTO в миллисекундах. Возвращаемое значение затем может использоваться в качестве аргумента функцииrtt_start.alarmФункция
, показанная в листинге 22.11, вызывается после получения ответа для обновления оценочного значения RTT и вычисления нового значения RTO.rtt_stopЛистинг 22.11. Функция rtt_stop: обновление показателей RTT и вычисление нового
//lib/rtt.c62 void63 rtt_stop(struct rtt_info *ptr, uint32_t ms)64 {65 double delta;66 ptr->rtt_rtt = ms / 1000.0; /* измеренное значение RTT в секундах */67 /*68 * Обновляем оценочные значения RTT среднего отклонения RTT.69 * (См. статью Джекобсона (Jacobson). SIGCOMM'88. Приложение А.)70 * Здесь мы для простоты используем числа с плавающей точкой.71 */72 delta = ptr->rtt_rtt - ptr->rtt_srtt;73 ptr->rtt_srtt += delta / 8; /* g - 1/8 */74 if (delta < 0.0)75 delta = -delta; /* |delta| */76 ptr->rtt_rttvar += (delta - ptr->rtt_rttvar) / 4; /* h - 1/4 */77 ptr->rtt_rto = rtt_minmax(RTT_RTOCALC(ptr));78 }Вторым аргументом является измеренное RTT, полученное вызывающим процессом при вычитании полученной в ответе отметки времени из текущей (функция62-78). Затем применяются уравнения, приведенные в начале этого раздела, и записываются новые значения переменныхrtt_ts,rtt_srttиrtt_rttvar.rtt_rtoПоследняя функция,
показана в листинге 22.12. Эта функция вызывается, когда истекает время таймера повторных передач.rtt_timeoutЛистинг 22.12. Функция rtt_timeout: применение экспоненциального смещения
//lib/rtt.c83 int84 rtt_timeout(struct rtt_info *ptr)85 {86 ptr->rtt_rto *= 2; /* следующее значение RTO */87 if (++ptr->rtt_nrexmt > RTT_MAXNREXMT)88 return (-1); /* закончилось время, отпущенное на попытки отправитьэтот пакет */89 return (0);90 }Текущее значение RTO удваивается — в этом и заключается экспоненциальное смещение.86Если мы достигли максимально возможного количества повторных передач, возвращается значение -1, указывающее вызывающему процессу, что дальнейшие попытки передачи должны прекратиться. В противном случае возвращается 0.87-89В нашем примере клиент соединялся дважды с двумя различными эхо-серверами в Интернете утром рабочего дня. Каждому серверу было отправлено по 500 строк. По пути к первому серверу было потеряно 8 пакетов, по пути ко второму — 16. Один из потерянных шестнадцати пакетов, предназначенных второму серверу, был потерян дважды, то есть пакет пришлось дважды передавать повторно, прежде чем был получен ответ. Все остальные потерянные пакеты пришлось передать повторно только один раз. Мы могли убедиться, что эти пакеты были действительно потеряны, посмотрев на выведенные порядковые номера каждого из полученных пакетов. Если пакет лишь опоздал, но не был потерян, после повторной передачи клиент получает два ответа: соответствующий запоздавшему первому пакету и повторно переданному. Обратите внимание, что у нас нет возможности определить, что именно было потеряно (и привело к необходимости повторной передачи клиентского запроса) — сам клиентский запрос или же ответ сервера, высланный после получения такого запроса.
ПРИМЕЧАНИЕДля первого издания этой книги автор написал для проверки этого клиента сервер UDP, который случайным образом игнорировал пакеты. Теперь он не используется. Нужно только соединить клиент с сервером через Интернет, и тогда нам почти гарантирована потеря некоторых пакетов!
22.6. Связывание с адресами интерфейсов
Одно из типичных применений функции
связано с приложениями UDP, которым нужно выполнять мониторинг всех интерфейсов на узле, чтобы знать, когда и на какой интерфейс приходит дейтаграмма. Это позволяет получающей программе узнавать адрес получателя дейтаграммы UDP, так как именно по этому адресу определяется сокет, на который доставляется дейтаграмма, даже если узел не поддерживает параметр сокетаget_ifi_info.IP_RECVDSTADDR