UNIX: разработка сетевых приложений
UNIX: разработка сетевых приложений читать книгу онлайн
Новое издание книги, посвященной созданию веб-серверов, клиент-серверных приложений или любого другого сетевого программного обеспечения в операционной системе UNIX, — классическое руководство по сетевым программным интерфейсам, в частности сокетам. Оно основано на трудах Уильяма Стивенса и полностью переработано и обновлено двумя ведущими экспертами по сетевому программированию. В книгу включено описание ключевых современных стандартов, реализаций и методов, она содержит большое количество иллюстрирующих примеров и может использоваться как учебник по программированию в сетях, так и в качестве справочника для опытных программистов.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
Мы хотим вычислить тайм-аут повторной передачи (RTO), чтобы использовать его при отправке каждого пакета. Для того чтобы выполнить это вычисление, мы измеряем RTT — действительное время обращения для пакета. Каждый раз, измеряя RTT, мы обновляем два статистических показателя:
srtt
rttvar
srtt
rttvar
delta = measuredRTT - srtt
srtt ← srtt + g × delta
rttvar ← rttvar + h (|delta| - rttvar)
RTO = srtt + 4 × rttvar
delta
srtt
g
h
Два приращения и множитель 4 в вычислении RTO специально выражены степенями числа 2 и могут быть вычислены с использованием операций сдвига вместо деления и умножения. На самом деле реализация TCP в ядре (см. раздел 25.7 [128]) для ускорения вычислений обычно использует арифметику с фиксированной точкой, но мы для простоты используем в нашем коде вычисления с плавающей точкой.
Другой важный момент, отмеченный в [52], заключается в том, что по истечении времени таймера повторной передачи для следующего RTO должно использоваться экспоненциальное смещение (exponential backoff). Например, если наше первое значение RTO равно 2 с и за это время ответа не получено, следующее значение RTO будет равно 4 с. Если ответ все еще не последовал, следующее значение RTO будет 8 с, затем 16 и т.д.
Алгоритмы Джекобсона (Jacobson) реализуют вычисление RTO при измерении RTT и увеличение RTO при повторной передаче. Однако, когда клиент выполняет повторную передачу и получает ответ, возникает проблема неопределенности повторной передачи (retransmission ambiguity problem). На рис. 22.2 показаны три возможных сценария, при которых истекает время ожидания повторной передачи:
■ запрос потерян;
■ ответ потерян;
■ значение RTO слишком мало.
Рис. 22.2. Три сценария, возможные при истечении времени таймера повторной передачи
Когда клиент получает ответ на запрос, отправленный повторно, он не может сказать, какому из запросов соответствует ответ. На рисунке, изображенном справа, ответ соответствует начальному запросу, в то время как на двух других рисунках ответ соответствуют второму запросу.
Алгоритм Карна (Karn) [58] обрабатывает этот сценарий в соответствии со следующими правилами, применяемыми в любом случае, когда ответ получен на запрос, отправленный более одного раза:
■ Если для запроса и ответа было измерено значение RTT, не следует использовать его для обновления оценочных значений, так как мы не знаем, какому запросу соответствует ответ.
■ Поскольку ответ пришел до того, как истекло время нашего таймера повторной передачи, используйте для следующего пакета текущее значение RTO. Только когда мы получим ответ на запрос, который не был передан повторно, мы изменяем значение RTT и снова вычисляем RTO.
При написании наших функций RTT применить алгоритм Карна несложно, но оказывается, что существует и более изящное решение. Оно используется в расширениях TCP для сетей с высокой пропускной способностью, то есть сетей, обладающих либо широкой полосой пропускания, либо большим значением RTT, либо обоими этими свойствами (RFC 1323 [53]). Кроме добавления порядкового номера к началу каждого запроса, который сервер должен отразить, мы добавляем отметку времени, которую сервер также должен отразить. Каждый раз, отправляя запрос, мы сохраняем в этой отметке значение текущего времени. Когда приходит ответ, мы вычисляем величину RTT для этого пакета как текущее время минус значение отметки времени, отраженной сервером в своем ответе. Поскольку каждый запрос несет отметку времени, отражаемую сервером, мы можем вычислить RTT для каждого ответа, который мы получаем. Теперь нет никакой неопределенности. Более того, поскольку сервер только отражает отметку времени клиента, клиент может использовать для отметок времени любые удобные единицы, и при этом не требуется, чтобы клиент и сервер синхронизировали часы.
Пример
Свяжем теперь всю эту информацию воедино в примере. Мы начнем с функции
main
SERV_PORT
В листинге 22.4 показана функция
dg_cli
sendto
recvfrom
dg_send_recv
Перед тем как представить функцию
dg_send_recv
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;
{