UNIX: разработка сетевых приложений - Уильям Стивенс
Шрифт:
Интервал:
Закладка:
40 iovrecv[l].iov_base = inbuff;
41 iovrecv[l].iov_len = inbytes;
1-5 Мы включаем новый заголовочный файл unprtt.h, показанный в листинге 22.8, который определяет структуру rtt_info, содержащую информацию RTT для клиента. Мы определяем одну из этих структур и ряд других переменных.
Определение структур msghdr и структуры hdr6-10 Мы хотим скрыть от вызывающего процесса добавление порядкового номера и отметки времени в начало каждого пакета. Проще всего использовать для этого функцию writev, записав свой заголовок (структура hdr), за которым следуют данные вызывающего процесса, в виде одной дейтаграммы UDP. Вспомните, что результатом выполнения функции writev на дейтаграммном сокете является отправка одной дейтаграммы. Это проще, чем заставлять вызывающий процесс выделять для нас место в начале буфера, а также быстрее, чем копировать наш заголовок и данные вызывающего процесса в один буфер (под который мы должны выделить память) для каждой функции sendto. Но поскольку мы работаем с UDP и нам необходимо задать адрес получателя, следует использовать возможности, предоставляемые структурой iovec функций sendmsg и recvmsg и отсутствующие в функциях sendto и recvfrom. Вспомните из раздела 14.5, что в некоторых системах доступна более новая структура msghdr, включающая вспомогательные данные (msg_control), тогда как в более старых системах вместо них применяются элементы msg_accright (так называемые права доступа — access rights), расположенные в конце структуры. Чтобы избежать усложнения кода директивами #ifdef для обработки этих различий, мы объявляем две структуры msghdr как static. При этом они инициализируются только нулевыми битами, а затем неиспользованные элементы в конце структур просто игнорируются.
Инициализация при первом вызове20-24 При первом вызове нашей функции мы вызываем функцию rtt_init.
Заполнение структур msghdr25-41 Мы заполняем две структуры msghdr, используемые для ввода и вывода. Для данного пакета мы увеличиваем на единицу порядковый номер отправки, но не устанавливаем отметку времени отправки, пока пакет не будет отправлен (поскольку он может отправляться повторно, а для каждой повторной передачи требуется текущая отметка времени).
Вторая часть функции вместе с обработчиком сигнала sig_alarm показана в листинге 22.7.
Листинг 22.7. Функция dg_send_recv: вторая половина
//rtt/dg_send_rеcv.c
42 Signal(SIGALRM, sig_alrm);
43 rtt_newpack(&rttinfo); /* инициализируем для этого пакета */
44 sendagain:
45 sendhdr.ts = rtt_ts(&rttinfo);
46 Sendmsg(fd, &msgsend, 0);
47 alarm(rtt_start(&rttinfo)); /* вычисляем тайм-аут. запускаем таймер */
48 if (sigsetjmp(jmpbuf, 1) != 0) {
49 if (rtt_timeout(&rttinfо) < 0) {
50 err_msg("dg_send_recv: no response from server, giving up");
51 rttinit = 0; /* повторная инициализация для следующего вызова */
52 errno = ETIMEDOUT;
53 return (-1);
54 }
55 goto sendagain;
56 }
57 do {
58 n = Recvmsg(fd, &msgrecv, 0);
59 } while (n < sizeof(struct hdr) || recvhdr.seq != sendhdr.seq);
60 alarm(0); /* останавливаем таймер SIGALRM */
61 /* вычисляем и записываем новое значение оценки RTT */
62 rtt_stop(&rttinfo, rtt_ts(&rttinfo) — recvhdr.ts);
63 return (n - sizeof(struct hdr)); /* возвращаем размер полученной
дейтаграммы */
64 }
65 static void
66 sig_alrm(int signo)
67 {
68 siglongjmp(jmpbuf, 1);
69 }
Установка обработчика сигналов42-43 Для сигнала SIGALRM устанавливается обработчик сигналов, а функция rtt_newpack устанавливает счетчик повторных передач в нуль.
Отправка дейтаграммы45-47 Функция rtt_ts получает текущую отметку времени. Отметка времени хранится в структуре hdr, которая добавляется к данным пользователя. Одиночная дейтаграмма UDP отправляется функцией sendmsg. Функция rtt_start возвращает количество секунд для этого тайм-аута, а сигнал SIGALRM контролируется функцией alarm.
Установка буфера перехода48 Мы устанавливаем буфер перехода для нашего обработчика сигналов с помощью функции sigsetjmp. Мы ждем прихода следующей дейтаграммы, вызывая функцию recvmsg. (Совместное использование функций sigsetjmp и siglongjmp вместе с сигналом SIGALRM мы обсуждали применительно к листингу 20.5.) Если время таймера истекает, функция sigsetjmp возвращает 1.
Обработка тайм-аута49-55 Когда возникает тайм-аут, функция rtt_timeout вычисляет следующее значение RTO (используя экспоненциальное смещение) и возвращает -1, если нужно прекратить попытки передачи дейтаграммы, или 0, если нужно выполнить очередную повторную передачу. Когда мы прекращаем попытки, мы присваиваем переменной errno значение ETIMEDOUT и возвращаемся в вызывающую функцию.
Вызов функции recvmsg, сравнение порядковых номеров57-59 Мы ждем прихода дейтаграммы, вызывая функцию recvmsg. Длина полученной дейтаграммы не должна быть меньше размера структуры hdr, а ее порядковый номер должен совпадать с порядковым номером запроса, ответом на который предположительно является эта дейтаграмма. Если при сравнении хотя бы одно из этих условий не выполняется, функция recvmsg вызывается снова.
Выключение таймера и обновление показателей RTT60-62 Когда приходит ожидаемый ответ, функция alarm отключается, а функция rtt_stop обновляет оценочное значение RTT. Функция rtt_ts возвращает текущую отметку времени, и отметка времени из полученной дейтаграммы вычитается из текущей отметки, что дает в результате RTT.
Обработчик сигнала SIGALRM65-69 Вызывается функция siglongjmp, результатом выполнения которой является то, что функция sigsetjmp в dg_send_recv возвращает 1.
Теперь мы рассмотрим различные функции RTT, которые вызывались нашей функцией dg_send_recv. В листинге 22.8 показан заголовочный файл unprtt.h.
Листинг 22.8. Заголовочный файл unprtt.h
//lib/unprtt.h
1 #ifndef __unp_rtt_h
2 #define __unp_rtt_h
3 #include "unp.h"
4 struct rtt_info {
5 float rtt_rtt; /* последнее измеренное значение RTT в секундах */
6 float rtt_srtt; /* сглаженная оценка RTT в секундах */
7 float rtt_rttvar; /* сглаженные средние значения отклонений
в секундах */
8 float rtt_rto; /* текущее используемое значение RTO, в секундах */
9 int rtt_nrexmt; /* количество повторных передач: 0, 1, 2, ... */
10 uint32_t rtt_base; /* число секунд, прошедшее после 1.1.1970 в начале */
11 };
12 #define RTT_RXTMIN 2 /* минимальное значение тайм-аута для
повторной передачи, в секундах */
13 #define RTT_RXTMAX 60 /* максимальное значение тайм-аута для
повторной передачи, в секундах */
14 #define RTT_MAXNREXMT 3 /* максимально допустимое количество
повторных передач одной дейтаграммы */
15 /* прототипы функций */
16 void rtt_debug(struct rtt_info*);
17 void rtt_init(struct rtt_info*);
18 void rtt_newpack(struct rtt_info*);
19 int rtt_start(struct rtt_info*);
20 void rtt_stop(struct rtt_info*, uint32_t);
21 int rtt_timeout(struct rtt_info*);
22 uint32_t rtt_ts(struct rtt_info*);
23 extern int rtt_d_flag; /* может быть ненулевым при наличии
дополнительной информации */
24 #endif /* _unp_rtt_h */
Структура rtt_info4-11 Эта структура содержит переменные, необходимые для того, чтобы определить время передачи пакетов между клиентом и сервером. Первые четыре переменных взяты из уравнений, приведенных в начале этого раздела.
12-14 Эти константы определяют минимальный и максимальный тайм-ауты повторной передачи и максимальное число возможных повторных передач.
В листинге 22.9 показан макрос RTT_RTOCALC и первые две из четырех функций RTT.
Листинг 22.9. Макрос RTT_RTOCALC, функции rtt_minmax и rtt_init
//lib/rtt.c
1 #include "unprtt.h"
2 int rtt_d_flag = 0; /* отладочный флаг; может быть установлен в
ненулевое значение вызывающим процессом */