UNIX: разработка сетевых приложений — страница 8 из 42

Параметры сокетов

7.1. Введение

Существуют различные способы получения и установки параметров сокетов:

■ функции

getsockopt
и
setsockopt
;

■ функция

fcntl
;

■ функция

ioctl
.

Эту главу мы начнем с описания функций

getsockopt
и
setsockopt
. Далее мы приведем пример, в котором выводятся заданные по умолчанию значения параметров, а затем дадим подробное описание всех параметров сокетов. Мы разделили описание параметров на следующие категории: общие, IPv4, IPv6, TCP и SCTP. При первом прочтении главы можно пропустить подробное описание параметров и при необходимости прочесть отдельные разделы, на которые даны ссылки. Отдельные параметры подробно описываются в дальнейших главах, например параметры многоадресной передачи IPv4 и IPv6 мы обсуждаем в разделе 19.5.

Мы также рассмотрим функцию

fcntl
, поскольку она реализует предусмотренные стандартом POSIX возможности отключить для сокета блокировку ввода-вывода, включить управление сигналами, а также установить владельца сокета. Функцию
ioctl
мы опишем в главе 17.

7.2. Функции getsockopt и setsockopt

Эти две функции применяются только к сокетам.

#include 


int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

Обе функции возвращают 0 в случае успешного завершения, -1 в случае ошибки

Переменная

sockfd
должна ссылаться на открытый дескриптор сокета. Переменная
level
определяет, каким кодом должен интерпретироваться параметр: общими программами обработки сокетов или зависящими от протокола программами (например, IPv4, IPv6, TCP или SCTP).

optval
— это указатель на переменную, из которой извлекается новое значение параметра с помощью функции
setsockopt
или в которой сохраняется текущее значение параметра с помощью функции
getsockopt
. Размер этой переменной задается последним аргументом. Для функции
setsockopt
тип этого аргумента — значение, а для функции
getsockopt
— «значение-результат».

В табл. 7.1 и 7.2 сведены параметры, которые могут запрашиваться функцией

getsockopt
или устанавливаться функцией
setsockopt
. В колонке «Тип данных» приводится тип данных того, на что указывает указатель
optval
для каждого параметра. Две фигурные скобки мы используем, чтобы обозначить структуру, например
linger{}
обозначает
struct linger
.


Таблица 7.1. Параметры сокетов для функций getsockopt и setsockopt

leveloptnamegetsetОписаниеФлагТип данных
SOL_SOCKETSO_BROADCASTПозволяет посылать широковещательные дейтаграммыint
SO_DEBUGРазрешает отладкуint
SO_DONTROUTEОбходит таблицу маршрутизацииint
SO_ERRORПолучает ошибку, ожидающую обработки, и возвращает значение параметра в исходное состояниеint
SO_KEEPALIVEПериодически проверяет, находится ли соединение в рабочем состоянииint
SO_LINGERЗадерживает закрытие сокета, если имеются данные для отправкиlinger{}
SO_OOBINLINEОставляет полученные внеполосные данные вместе с обычными данными (inline)int
SO_RCVBUFРазмер приемного буфераint
SO_SNDBUFРазмер буфера отправкиint
SO_RCVLOWATМинимальное количество данных для приемного буфера сокетаint
SO_SNDLOWATМинимальное количество данных для буфера отправки сокетаint
SO_RCVTIMEOТайм-аут при полученииtimeval{}
SO_SNDTIMEOТайм-аут при отправкеtimeval{}
SO_REUSEADDRДопускает повторное использование локального адресаint
SO_REUSEPORTДопускает повторное использование локального адресаint
SO_TYPEВозвращает тип сокетаint
SO_USELOOPBACKМаршрутизирующий сокет получает копию того, что он отправляетint
IPPROTO_IPIP_HDRINCLВключается IP- заголовокint
IP_OPTIONSВ заголовке IPv4 устанавливаются параметры IPсм. текст
IP_RECVDSTADDRВозвращает IP-адрес получателяint
IP_RECVIFВозвращает индекс интерфейса, на котором принимается дейтаграмма UDPint
IP_TOSТип сервиса и приоритетint
IP_TTLВремя жизниint
IP_MULTICAST_IFЗадает интерфейс для исходящих дейтаграммin_addr{}
IP_MULTICAST_TTLЗадает TTL для исходящих дейтаграммu_char
IP_MULTICAST_LOOPРазрешает или отменяет отправку копии дейтаграммы на тот узел, откуда она была послана (loopback)u_char
IP_ADD_MEMBERSHIPВключение в группу многоадресной передачиip_mreq{}
IP_DROP_MEMBERSHIPОтключение от группы многоадресной передачиip_mreq{}
IP_{BLOCK, UNBLOCK}_SOURCEБлокирование и разблокирование источника многоадресной передачиip_mreq_source{}
IP_{ADD, DROP}_SOURCE_MEMBERSHIPПрисоединение или отключение от многоадресной передачи от источника (source-specific)ip_mreq_source{}
IPPROTO_ICMPV6ICMP6_FILTERУказывает тип сообщения ICMPv6, которое передается процессуicmp6_filter{}
IPPROTO_IPV6IPV6_ADDRFORMМеняет формат адреса сокетаint
IPV6_CHECKSUMОтступ поля контрольной суммы для символьных (неструктурированных) сокетовint
IPV6_DONTFRAGНе фрагментировать, а сбрасывать большие пакетыint
IPV6_NEXTHOPЗадает следующий транзитный адресsockaddr{}
IPV6_PATHMTUПолучение текущей маршрутной МТУip6_mtuinfo{}
IPV6_RECVDSTOPTSПолучение параметров адресатаint
IPV6_RECVHOPLIMITПолучение ограничения на количество транзитных узлов при направленной передачеint
IPV6_RECVHOPOPTSПолучение параметров прыжковint
IPV6_RECVPATHMTUПолучение маршрутной MTUint
IPV6_RECVPKTINFOПолучение информации о пакетахint
IPV6_RECVRTHDRПолучение маршрута от источникаint
IPV6_RECVTCLASSПолучение класса трафикаint
IPV6_UNICAST_HOPSПредел количества транзитных узлов, задаваемый по умолчаниюint
IPV6_USE_MIN_MTUИспользовать минимальную MTUint
IPV6_V60NLYОтключить совместимость с IPv4int
IPV6_XXXВспомогательные данныесм. текст
IPV6_MULTICAST_IFЗадает интерфейс для исходящих дейтаграммu_int
IPV6_MULTICAST_HOPSЗадает предельное количество транзитных узлов для исходящих широковещательных сообщенийint
IPV6_MULTICAST_LOOPРазрешает или отменяет отправку копии дейтаграммы на тот узел, откуда она была послана (loopback)u_int
IPV6_LEAVE_GROUPВыход из группы многоадресной передачиipv6_mreq{}
IPPROTO_IP или IPPROTO_IPV6MCAST_JOIN_GROUPПрисоединение к группе многоадресной передачиgroup_req{}
MCAST_LEAVE_GROUPВыход из группы многоадресной передачиgroup_source_req{}
MCAST_BLOCK_SOURCEБлокирование источника многоадресной передачиgroup_source_req{}
MCAST_UNBLOCK_SOURCEРазблокирование источника многоадресной передачиgroup_source_req{}
MCAST_JOIN_SOURCE_GROUPПрисоединение к группе многоадресной передачи от источникаgroup_source_req{}
MCAST_LEAVE_SOURCE_GROUPВыход из группы многоадресной передачи от источникаgroup_source_req{}

Таблица 7.2. Параметры сокетов транспортного уровня

LeveloptnamegetsetОписаниеФлагТип данных
IPPROTO_TCPTCP_MAXSEGМаксимальный размер сегмента TCPint
TCP_NODELAYОтключает алгоритм Наглаint
IPPROTO_SCTPSCTP_ADAPTION_LAYERУказание на уровень адаптацииsctp_setadaption
SCTP_ASSOCINFO+Получение и задание сведений об ассоциацииsctp_assocparamms{}
SCTP_AUTOCLOSEАвтоматическое закрытиеint
SCTP_DEFAULT_SEND_PARAMПараметры отправки но умолчаниюsctp_sndrcvinfo{}
SCTP_DISABLE_FRAGMENTSФрагментация SCTPint
SCTP_EVENTSУведомление об интересующих событияхsctp_event_subscribe{}
SCTP_GET_PEER_ADDR_INFO+Получение состояния адреса собеседникаsctp_paddrinfo{}
SCTP_I_WANT_MAPPED_V4_ADDRОтображение адресов IPv4int
SCTP_INITMSGПараметры пакета INIT по умолчаниюsctp_initmsg{}
SCTP_MAXBURSTМаксимальный размер набора пакетовint
SCTP_MAXSEGМаксимальный размер фрагментацииint
SCTP_NODELAYОтключение алгоритма Наглаint
SCTP_PEER_ADDR_PARAMS+Параметры адреса собеседникаsctp_paddrparams{)
SCTP_PRIMARY_ADDR+Основной адрес назначенияsctp_setprim{}
SCTP_RTOINFO+Информация RTOsctp_rtoinfo{}
SCTP_SET_PEER_PRIMARY_ADDRОсновной адрес назначения собеседникаsctp_setpeerprim{}
SCTP_STATUS+Получение сведений о статусе ассоциацииsctp_status{}

Существует два основных типа параметров: двоичные параметры, включающие или отключающие определенное свойство (флаги), и параметры, получающие и возвращающие значения параметров, которые мы можем либо задавать, либо проверять. В колонке «Флаг» указывается, относится ли параметр к флагам. Для флагов при вызове функции

getsockopt
аргумент
*optval
является целым числом. Возвращаемое значение
*optval
нулевое, если параметр отключен, и ненулевое, если параметр включен. Аналогично, функция
setsockopt
требует ненулевого значения
*optval
для включения параметра, и нулевого значения — для его выключения. Если в колонке «Флаг» не содержится символа «•», то параметр используется для передачи значения заданного типа между пользовательским процессом и системой.

В последующих разделах этой главы приводятся дополнительные подробности о параметрах сокетов.

7.3. Проверка наличия параметра и получение значения по умолчанию

Напишем программу, которая проверяет, поддерживается ли большинство параметров, представленных в табл. 7.1 и 7.2, и если да, то выводит их значения, заданные по умолчанию. В листинге 7.1[1] содержатся объявления нашей программы.

Листинг 7.1. Объявления для нашей программы, проверяющей параметры сокетов

//sockopt/checkopts.с

 1 #include "unp.h"

 2 #include  /* определения констант TCP_xxx */


 3 union val {

 4  int i_val;

 5  long l_val;

 6  struct linger linger_val;

 7  struct timeval timeval_val;

 8 } val;


 9 static char *sock_str_flag(union val*, int);

10 static char *sock_str_int(union val*, int);

11 static char *sock_str_linger(union val*, int);

12 static char *sock_str_timeval(union val*, int);


13 struct sock_opts {

14  const char *opt_str;

15  int opt_level;

16  int opt_name;

17  char *(*opt_val_str)(union val*, int);

18 } sock_opts[] = {

19  { "SO_BROADCAST",      SOL_SOCKET,   SO_BROADCAST,   sock_str_flag },

20  { "SO_DEBUG",          SOL_SOCKET,   SO_DEBUG,       sock_str_flag },

21  { "SO_DONTROUTE",      SOL_SOCKET,   SO_DONTROUTE,   sock_str_flag },

22  { "SO_ERROR",          SOL_SOCKET,   SO_ERROR,       sock_str_int },

23  { "SO_KEEPALIVE",      SOL_SOCKET,   SO_KEEPALIVE,   sock_str_flag },

24  { "SO_LINGER",         SOL_SOCKET,   SO_LINGER,      sock_str_linger },

25  { "SO_OOBINLINE",      SOL_SOCKET,   SO_OOBINLINE,   sock_str_flag },

26  { "SO_RCVBUF",         SOL_SOCKET,   SO_RCVBUF,      sock_str_int },

27  { "SO_SNDBUF",         SOL_SOCKET,   SO_SNDBUF,      sock_str_int },

28  { "SO_RCVLOWAT",       SOL_SOCKET,   SO_RCVLOWAT,    sock_str_int },

29  { "SO_SNDLOWAT",       SOL_SOCKET,   SO_SNDLOWAT,    sock_str_int },

30  { "SO_RCVTIMEO",       SOL_SOCKET,   SO_RCVTIMEO,    sock_str_timeval },

31  { "SO_SNDTIMEO",       SOL_SOCKET,   SO_SNDTIMEO,    sock_str_timeval },

32  { "SO_REUSEADDR",      SOL_SOCKET,   SO_REUSEADDR,   sock_str_flag },

33 #ifdef SO_REUSEPORT

34  { "SO_REUSEPORT",      SOL_SOCKET,   SO_REUSEPORT,   sock_str_flag },

35 #else

36  { "SO_REUSEPORT",      0,            0, NULL },

37 #endif

38  { "SO_TYPE",           SOL_SOCKET,   SO_TYPE,        sock_str_int },

39  { "SO_USELOOPBACK",    SOL_SOCKET,   SO_USELOOPBACK, sock_str_flag },

40  { "IP_TOS",            IPPROTO_IP,   IP_TOS,         sock_str_int },

41  { "IP_TTL",            IPPROTO_IP,   IP_TTL,         sock_str_int },

42  { "IPV6_DONTFRAG",     IPPROTO_IPV6, IPV6_DONTFRAG,  sock_str_flag },

43  { "IPV6_UNICAST_HOPS", IPPROTO_IPV6, IPV6_UNICAST_HOPS, sock_str_int },

44  { "IPV6_V6ONLY",       IPPROTO_IPV6, IPV6_V6ONLY,    sock_str_flag },

45  { "TCP_MAXSEG",        IPPROTO_TCP,  TCP_MAXSEG,     sock_str_int },

46  { "TCP_NODELAY",       IPPROTO_TCP,  TCP_NODELAY,    sock_str_flag },

47  { "SCTP_AUTOCLOSE",    IPPROTO_SCTP, SCTP_AUTOCLOSE, sock_str_int },

48  { "SCTP_MAXBURST",     IPPROTO_SCTP, SCTP_MAXBURST,  sock_str_int },

49  { "SCTP_MAXSEG",       IPPROTO_SCTP, SCTP_MAXSEG,    sock_str_int },

50  { "SCTP_NODELAY",      IPPROTO_SCTP, SCTP_NODELAY,   sock_str_flag },

51  { NULL,                0,            0,              NULL }

52 };

Объявление объединения возможных значений

3-9
 Наше объединение
val
содержит по одному элементу для каждого возможного возвращаемого значения из функции
getsockopt
.

Задание прототипов функций

10-13
 Мы определяем прототипы для четырех функций, которые вызываются для вывода значения данного параметра сокета.

Задание структуры и инициализация массива

14-46
 Наша структура
sock_opts
содержит всю информацию, которая необходима, чтобы вызвать функцию
getsockopt
для каждого из параметров сокета и вывести его текущее значение. Последний элемент,
opt_val_str
, является указателем на одну из четырех функций, которые выводят значение параметра. Мы размещаем в памяти и инициализируем массив этих структур, каждый элемент которого соответствует одному параметру сокета.

ПРИМЕЧАНИЕ

Не все реализации поддерживают полный набор параметров сокетов. Чтобы определить, поддерживается ли данный параметр, следует использовать #ifdef или #if defined, как показано для параметра SO_REUSEPORT. Для полноты картины требуется обработать подобным образом все параметры, но в книге мы пренебрегаем этим, потому что #ifdef только удлиняет показанный код и не влияет на суть дела.

В листинге 7.2 показана наша функция

main
.

Листинг 7.2. Функция main для проверки параметров сокетов

//sockopt/checkopts.c

53 int

54 main(int argc, char **argv)

55 {

56  int fd;

57  socklen_t len;

58  struct sock_opts *ptr;


59  for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {

60   printf("%s: ptr->opt_str);

61   if (ptr->opt_val_str == NULL)

62    printf("(undefined)\n");

63   else {

64    switch(ptr->opt_level) {

65    case SOL_SOCKET:

66    case IPPROTO_IP:

67    case IPPROTO_TCP:

68     fd = Socket(AF_INET, SOCK_STREAM, 0);

69     break;

70 #ifdef IPV6

71    case IPPROTO_IPV6:

72     fd = Socket(AF_INET6, SOCK_STREAM, 0);

73     break;

74 #endif

75 #ifdef IPPROTO_SCTP

76    case IPPROTO_SCTP:

77     fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);

78     break;

79 #endif

80    default:

81     err_quit("Can't create fd for level %d\n", ptr->opt_level);

82    }


83    len = sizeof(val);

84    if (getsockopt(fd, ptr->opt_level, ptr->opt_name,

85     &val, &len) == -1) {

86     err_ret("getsockopt error");

87    } else {

88     printf("default = %s\n", (*ptr->opt_val_str)(&val, len));

89    }

90    close(fd);

91   }

92  }

93  exit(0);

94 }

Перебор всех параметров

59-63
 Мы перебираем все элементы нашего массива. Если указатель
opt_val_str
пустой, то параметр не определен реализацией (что, как мы показали, возможно для
SO_REUSEPORT
).

Создание сокета

63-82
 Мы создаем сокет, на котором проверяем действие параметров. Для проверки параметров сокета и уровней IPv4 и TCP мы используем сокет IPv4 TCP. Для проверки параметров сокетов уровня IPv6 мы используем сокет IPv6 TCP, а для проверки параметров SCTP — сокет IPv4 SCTP.

Вызов функции getsockopt

83-87
 Мы вызываем функцию
getsockopt
, но не завершаем ее выполнение, если возвращается ошибка. Многие реализации определяют имена некоторых параметров сокетов, даже если не поддерживают эти параметры. Неподдерживаемые параметры выдают ошибку
ENOPROTOOPT
.

Вывод значения параметра по умолчанию

88-89
 Если функция
getsockopt
успешно завершается, мы вызываем нашу функцию для преобразования значения параметра в строку и выводим эту строку.

В листинге 7.1 мы показали четыре прототипа функций, по одному для каждого типа возвращаемого значения параметра. В листинге 7.3 показана одна из этих функций,

sock_str_flag
, которая выводит значение параметра, являющегося флагом. Другие три функции аналогичны этой.

Листинг 7.3. Функция sock_str_flag: преобразование флага в строку

//sockopt/checkopts.с

 95 static char strres[128];


 96 static char *

 97 sock_str_flag(union val *ptr, int len)

 98 {

 99  if (len != sizeof(int))

100   snprint(strres, sizeof(strres), "size (%d) not sizeof(int)", len);

101  else

102   snprintf(strres, sizeof(strres),

103    "%s", (ptr->i_val == 0) ? "off" : "on");

104  return(strres);

105 }

99-104
 Вспомните, что последний аргумент функции
getsockopt
— это аргумент типа «значение-результат». Первое, что мы проверяем, — это то, что размер значения, возвращаемого функцией
getsockopt
, совпадает с предполагаемым. В зависимости от того, является ли значение флага нулевым или нет, возвращается строка
off
или
on
.

Выполнение этой программы под FreeBSD 4.8 с пакетами обновлений KAME SCTP дает следующий вывод:

freebsd % checkopts

SO_BROADCAST: default = off

SO_DEBUG: default = off

SO_DONTROUTE: default = off

SO_ERROR: default = 0

SO_KEEPALIVE: default = off

SO_LINGER: default = l_onoff = 0, l_linger = 0

SO_OOBINLINE: default = off

SO_RCVBUF: default = 57344

SO_SNDBUF: default = 32768

SO_RCVLOWAT: default = 1

SO_SNDLOWAT: default = 2048

SO_RCVTIMEO: default = 0 sec, 0 usec

SO_SNDTIMEO: default = 0 sec, 0 usec

SO_REUSEADDR: default = off

SO_REUSEPORT: default = off

SO_TYPE: default = 1

SO_USELOOPBACK: default = off

IP_TOS: default = 0

IP_TTL: default = 64

IPV6_DONTFRAG: default = off

IPV6_UNICAST_HOPS: default = -1

IPV6_V6ONLY: default = off

TCP_MAXSEG: default = 512

TCP_NODELAY: default = off

SCTP_AUTOCLOSE: default = 0

SCTP_MAXBURST: default = 4

SCTP_MAXSEG: default = 1408

SCTP_NODELAY: default = off

Значение 1, возвращаемое для параметра

SO_TYPE
, для этой реализации соответствует
SOCK_STREAM
.

7.4. Состояния сокетов

Для некоторых параметров сокетов время их установки или получения зависит некоторым образом от состояния сокета. Далее мы обсудим эту зависимость для тех параметров, к которым это относится.

Следующие параметры сокетов наследуются присоединенным сокетом TCP от прослушиваемого сокета [128, с. 462-463]:

SO_DEBUG
,
SO_DONTROUTE
,
SO_KEEPALIVE
,
SO_LINGER
,
SO_OOBINLINE
,
SO_RCVBUF
,
SO_RCVLOWAT
,
SO_SNDBUF
,
SO_SNDLOWAT
,
TCP_MAXSEG
и
TCP_NODELAY.
Это важно для TCP, поскольку присоединенный сокет не возвращается серверу функцией
accept
, пока трехэтапное рукопожатие не завершится на уровне TCP. Если при завершении трехэтапного рукопожатия мы хотим убедиться, что один из этих параметров установлен для присоединенного сокета, нам следует установить этот параметр еще для прослушиваемого сокета.

7.5. Общие параметры сокетов

Мы начнем с обсуждения общих параметров сокетов. Эти параметры не зависят от протокола (то есть они управляются не зависящим от протокола кодом внутри ядра, а не отдельным модулем протокола, такого как IPv4), но некоторые из них применяются только к определенным типам сокетов. Например, несмотря на то что параметр сокета

SO_BROADCAST
называется общим, он применяется только к дейтаграммным сокетам.

Параметр сокета SO_BROADCAST

Этот параметр управляет возможностью отправки широковещательных сообщений. Широковещательная передача поддерживается только для сокетов дейтаграмм и только в сетях, поддерживающих концепцию широковещательных сообщений (Ethernet, Token Ring и т.д.). Широковещательная передача в сетях типа «точка-точка» или по ориентированному на установление соединения транспортному протоколу типа SCTP или TCP, неосуществима. Более подробно о широковещательной передаче мы поговорим в главе 18.

Поскольку перед отправкой широковещательной дейтаграммы приложение должно установить этот параметр сокета, оно не сможет отправить широковещательное сообщение, если это не предполагалось заранее. Например, приложение UDP может принять IP-адрес получателя в качестве аргумента командной строки, но оно может и не предполагать, что пользователь вводит широковещательный адрес. Проверку того, является ли данный адрес широковещательным, осуществляет не приложение, а ядро: если адрес получателя является широковещательным адресом и данный параметр сокета не установлен, возвратится ошибка

EACCESS
[128, с. 233].

Параметр сокета SO_DEBUG

Этот параметр поддерживается только протоколом TCP. При подключении к сокету TCP ядро отслеживает подробную информацию обо всех пакетах, отправленных или полученных протоколом TCP для сокета. Они хранятся в кольцевом буфере внутри ядра, который можно проверить с помощью программы

trpt
. В [128, с. 916-920] приводится более подробная информация и пример использования этого параметра.

Параметр сокета SO_DONTROUTE

Этот параметр указывает, что исходящие пакеты должны миновать обычные механизмы маршрутизации соответствующего протокола. Например, в IPv4 пакет направляется на соответствующий локальный интерфейс, который задается адресом получателя, а именно сетевым адресом и маской подсети. Если локальный интерфейс не может быть определен по адресу получателя (например, получателем не является другой конец соединения типа «точка-точка» или он не находится в той же сети), возвращается ошибка

ENETUNREACH
.

Эквивалент этого параметра можно также применять к индивидуальным дейтаграммам, используя флаг

MSG_DONTROUTE
с функциями
send
,
sendto
или
sendmsg
.

Этот параметр часто используется демонами маршрутизации (

routed
и
gated
) для того, чтобы миновать таблицу маршрутизации (в случае, если таблица маршрутизации неверна) и заставить пакет отправиться на определенный интерфейс.

Параметр сокета SO_ERROR

Когда на сокете происходит ошибка, модуль протокола в ядре, происходящем от Беркли, присваивает переменной

so_error
для этого сокета одно из стандартных значений Unix
Exxx
. Это так называемая ошибка, требующая обработки (pending error) для данного сокета. Процесс может быть немедленно оповещен об ошибке одним из двух способов:

1. Если процесс блокируется в вызове функции

select
(см. раздел 6.3), ожидая готовности данного сокета к чтению или записи, функция
select
возвращает управление и уведомляет процесс о соответствующем состоянии готовности.

2. Если процесс использует управляемый сигналом ввод-вывод (см. главу 25), для него или для группы таких процессов генерируется сигнал

SIGIO
.

Процесс может получить значение переменной

so_error
, указав параметр сокета
SO_ERROR
. Целое значение, возвращаемое функцией
getsockopt
, является кодом ошибки, требующей обработки. Затем значение переменной
so_error
сбрасывается ядром в 0 [128, с. 547].

Если процесс вызывает функцию

read
и возвращаемых данных нет, а значение
so_error
ненулевое, то функция
read
возвращает -1 с
errno
, которой присвоено значение переменной
so_error
[128, с. 516]. Это значение
so_error
затем сбрасывается в 0. Если в очереди для сокета есть данные, эти данные возвращаются функцией
read
вместо кода ошибки. Если значение
so_error
ненулевое, то при вызове процессом функции
write
возвращается -1 с
errno
, равной значению переменной
so_error
[128, с. 495], а значение
so_error
сбрасывается в 0.

ПРИМЕЧАНИЕ

В коде, показанном на с. 495 [128], есть ошибка: so_error не сбрасывается в 0. Она была выявлена в реализации BSD/OS. Всегда, когда для сокета возвращается ошибка, требующая обработки, so_error должна быть сброшена в 0.

Здесь вы впервые встречаетесь с параметром сокета, который можно получить, но нельзя установить.

Параметр сокета SO_KEEPALIVE

Когда параметр

SO_KEEPALIVE
установлен для сокета TCP и в течение двух часов не происходит обмена данными по сокету в любом направлении, TCP автоматически посылает собеседнику проверочное сообщение (keepalive probe). Это сообщение — сегмент TCP, на который собеседник должен ответить. Далее события могут развиваться по одному из трех сценариев.

1. Собеседник отвечает, присылая ожидаемый сегмент ACK. Приложение не получает уведомления (поскольку все в порядке). TCP снова отправит одно проверочное сообщение еще через два часа отсутствия активности в этом соединении.

2. Собеседник отвечает, присылая сегмент RST, который сообщает локальному TCP, что узел собеседника вышел из строя и перезагрузился. Ошибка сокета, требующая обработки, устанавливается равной

ECONNRESET
и сокет закрывается.

3. На проверочное сообщение не приходит ответ от собеседника. Код TCP, происходящий от Беркли, отправляет восемь дополнительных проверочных сообщений с интервалом в 75 с, пытаясь выявить ошибку. TCP прекратит попытки, если ответа не последует в течение 11 мин и 15 с после отправки первого сообщения.

ПРИМЕЧАНИЕ

HP-UX обрабатывает поверочные сообщения так же, как и обычные данные, то есть второе сообщение отсылается по истечении периода повторной передачи, после чего для каждого последующего пакета интервал ожидания удваивается, пока не будет достигнут максимальный интервал (по умолчанию — 10 мин).

Если на все проверочные сообщения TCP не приходит ответа, то ошибка сокета, требующая обработки, устанавливается в

ETIMEDOUT
и сокет закрывается. Но если сокет получает ошибку ICMP (Internet Control Message Protocol — протокол управляющих сообщений Интернета) в ответ на одно из проверочных сообщений, то возвращается одна из соответствующих ошибок (см. табл. А.5 и А.6), но сокет также закрывается. Типичная ошибка ICMP в этом сценарии —
Host unreachable
(Узел недоступен) — указывает на то, что узел собеседника не вышел из строя, а только является недоступным. При этом ошибка, ожидающая обработки, устанавливается в
EHOSTUNREACH
. Это может произойти из-за отказа сети или при выходе удаленного узла из строя и обнаружении этого последним маршрутизатором.

В главе 23 [111] и на с. 828-831 [128] содержатся дополнительные подробности об этом параметре.

Без сомнения, наиболее типичный вопрос, касающийся этого параметра, состоит в том, могут ли изменяться временные параметры (обычно нас интересует возможность сокращения двухчасовой задержки). В разделе 7.9 мы описываем новый параметр

TCP_KEEPALIVE
, но он не реализован достаточно широко. В приложении Е [111] обсуждается изменение временных параметров для различных ядер. Необходимо учитывать, что большинство ядер обрабатывают эти параметры глобально, и поэтому сокращение времени ожидания, например с 2 час до 15 мин, повлияет на все сокеты узла, для которых включен параметр
SO_KEEPALIVE
.

Назначение этого параметра — обнаружение сбоя на узле собеседника. Если процесс собеседника выходит из строя, его TCP отправит через соединение сегмент FIN, который мы сможем легко обнаружить с помощью функции

select
(поэтому мы использовали функцию
select
в разделе 6.4). Также нужно понимать, что если на проверочное сообщение не приходит ответа (сценарий 3), то это не обязательно означает, что на узле сервера произошел сбой и существует вероятность, что TCP закроет действующее соединение. Если, например, промежуточный маршрутизатор вышел из строя на 15 мин, то эти 15 мин полностью перекрывают период отправки проверочных сообщений от нашего узла, равный 11 мин и 15 с. Поэтому правильнее было бы назвать эту функцию не проверкой жизнеспособности (keep-alive), а контрольным выстрелом (make-dead), поскольку она может завершать еще открытые соединения.

Этот параметр обычно используется серверами, хотя его могут использовать и клиенты. Серверы используют его, поскольку большую часть своего времени они проводят в блокированном состоянии, ожидая ввода по соединению TCP, то есть в ожидании запроса клиента. Но если узел клиента выходит из строя, процесс сервера никогда не узнает об этом и сервер будет продолжать ждать ввода данных, которые никогда не придут. Это называется наполовину открытым соединением (half-open connection). Данный параметр позволяет обнаружить наполовину открытые соединения и завершить их.

Некоторые серверы, особенно серверы FTP, предоставляют приложению тайм- аут, часто до нескольких минут. Это выполняется самим приложением, обычно при вызове функции

read
, когда считывается следующая команда клиента. Этот тайм-аут не связан с данным параметром сокета.

ПРИМЕЧАНИЕ

В SCTP имеется механизм проверки пульса (heartbeat), аналогичный механизму проверочных сообщений (keep-alive) TCP. Этот механизм настраивается при помощи элементов параметра сокета SCTP_SET_PEER_ADDR_PARAMS, который будет описан далее, а не при помощи параметра SO_KEEPALIVE. Последний полностью игнорируется сокетом SCTP и не мешает работе механизма проверки пульса.

В табл. 7.3 суммируются различные методы, применяемые для обнаружения того, что происходит на другом конце соединения TCP. Когда мы говорим «использование функции

select
для проверки готовности к чтению», мы имеем в виду вызов функции
select
для проверки, готов ли сокет для чтения.


Таблица 7.3. Методы определения различных условий TCP

СценарийПроцесс собеседника выходит из строяУзел собеседника выходит из строяУзел собеседника недоступен
Наш TCP активно посылает данныеTCP собеседника посылает сегмент FIN, что мы можем сразу же обнаружить, используя функцию select для проверки готовности к чтению. Если TCP посылает второй сегмент, TCP собеседника посылает в ответ сегмент RST. Если TCP посылает еще один сегмент, наш TCP посылает сигнал SIGPIPEПо истечении времени ожидания TCP возвращается ошибка ETIMEDOUTПо истечении времени ожидания TCP возвращается ошибка ETIMEDOUT
Наш TCP активно принимает данныеTCP собеседника посылает сегмент FIN, который мы прочитаем как признак конца файла (возможно, преждевременный)Мы больше не получаем никаких данныхМы больше не получаем никаких данных
Соединение неактивно, посылается пробный пакетTCP собеседника посылает сегмент FIN, который мы можем сразу же обнаружить, используя функцию select для проверки готовности к чтениюПо истечении двух часов отсутствия активности отсылается 9 сообщений для проверки наличия связи с собеседником, а затем возвращается ошибка ETIMEDOUTПо истечении двух часов отсутствия активности отсылается 9 сообщений для проверки наличия связи с собеседником, а затем возвращается ошибка ETIMEDOUT
Соединение неактивно, не посылается проверочное сообщениеTCP собеседника посылает сегмент FIN, который мы можем сразу же обнаружить, используя функцию select для проверки готовности к чтениюНичего не происходитНичего не происходит

Параметр сокета SO_LINGER

Этот параметр определяет, как работает функция

close
для протоколов, ориентированных на установление соединения (например, TCP и SCTP, но не UDP). По умолчанию функция
close
возвращает управление немедленно, но если в отправляющем буфере сокета остаются какие-либо данные, система попытается доставить данные собеседнику.

Параметр сокета

SO_LINGER
позволяет нам изменять поведение по умолчанию. Для этого необходимо, чтобы между пользовательским процессом и ядром была передана следующая структура, определяемая в заголовочном файле
:

struct linger {

 int l_onoff; /* 0=off, ненулевое значение=on */ int l_linger;

              /* время ожидания, в POSIX измеряется в секундах */

};

Вызов функции

setsockopt
приводит к одному из трех следующих сценариев в зависимости от значений двух элементов структуры
linger
.

1. Если

l_onoff
  имеет нулевое значение, параметр выключается. Значение
l_linger
игнорируется и применяется ранее рассмотренный заданный по умолчанию сценарий TCP: функция
close
завершается немедленно.

2. Если значение

l_onoff
ненулевое, а
l_linger
равно нулю, TCP сбрасывает соединение, когда оно закрывается [128, с. 1019–1020], то есть TCP игнорирует все данные, остающиеся в буфере отправки сокета, и отправляет собеседнику сегмент RST, а не обычную последовательность завершения соединения, состоящую из четырех пакетов (см. раздел 2.5). Пример мы покажем в листинге 16.14. Тогда не наступает состояние TCP TIME_WAIT, но из-за этого возникает возможность создания другого воплощения (incarnation) этого соединения в течение 2MSL секунд (удвоенное максимальное время жизни сегмента). Оставшиеся старые дублированные сегменты из только что завершенного соединения могут быть доставлены новому воплощению, что приведет к ошибкам (см. раздел 2.6).

При указанных выше значениях

l_onoff
и
l_linger
SCTP также выполняет аварийное закрытие сокета, отправляя собеседнику пакет ABORT (см. раздел 9.2 [117]).

ПРИМЕЧАНИЕ

Отдельные выступления в Usenet звучат в защиту использования этой возможности, поскольку она позволяет избежать состояния TIME_WAIT и снова запустить прослушивающий сервер, даже если соединения все еще используются с известным портом сервера. Так не нужно делать, поскольку это может привести к искажению данных, как показано в RFC 1337 [11]. Вместо этого перед вызовом функции bind на стороне сервера всегда нужно использовать параметр сокета SO_REUSEADDR, как показано далее. Состояние TIME_WAIT — наш друг, так как оно предназначено для того, чтобы помочь нам дождаться, когда истечет время жизни в сети старых дублированных сегментов. Вместо того, чтобы пытаться избежать этого состояния, следует понять его назначение (см. раздел 2.6).

Тем не менее в некоторых обстоятельствах использование аварийного закрытия может быть оправдано. Одним из примеров является сервер терминалов RS-232, который может навечно зависнуть в состоянии CLOSE_WAIT, пытаясь доставить данные на забитый порт. Если же он получит сегмент RST, он сможет сбросить накопившиеся данные и заново инициализировать порт.

3. Если оба значения —

l_onoff
и
l_linger
— ненулевые, то при закрытии сокета ядро будет ждать (linger) [128, с. 472]. То есть если в буфере отправки сокета еще имеются какие-либо данные, процесс входит в состояние ожидания до тех пор, пока либо все данные не будут отправлены и подтверждены другим концом TCP, либо не истечет время ожидания. Если сокет был установлен как неблокируемый (см. главу 16), он не будет ждать завершения выполнения функции
close
, даже если время задержки ненулевое. При использовании этого свойства параметра
SO_LINGER
приложению важно проверить значение, возвращаемое функцией
close
. Если время ожидания истечет до того, как оставшиеся данные будут отправлены и подтверждены, функция
close
возвратит ошибку
EWOULDBLOCK
и все данные, оставшиеся в буфере отправки сокета, будут сброшены.

Теперь нам нужно точно определить, когда завершается функция

close
на сокете в различных сценариях, которые мы рассмотрели. Предполагается, что клиент записывает данные в сокет и вызывает функцию
close
. На рис. 7.1 показана ситуация по умолчанию.

Рис. 7.1. Действие функции close, заданное по умолчанию: немедленное завершение

Мы предполагаем, что когда приходят данные клиента, сервер временно занят. Поэтому данные добавляются в приемный буфер сокета его протоколом TCP. Аналогично, следующий сегмент (сегмент FIN клиента) также добавляется к приемному буферу сокета (каким бы образом реализация ни сохраняла сегмент FIN). Но по умолчанию клиентская функция

close
сразу же завершается. Как мы показываем в этом сценарии, клиентская функция
close
может завершиться перед тем, как сервер прочитает оставшиеся данные в приемном буфере его сокета. Если узел сервера выйдет из строя перед тем, как приложение-сервер считает оставшиеся данные, клиентское приложение никогда об этом не узнает.

Клиент может установить параметр сокета

SO_LINGER
, задав некоторое положительное время задержки. Когда это происходит, клиентская функция
close
не завершается до тех пор, пока все данные клиента и его сегмент FIN не будут подтверждены протоколом TCP сервера. Мы показываем это на рис. 7.2.

Рис. 7.2. Закрытие сокета с параметром SO_LINGER и положительным l_linger

Но у нас остается та же проблема, что и на рис. 7.1: если на узле сервера происходит сбой до того, как приложение-сервер считает оставшиеся данные, клиентское приложение никогда не узнает об этом. Еще худший вариант развития событий показан на рис. 7.3, где значение SO_

LINGER
было установлено слишком маленьким.

Рис. 7.3. Закрытие сокета с параметром SO_LINGER при малом положительном l_linger

Основным принципом взаимодействия является то, что успешное завершение функции

close
с установленным параметром сокета
SO_LINGER
говорит нам лишь о том, что данные, которые мы отправили (и наш сегмент FIN) подтверждены протоколом TCP собеседника. Но это не говорит нам, прочитало ли данные приложение собеседника. Если мы не установим параметр сокета
SO_LINGER
, мы не будем знать, подтвердил ли другой конец TCP отправленные ему данные.

Чтобы узнать, что сервер прочитал данные клиента, клиент может вызвать функцию

shutdown
(со вторым аргументом
SHUT_WR
) вместо функции
close
и ждать, когда собеседник закроет с помощью функции
close
свой конец соединения. Этот сценарий показан на рис. 7.4.

Рис. 7.4. Использование функции shutdown для проверки того, что собеседник получил наши данные

Сравнивая этот рисунок с рис. 7.1 и 7.2, мы видим, что когда мы закрываем наш конец соединения, то в зависимости от вызванной функции (

close
или
shutdown
) и от того, установлен или нет параметр сокета
SO_LINGER
, завершение может произойти в один из трех различных моментов времени: '

1. Функция

close
завершается немедленно, без всякого ожидания (сценарий, заданный по умолчанию, см. рис. 7.1).

2. Функция

close
задерживается до тех пор, пока не будет получен сегмент ACK, подтверждающий получение сервером сегмента FIN от клиента (см. рис. 7.2).

3. Функция

shutdown
, за которой следует функция
read
, ждет, когда мы получим сегмент FIN собеседника (в данном случае сервера) (см. рис. 7.2).

Другой способ узнать, что приложение-собеседник прочитало наши данные, — использовать подтверждение на уровне приложения, или ACK приложения. Например, клиент отправляет данные серверу и затем вызывает функцию

read
для одного байта данных:

char ack;


Write(sockfd, data, nbytes); /* данные от клиента к серверу */

n = Read(sockfd, &ack, 1);   /* ожидание подтверждения на уровне приложения */

Сервер читает данные от клиента и затем отправляет ему 1-байтовый сегмент — подтверждение на уровне приложения:

nbytes = Read(sockfd, buff, sizeof(buff)); /* данные от клиента */

/* сервер проверяет, верное ли количество данных он получил от клиента */

Write(sockfd, 1); /* сегмент ACK сервера возвращается клиенту */

Таким образом, мы получаем гарантию, что на момент завершения функции read на стороне клиента процесс сервера прочитал данные, которые мы отправили. (При этом предполагается, что либо сервер знает, сколько данных отправляет клиент, либо существует некоторый заданный приложением маркер конца записи, который мы здесь не показываем.) В данном случае сегмент ACK на уровне приложения представляет собой нулевой байт, но вообще содержимое этого сегмента можно использовать для передачи от сервера к клиенту сообщений о других условиях. На рис. 7.5 показан возможный обмен пакетами.

Рис. 7.5. ACK приложения

В табл. 7.4 описаны два возможных вызова функции

shutdown
и три возможных вызова функции
close
, а также их влияние на сокет TCP.


Таблица 7.4. Итоговая таблица сценариев функции shutdown и параметров сокета SO_LINGER

ФункцияОписание
shutdown, SHUT_RDЧерез сокет больше нельзя принимать данные; процесс может по-прежнему отправлять данные через этот сокет; приемный буфер сокета сбрасывается; все данные, получаемые в дальнейшем, игнорируются протоколом TCP (см. упражнение 6.5); не влияет на буфер отправки сокета
shutdown, SHUT_WRЧерез сокет больше нельзя отправлять данные; процесс может по-прежнему получать данные через этот сокет; содержимое буфера отправки сокета отсылается на другой конец соединения, затем выполняется обычная последовательность действий по завершению соединения TCP (FIN); не влияет на приемный буфер сокета
close, l_onoff = 0 (по умолчанию)Через сокет больше нельзя отправлять и получать данные; содержимое буфера отправки сокета отсылается на другой конец соединения. Если счетчик ссылок дескриптора становится нулевым, то следом за отправкой данных из буфера отправки сокета выполняется нормальная последовательность завершения соединения TCP (FIN), данные из приемного буфера сокета сбрасываются
close, l_onoff = 1 l_linger = 0Через сокет больше нельзя отправлять и получать данные. Если счетчик ссылок дескриптора становится нулевым, то на другой конец соединения посылается сегмент RST, соединение переходит в состояние в CLOSED (минуя состояние TIME_WAIT), данные из буфера отправки и приемного буфера сокета сбрасываются
close, l_onoff = 1 l_linger = 0Через сокет больше нельзя отправлять и получать данные; содержимое буфера отправки сокета отсылается на другой конец соединения. Если счетчик ссылок дескриптора становится нулевым, то следом за отправкой данных из буфера отправки сокета выполняется нормальная последовательность завершения соединения TCP (FIN), данные из приемного буфера сокета сбрасываются, и если время задержки истекает, прежде чем оставшиеся в буфере данные будут посланы и будет подтвержден их прием, функция close возвратит ошибку EWOULDBLOCK

Параметр сокета SO_OOBINLINE

Когда установлен этот параметр, внеполосные данные помещаются в очередь нормального ввода (то есть вместе с обычными данными (inline)). Когда это происходит, флаг

MSG_OOB
не может быть использован для чтения полученных внеполосных данных. Более подробно внеполосные данные мы рассмотрим в главе 24.

Параметры сокета SO_RCVBUF и SO_SNDBUF

У каждого сокета имеется буфер отправки и приемный буфер (буфер приема). Мы изобразили действие буферов отправки TCP, UDP и SCTP на рис. 2.15, 2.16 и 2.17.

Приемные буферы используются в TCP, UDP и SCTP для хранения полученных данных, пока они не будут считаны приложением. В случае TCP доступное пространство в приемном буфере сокета — это окно, размер которого TCP сообщает другому концу соединения. Приемный буфер сокета TCP не может переполниться, поскольку собеседнику не разрешается отправлять данные, размер которых превышает размер окна. Так действует управление передачей TCP, и если собеседник игнорирует объявленное окно и отправляет данные, превышающие его размер, принимающий TCP игнорирует эти данные. Однако в случае UDP дейтаграмма, не подходящая для приемного буфера сокета, игнорируется. Вспомните, что в UDP отсутствует управление потоком: более быстрый отправитель легко переполнит буфер медленного получателя, заставляя UDP получателя игнорировать дейтаграммы, как мы покажем в разделе 8.13. Более того, быстрый отправитель может переполнить даже собственный сетевой интерфейс, так что дейтаграммы будут сбрасываться еще до отправки их с исходного узла.

Указанные в заголовке раздела параметры позволяют нам изменять размеры буферов, заданные по умолчанию. Значения по умолчанию сильно отличаются в зависимости от реализации. Более ранние реализации, происходящие от Беркли, по умолчанию имели размеры буферов отправки и приема 4096 байт, а более новые системы используют буферы больших размеров, от 8192 до 61 440 байт. Размер буфера отправки UDP по умолчанию часто составляет около 9000 байт, а если узел поддерживает NFS, то размер приемного буфера UDP увеличивается до 40 000 байт.

При установке размера приемного буфера сокета TCP важен порядок вызова функций, поскольку в данном случае учитывается параметр масштабирования окна TCP (см. раздел 2.5). При установлении соединения обе стороны обмениваются сегментами SYN, в которых может содержаться этот параметр. Для клиента это означает, что параметр сокета

SO_RCVBUF
должен быть установлен перед вызовом функции
connect
. Для сервера это означает, что данный параметр должен быть установлен для прослушиваемого сокета перед вызовом функции
listen
. Установка этого параметра для присоединенного сокета никак не повлияет на параметр масштабирования окна, поскольку функция
accept
не возвращает управление процессу, пока не завершится трехэтапное рукопожатие TCP. Поэтому данный параметр должен быть установлен для прослушиваемого сокета. (Размеры буферов сокета всегда наследуются от прослушиваемого сокета создаваемым присоединенным сокетом [128, с. 462-463]).

Размеры буферов сокета TCP должны быть как минимум вчетверо больше MSS (максимальный размер сегмента) для соединения. Если мы имеем дело с направленной передачей данных, такой как передача файла в одном направлении, то говоря «размеры буферов сокета», мы подразумеваем буфер отправки сокета на отправляющем узле или приемный буфер сокета на принимающем узле. В случае двусторонней передачи данных мы имеем в виду оба размера буферов на обоих узлах. С типичным размером буфера 8192 байт или больше и типичным MSS, равным 512 или 1460 байт, это требование обычно выполняется. Проблемы были замечены в сетях с большими MTU (максимальная единица передачи), которые предоставляют MSS больше обычного (например, в сетях ATM с MTU, равной 9188).

ПРИМЕЧАНИЕ

Значение минимального множителя (4) обусловлено принципом работы алгоритма быстрого восстановления TCP. Отправитель использует три двойных подтверждения, чтобы обнаружить утерянный пакет (RFC 2581 [4]). Получатель отправляет двойное подтверждение для каждого сегмента, принятого после того, который был пропущен. Если размер окна меньше четырех сегментов, трех двойных подтверждений не будет и алгоритм быстрого восстановления не сработает.

Размеры буфера сокета TCP должны быть также четное число раз кратны размеру MSS для соединения. Некоторые реализации выполняют это требование для приложения, округляя размеры в сторону большего размера буфера сокета после установления соединения [128, с. 902]. Это другая причина, по которой следует задавать эти два параметра сокета перед установлением соединения. Например, если использовать размеры, заданные по умолчанию в 4.4BSD (8192 байт), и считать, что используется Ethernet с размером MSS, равным 1460 байт, то при установлении соединения размеры обоих буферов сокета будут округляться до 8760 байт (6×1460). Это требование не жесткое, лишнее место в буфере просто не будет использоваться.

Другое соображение относительно установки размеров буфера сокета связано с производительностью. На рис. 7.6 показано соединение TCP между двумя конечными точками (которое мы называем каналом) с вместимостью, допускающей передачу восьми сегментов.

Рис. 7.6. Соединение TCP (канал), вмещающее восемь сегментов

Мы показываем четыре сегмента данных вверху и четыре сегмента ACK внизу. Даже если в канале только четыре сегмента данных, у клиента должен быть буфер отправки, вмещающий минимум восемь сегментов, потому что TCP клиента должен хранить копию каждого сегмента, пока не получен сегмент ACK от сервера.

ПРИМЕЧАНИЕ

Здесь мы игнорируем некоторые подробности. Прежде всего, алгоритм медленного запуска TCP ограничивает скорость, с которой сегменты начинают отправляться по соединению, которое до этого было неактивным. Далее, TCP часто подтверждает каждый второй сегмент, а не каждый сегмент, как мы это показываем. Все эти подробности описаны в главах 20 и 24 [111].

Нам необходимо понять принцип функционирования двустороннего канала и узнать, что такое его вместимость и как она влияет на размеры буферов сокетов на обоих концах соединения. Вместимость канала характеризуется произведением пропускной способности на задержку (bandwidth-delay product). Мы будем вычислять ее, умножая пропускную способность канала (в битах в секунду) на период обращения (RTT, round-trip time) (в секундах) и преобразуя результат из битов в байты. RTT легко измеряется с помощью утилиты

ping
. Пропускная способность — это значение, соответствующее наиболее медленной связи между двумя конечными точками; предполагается, что это значение каким-то образом определено. Например, линия T1 (1 536 000 бит/с) с RTT 60 мс дает произведение пропускной способности на задержку, равное 11 520 байт. Если размеры буфера сокета меньше указанного, канал не будет заполнен и производительность окажется ниже предполагаемой. Большие буферы сокетов требуются, когда повышается пропускная способность (например, для линии T3, где она равна 45 Мбит/с) или когда увеличивается RTT (например, спутниковые каналы связи с RTT около 500 мс). Когда произведение пропускной способности на задержку превосходит максимальный нормальный размер окна TCP (65 535 байт), обоим концам соединения требуются также параметры TCP для канала с повышенной пропускной способностью (long fat pipe), о которых мы упоминали в разделе 2.6.

ПРИМЕЧАНИЕ

В большинстве реализаций размеры буферов отправки и приема ограничиваются некоторым предельным значением. В более ранних реализациях, происходящих от Беркли, верхний предел был около 52 000 байт, но в новых реализациях предел по умолчанию равен 256 000 байт или больше, и обычно администратор имеет возможность увеличивать его. К сожалению, не существует простого способа, с помощью которого приложение могло бы узнать этот предел. POSIX определяет функцию fpathconf, поддерживаемую большинством реализаций, а в качестве второго аргумента этой функции должна использоваться константа _PC_SOCK_MAXBUF. Приложение может также попытаться установить желаемый размер буфера сокета, а если попытка окажется неудачной, сократить размер вдвое и вызвать функцию снова. Наконец, приложение должно убедиться, что оно не уменьшает размер буфера по умолчанию, задавая свое собственное значение. В первую очередь следует вызвать getsockopt для определения значения, установленного по умолчанию, которое вполне может оказаться достаточным.

Параметры сокета SO_RCVLOWAT и SO_SNDLOWAT

Каждый сокет характеризуется также минимальным количеством данных (low- water mark) для буферов приема и отправки. Эти значения используются функцией

select
, как мы показали в разделе 6.3. Указанные параметры сокета позволяют нам изменять эти два значения.

Минимальное количество данных — это количество данных, которые должны находиться в приемном буфере сокета, чтобы функция

select
возвратила ответ «Сокет готов для чтения». По умолчанию это значение равно 1 для сокетов TCP и UDP. Минимальный объем для буфера отправки — это количество свободного пространства, которое должно быть в буфере отправки сокета, чтобы функция select возвратила «Сокет готов для записи». Для сокетов TCP по умолчанию оно обычно равно 2048. С UDP это значение используется так, как мы показали в разделе 6.3, но поскольку число байтов доступного пространства в буфере отправки для сокета UDP никогда не изменяется (поскольку UDP не хранит копии дейтаграмм, отправленных приложением), сокет UDP всегда готов для записи, пока размер буфера отправки сокета UDP больше минимального объема. Вспомните рис. 2.16: UDP не имеет настоящего буфера отправки, у него есть только параметр размера буфера отправки.

Параметры сокета SO_RCVTIMEO и SO_SNDTIMEO

Эти два параметра сокета позволяют нам устанавливать тайм-аут при получении и отправке через сокет. Обратите внимание, что аргумент двух функций

sockopt
— это указатель на структуру
timeval
, ту же, которую использует функция
select
(раздел 6.3). Это позволяет использовать для задания тайм-аута секунды и миллисекунды. Отключение тайм-аута осуществляется установкой его значения в 0 секунд и 0 миллисекунд. Оба тайм-аута по умолчанию отключены.

Тайм-аут приема влияет на пять функций ввода:

read
,
readv
,
recv
,
recvfrom
и
recvmsg
. Тайм-аут отправки влияет на пять функций вывода:
write
,
writev
,
send
,
sendto
и
sendmsg
. Более подробно о тайм-аутах сокета мы поговорим в разделе 14.2.

ПРИМЕЧАНИЕ

Эти два параметра сокета и концепция тайм-аута сокетов вообще были добавлены в реализации 4.3BSD Reno.

В реализациях, происходящих от Беркли, указанные параметры инициализируют таймер отсутствия активности, а не абсолютный таймер системного вызова чтения или записи. На с. 496 и 516 [128] об этом рассказывается более подробно.

Параметры сокета SO_REUSEADDR и SO_REUSEPORT

Параметр сокета

SO_REUSEADDR
служит для четырех целей.

1. Параметр

SO_REUSEADDR
позволяет прослушивающему серверу запуститься и с помощью функции
bind
связаться со своим заранее известным портом, даже если существуют ранее установленные соединения, использующие этот порт в качестве своего локального порта. Эта ситуация обычно возникает следующим образом:

 1) запускается прослушивающий сервер;

 2) от клиента приходит запрос на соединение, и для обработки этого клиента генерируется дочерний процесс;

 3) прослушивающий сервер завершает работу, но дочерний процесс продолжает обслуживание клиента на существующем соединении;

 4) прослушивающий сервер перезапускается.

По умолчанию, когда прослушивающий сервер перезапускается при помощи вызова функций

socket
,
bind
и
listen
, вызов функции
bind
оказывается неудачным, потому что прослушивающий сервер пытается связаться с портом, который является частью существующего соединения (обрабатываемого ранее созданным дочерним процессом). Но если сервер устанавливает параметр сокета
SO_REUSEADDR
между вызовами функций
socket
и
bind
, последняя выполнится успешно. Все серверы TCP должны задавать этот параметр сокета, чтобы позволить перезапускать сервер в подобной ситуации.

ПРИМЕЧАНИЕ

Этот сценарий вызывает больше всего вопросов в Usenet.

2. Параметр

SO_REUSEADDR
позволяет множеству экземпляров одного и того же сервера запускаться на одном и том же порте, если все экземпляры связываются с различными локальными IP-адресами. Это типичная ситуация для узла, на котором размещаются несколько серверов HTTP, использующих технологию альтернативных IP-адресов, или псевдонимов (IP alias technique) (см. раздел А.4). Допустим, первичный IP-адрес локального узла — 198.69.10.2, но он имеет два альтернативных адреса — 198.69.10.128 и 198.69.10.129. Запускаются три сервера HTTP. Первый сервер с помощью функции bind свяжется с локальным IP-адресом 198.69.10.128 и локальным портом 80 (заранее известный порт HTTP). Второй сервер с помощью функции
bind
свяжется с локальным IP-адресом 198.69.10.129 и локальным портом 80. Но второй вызов функции
bind
не будет успешным, пока не будет установлен параметр
SO_REUSEADDR
перед обращением к ней. Третий сервер вызовет функцию bind с универсальным адресом в качестве локального IP-адреса и локальным портом 80. И снова требуется параметр
SO_REUSEADDR
, для того чтобы последний вызов оказался успешным. Если считать, что установлен параметр
SO_REUSEADDR
и запущены три сервера, то входящие запросы TCP на соединение с IP-адресом получателя 198.69.10.128 и портом получателя 80 доставляются на второй сервер, входящие запросы на соединение с IP-адресом получателя 198.69.10.129 и портом получателя 80 — на третий сервер, а все остальные входящие запросы TCP на соединение с портом получателя 80 доставляются на первый сервер. Этот сервер обрабатывает запросы, адресованные на 198.69.10.2, в дополнение к другим альтернативным IP-адресам, для которых этот узел может быть сконфигурирован. Символ подстановки означает в данном случае «все, для чего не нашлось более точного совпадения». Заметим, что этот сценарий, допускающий множество серверов для данной службы, обрабатывается автоматически, если сервер всегда устанавливает параметр сокета
SO_REUSEADDR
(как мы рекомендуем).

TCP не дает нам возможности запустить множество серверов, которые с помощью функции

bind
связываются с одним и тем же IP-адресом и одним и тем же портом: это случай полностью дублированного связывания (completely duplicate binding). То есть мы не можем запустить один сервер, связывающийся с адресом 198.69.10.2 и портом 80, и другой сервер, также связывающийся с адресом 198.69.10.2 и портом 80, даже если для второго сервера мы установим параметр
SO_REUSEADDR
.

По соображениям безопасности некоторые операционные системы запрещают связывать несколько серверов с адресом подстановки, то есть описанный выше сценарий не работает даже с использованием параметра

SO_REUSEADDR
. В такой системе сервер, связываемый с адресом подстановки, должен запускаться последним. Таким образом предотвращается привязка сервера злоумышленника к IP-адресу и порту, которые уже обрабатываются системной службой. Особенно это важно для службы NFS, которая обычно не использует выделенный порт.

3. Параметр

SO_REUSEADDR
позволяет одиночному процессу связывать один и тот же порт с множеством сокетов, так как при каждом связывании задается уникальный IP-адрес. Это обычное явление для серверов UDP, так как им необходимо знать IP-адрес получателя запросов клиента в системах, не поддерживающих параметр сокета
IP_RECVSTADDR
. Эта технология обычно не применяется с серверами TCP, поскольку сервер TCP всегда может определить IP-адрес получателя при помощи вызова функции
getsockname
, после того как соединение установлено. Однако на многоинтерфейсном узле сервер TCP, работающий с частью адресов локального узла, мог бы воспользоваться этой функцией.

4. Параметр

SO_REUSEADDR
допускает полностью дублированное связывание: связывание с помощью функции
bind
с IP-адресом и портом, когда тот же IP-адрес и тот же порт уже связаны с другим сокетом. Обычно это свойство доступно только в системах с поддержкой многоадресной передачи без поддержки параметра сокета SO_REUSEPORT (который мы опишем чуть ниже), и только для сокетов UDP (многоадресная передача не работает с TCP).

Это свойство применяется при многоадресной передаче для многократного выполнения одного и того же приложения на одном и том же узле. Когда приходит дейтаграмма UDP для одного из многократно связанных сокетов, действует следующее правило: если дейтаграмма предназначена либо для широковещательного адреса, либо для адреса многоадресной передачи, то одна копия дейтаграммы доставляется каждому сокету. Но если дейтаграмма предназначена для адреса направленной передачи, то дейтаграмма доставляется только на один сокет. Какой сокет получит дейтаграмму, если в случае направленной передачи существует множество сокетов, соответствующих дейтаграмме, — зависит от реализации. На с. 777-779 [128] об этом свойстве рассказывается более подробно. О широковещательной и многоадресной передаче мы поговорим соответственно в главах 20 и 21.

В упражнениях 7.5 и 7.6 показаны примеры использования этого параметра сокета.

Вместо того чтобы перегружать параметр

SO_REUSEADDR
семантикой многоадресной передачи, допускающей полностью дублированное связывание, в 4.4BSD был введен новый параметр сокета
SO_REUSEPORT
, обладающий следующей семантикой:

1. Этот параметр допускает полностью дублированное связывание, но только если каждый сокет, который хочет связаться с тем же IP-адресом и портом, задает этот параметр сокета.

2. Параметр

SO_REUSEADDR
считается эквивалентным параметру
SO_REUSEPORT
, если связываемый IP-адрес является адресом многоадресной передачи [128, с. 731].

Проблема с этим параметром сокета заключается в том, что не все системы его поддерживают. В системах без поддержки этого параметра, но с поддержкой многоадресной передачи его функции выполняет параметр

SO_REUSEADDR
, допускающий полностью дублированное связывание, когда оно имеет смысл (то есть когда имеется сервер UDP, который может быть запущен много раз на одном и том же узле в одно и то же время, предполагающий получать либо широковещательные дейтаграммы, либо дейтаграммы многоадресной передачи).

Обобщить обсуждение этих параметров сокета можно с помощью следующих рекомендаций:

1. Устанавливайте параметр SO_

REUSEADDR
перед вызовом функции
bind
на всех серверах TCP.

2. При создании приложения многоадресной передачи, которое может быть запущено несколько раз на одном и том же узле в одно и то же время, устанавливайте параметр

SO_REUSEADDR
и связывайтесь с адресом многоадресной передачи, используемым в качестве локального IP-адреса.

Более подробно об этих параметрах сокета рассказывается в главе 22 [128].

Существует потенциальная проблема безопасности, связанная с использованием параметра

SO_REUSEADDR
. Если существует сокет, связанный, скажем, с универсальным адресом и портом 5555, то, задав параметр
SO_REUSEADDR
, мы можем связать этот порт с другим IP-адресом, например с основным (primary) IP-адресом узла. Любые приходящие дейтаграммы, предназначенные для порта 5555 и IP- адреса, который мы связали с нашим сокетом, доставляются на наш сокет, а не на другой сокет, связанный с универсальным адресом. Это могут быть сегменты SYN TCP или дейтаграммы UDP. (В упражнении 11.9 показано это свойство для UDP.) Для большинства известных служб, таких как HTTP, FTP и Telnet, это не составляет проблемы, поскольку все эти серверы связываются с зарезервированным портом. Следовательно, любой процесс, запущенный позже и пытающийся связаться с конкретным экземпляром этого порта (то есть пытающийся завладеть портом), требует прав привилегированного пользователя. Однако NFS (Network File System — сетевая файловая система) может вызвать проблемы, поскольку ее стандартный порт (2049) не зарезервирован.

ПРИМЕЧАНИЕ

Одна из сопутствующих проблем API сокетов в том, что установка пары сокетов выполняется с помощью двух вызовов функций (bind и connect) вместо одного. В [122] предлагается одиночная функция, разрешающая эту проблему:

int bind_connect_listen(int sockfd,

 const struct sockaddr *laddr, int laddrlen,

 const struct sockaddr *faddr, int faddrlen,

 int listen);

Аргумент laddr задает локальный IP-адрес и локальный порт, аргумент faddr — удаленный IP-адрес и удаленный порт, аргумент listen задает клиент (0) или сервер (значение ненулевое; то же, что и аргумент backlog функции listen). В таком случае функция bind могла бы быть библиотечной функцией, вызывающей эту функцию с пустым указателем faddr и нулевым faddrlen, а функция connect — библиотечной функцией, вызывающей эту функцию с пустым указателем laddr и нулевым laddrlen. Существует несколько приложений, особенно FTP, которым необходимо задавать и локальную пару, и удаленную пару, которые могут вызывать bind_connect_listen непосредственно. При наличии подобной функции отпадает необходимость в параметре SO_REUSEADDR, в отличие от серверов UDP, которым явно необходимо допускать полностью дублированное связывание с одним и тем же IP-адресом и портом. Другое преимущество этой новой функции в том, что сервер TCP может ограничить себя обслуживанием запросов на соединения, приходящих от одного определенного IP-адреса и порта. Это определяется в RFC 793 [96], но невозможно с существующими API сокетов.

Параметр сокета SO_TYPE

Этот параметр возвращает тип сокета. Возвращаемое целое число — константа

SOCK_STREAM
или
SOCK_DGRAM
. Этот параметр обычно используется процессом, наследующим сокет при запуске.

Параметр сокета SO_USELOOPBACK

Этот параметр применяется только к маршрутизирующим сокетам (

AF_ROUTE
). По умолчанию он включен на этих сокетах (единственный из параметров
SO_xxx
, по умолчанию включенный). В этом случае сокет получает копию всего, что отправляется на сокет.

ПРИМЕЧАНИЕ

Другой способ отключить получение этих копий — вызвать функцию shutdown со вторым аргументом SHUT_RD.

7.6. Параметры сокетов IPv4

Эти параметры сокетов обрабатываются IPv4 и для них аргумент

level
равен
IPPROTO_IP
. Обсуждение пяти параметров сокетов многоадресной передачи мы отложим до раздела 19.5.

Параметр сокета IP_HRDINCL

Если этот параметр задан для символьного сокета IP (см. главу 28), нам следует создать наш собственный заголовок IP для всех дейтаграмм, которые мы отправляем через символьный сокет. Обычно ядро создает заголовок IP для дейтаграмм, отправляемых через символьный сокет, но существует ряд приложений (в частности,

traceroute
), создающих свой собственный заголовок IP, заменяющий значения, которые IP поместил бы в определенные поля заголовка.

Когда установлен этот параметр, мы создаем полный заголовок IP со следующими исключениями:

■ IP всегда сам вычисляет и записывает контрольную сумму заголовка IP.

■ Если мы устанавливаем поле идентификации IP в 0, ядро устанавливает это поле самостоятельно.

■ Если IP-адрес отправителя (source address) —

INADDR_ANY
, IP устанавливает его равным основному IP-адресу исходящего интерфейса.

■ Как устанавливать параметры IP, зависит от реализации. Некоторые реализации добавляют любые параметры IP, установленные с использованием параметра сокета

IP_OPTIONS
, к создаваемому нами заголовку, в то время как другие требуют, чтобы мы сами добавили в заголовок все необходимые параметры IP.

■ Некоторые поля должны располагаться в порядке байтов узла, тогда как другие — в сетевом порядке байтов. Это тоже зависит от реализации, из-за чего программы, работающие с символьными сокетами с параметром

IP_HDRINCL
, становятся не такими переносимыми, как хотелось бы.

Пример использования этого параметра показан в разделе 29.7. Дополнительная информация об этом параметре представлена в [128, с. 1056–1057].

Параметр сокета IP_OPTIONS

Установка этого параметра позволяет нам задавать параметры IP в заголовке IPv4. Это требует точного знания формата параметров IP в заголовке IP. Мы рассмотрим этот параметр в контексте маршрутизации от отправителя IPv4 в разделе 27.3.

Параметр сокета IP_RECVDSTADDR

Этот параметр сокета заставляет функцию

recvmsg
возвращать IP-адрес получателя в получаемой дейтаграмме UDP в качестве вспомогательных данных. Пример использования этого параметра мы приводим в разделе 22.2.

Параметр сокета IP_RECVIF

Этот параметр сокета заставляет функцию

recvmsg
возвращать индекс интерфейса, на котором принимается дейтаграмма UDP, в качестве вспомогательных данных. Пример использования этого параметра мы приводим в разделе 22.2.

Параметр сокета IP_TOS

Этот параметр позволяет нам устанавливать поле тип службы (тип сервиса) (TOS, type-of-service) (рис. А.1) в заголовке IP для сокета TCP или UDP. Если мы вызываем для этого сокета функцию getsockopt, возвращается текущее значение, которое будет помещено в поля DSCP и ECN заголовка IP (по умолчанию значение нулевое). Не существует способа извлечь это значение из полученной дейтаграммы IP.

Приложение может установить DSCP равным одному из значений, о которых существует договоренность с провайдером. Каждому значению соответствует определенный тип обслуживания, например IP-телефонии требуется низкая задержка, а передачи больших объемов данных требуют повышенной пропускной способности. Документ RFC 2474 [82] определяет архитектуру diffserv, которая обеспечивает лишь ограниченную обратную совместимость с историческим определением поля TOS (RFC 1349 [5]). Приложения, устанавливающие параметр

IP_TOS
равным одной из констант, определенных в файле
(например,
IPTOS_LOWDELAY
или
IPTOS_THROUGHPUT
), должны вместо этого использовать различные значения DSCP. Архитектура diffserv сохраняет только два значения (6 и 7, что соответствует константам
IPTOS_PREC_NETCONTROL
и
IPTOS_PREC_INTERNETCONTROL
), так что только те приложения, которые используют именно эти константы, будут работать в сетях diffserv.

Документ RFC 3168 [100] определяет поле ECN. Приложениям рекомендуется предоставлять установку этого поля ядру и сбрасывать в нуль два младших бита значения, заданного

IP_TOS
.

Параметр сокета IP_TTL

С помощью этого параметра мы можем устанавливать и получать заданное по умолчанию значение TTL (time-to-live field — поле времени жизни, рис. А.1), которое система будет использовать для данного сокета. (TTL для многоадресной передачи устанавливается при помощи параметра сокета

IP_MULTICAST_TTL
, который описывается в разделе 21.6.) В системе 4.4BSD, например, значение TTL по умолчанию для сокетов TCP и UDP равно 64 (оно определяется в RFC 1700), а для символьных сокетов — 255. Как и в случае поля TOS, вызов функции
getsockopt
возвращает значение поля по умолчанию, которое система будет использовать в исходящих дейтаграммах, и не существует способа определить это значение по полученной дейтаграмме. Мы устанавливаем этот параметр сокета в нашей программе
traceroute
в листинге 28.15.

7.7. Параметр сокета ICMPv6

Единственный параметр сокета, обрабатываемый ICMPv6, имеет аргумент

level
, равный
IPPROTO_ICMPV6
.

Параметр сокета ICMP6_FILTER

Этот параметр позволяет нам получать и устанавливать структуру

icmp6_filter
, которая определяет, какие из 256 возможных типов сообщений ICMPv6 передаются для обработки на символьный сокет. Мы обсудим этот параметр в разделе 28.4.

7.8. Параметры сокетов IPv6

Эти параметры сокетов обрабатываются IPv6 и имеют аргумент

level
, равный
IPPROTO_IPV6
. Мы отложим обсуждение пяти параметров сокетов многоадресной передачи до раздела 21.6. Отметим, что многие из этих параметров используют вспомогательные данные с функцией
recvmsg
, и мы покажем это в разделе 14.6. Все параметры сокетов IPv6 определены в RFC 3493 [36] и RFC 3542 [114].

Параметр сокета IPV6_CHECKSUM

Этот параметр сокета задает байтовое смещение поля контрольной суммы внутри данных пользователя. Если значение неотрицательное, ядро, во-первых, вычисляет и хранит контрольную сумму для всех исходящих пакетов и, во-вторых, проверяет полученную контрольную сумму на вводе, игнорируя пакеты с неверной контрольной суммой. Этот параметр влияет на символьные сокеты IPv6, отличные от символьных сокетов ICMPv6. (Ядро всегда вычисляет и хранит контрольную сумму для символьных сокетов ICMPv6.) Если задано значение -1 (значение по умолчанию), ядро не будет вычислять и хранить контрольную сумму для исходящих пакетов на этом символьном сокете и не будет проверять контрольную сумму для получаемых пакетов.

ПРИМЕЧАНИЕ

Все протоколы, использующие IPv6, должны иметь контрольную сумму в своих собственных заголовках. Эти контрольные суммы включают псевдозаголовок (RFC 2460 [27]), куда входит IPv6-адрес отправителя (что отличает IPv6 от всех остальных протоколов, которые обычно реализуются с использованием символьного сокета IPv4). Ядро не заставляет приложение, использующее символьный сокет, выбирать адрес отправителя, но делает это самостоятельно и затем вычисляет и сохраняет контрольную сумму, включающую псевдозаголовок IPv6.

Параметр сокета IPV6_DONTFRAG

Установка этого параметра запрещает автоматическое включение заголовка фрагментации для UDP и символьных сокетов. При этом исходящие пакеты, размер которых превышает MTU исходящего интерфейса, просто сбрасываются. Системный вызов ошибку не возвращает, так как пакет может быть сброшен и на промежуточном маршрутизаторе, если он превысит MTU одной из промежуточных линий. Чтобы получать уведомления об изменении маршрутной MTU, приложению следует включить параметр сокета

IPV6_RECVPATHMTU
(см. раздел 22.9).

Параметр сокета IPV6_NEXTHOP

Этот параметр задает адрес следующего транзитного узла для дейтаграммы в виде структуры адреса сокета. Подробнее о нем рассказывается в разделе 22.8.

Параметр сокета IPV6_PATHMTU

Этот параметр может быть только получен, но не установлен. При его считывании система возвращает текущее значение маршрутной MTU, определенное соответствующим методом (см. раздел 22.9).

Параметр сокета IPV6_RECVDSTOPTS

Установка этого параметра означает, что любые полученные IPv6-параметры получателя должны быть возвращены в качестве вспомогательных данных функцией

recvmsg
. По умолчанию параметр отключен. Мы опишем функции, используемые для создания и обработки этих параметров, в разделе 27.5.

Параметр сокета IPV6_RECVHOPLIMIT

Установка этого параметра определяет, что полученное поле предельного количества транзитных узлов (hop limit field) должно быть возвращено в качестве вспомогательных данных функцией

recvmsg
. По умолчанию параметр отключен. Мы опишем функции, используемые для создания и обработки этого параметра, в разделе 22.8.

ПРИМЕЧАНИЕ

В IPv4 не существует способа определить значение получаемого поля TTL.

Параметр сокета IPV6_RECVHOPOPTS

Установка этого параметра означает, что любые полученные параметры транзитных узлов (hop-by-hop options) IPv6 должны быть возвращены в качестве вспомогательных данных функцией

recvmsg
. По умолчанию параметр отключен. Мы опишем функции, используемые для создания и обработки этого параметра, в разделе 27.5.

Параметр сокета IPV6_RECVPATHMTU

Установка этого параметра означает, что маршрутная MTU должна быть возвращена в качестве вспомогательных данных функцией

recvmsg
, при условии, что ее значение изменилось. Параметр будет описан в разделе 22.9.

Параметр сокета IPV6_RECVPKTINFO

Установка этого параметра означает, что два фрагмента информации о полученной дейтаграмме IPv6 — IPv6-адрес получателя и индекс принимающего интерфейса — должны быть возвращены в качестве вспомогательных данных функцией

recvmsg
. Мы опишем этот параметр в разделе 22.8.

Параметр сокета IPV6_RECVRTHDR

Установка этого параметра означает, что получаемый заголовок маршрутизации IPv6 должен быть возвращен в качестве вспомогательных данных функцией

recvmsg
. По умолчанию этот параметр отключен. Мы опишем функции, которые используются для создания и обработки заголовка маршрутизации IPv6, в разделе 27.6.

Параметр сокета IPV6_RECVTCLASS

Установка этого параметра означает, что функция

recvmsg
должна вернуть сведения о классе трафика полученного сообщения (то есть содержимое полей DSCP и ECN) в качестве внешних данных. По умолчанию параметр отключен. Подробнее о нем будет рассказано в разделе 22.8.

Параметр сокета IPV6_UNICAST_HOPS

Этот параметр аналогичен параметру сокета IPv4

IP_TTL
. Он определяет предельное количество транзитных узлов, заданное по умолчанию для исходящих дейтаграмм, отправляемых через этот сокет. При получении значения этого параметра сокета возвращается предельное количество транзитных узлов, которое ядро будет использовать для сокета. Чтобы определить действительное значение предельного количества транзитных узлов из полученной дейтаграммы IPv6, требуется использовать параметр сокета
IPV6_RECVHOPLIMIT
. Мы устанавливаем этот параметр сокета в нашей программе
traceroute
в листинге 28.15.

Параметр сокета IPV6_USE_MIN_MTU

Установка этого параметра равным 1 указывает на то, что определять маршрутную MTU не следует, а пакеты должны отправляться с минимальным значением MTU для IPv6, что предотвращает их фрагментацию. Если же значение параметра равно 0, определение маршрутной MTU выполняется для всех адресов назначения. Значение -1 (установленное по умолчанию) указывает на необходимость определения маршрутной MTU для направленной передачи, но для многоадресной передачи в этом случае используется минимально возможная MTU. Подробнее об этом параметре рассказывается в разделе 22.9.

Параметр сокета IPV6_V6ONLY

Включение этого параметра для сокета семейства

AF_INET6
ограничивает его использование исключительно протоколом IPv6. По умолчанию параметр отключен, хотя в некоторых системах существует возможность включить его по умолчанию. Взаимодействие по IPv4 и IPv6 через сокеты
AF_INET6
будет описано в разделах 12.2 и 12.3.

Параметры сокета IPV6_XXX

Большинство параметров IPv6, предназначенных для изменения содержимого заголовка, предполагают, что приложение использует сокет UDP и взаимодействует с ядром при помощи функций

recvmsg
и
sendmsg
. Сокет TCP получает и устанавливает значения параметров при помощи специальных функций
getsockopt
и
setsockopt
. Параметр сокета TCP совпадает с типом вспомогательных данных для UDP, а в буфере после вызова функций
getsockopt
и
setsockopt
оказывается та же информация, что и во вспомогательных данных. Подробнее см. раздел 27.7.

7.9. Параметры сокетов TCP

Для сокетов TCP предусмотрены два специальных параметра. Для них необходимо указывать

level
IPPROTO_TCP.

Параметр сокета TCP_MAXSEG

Этот параметр сокета позволяет нам получать или устанавливать максимальный размер сегмента (maximum segment size, MSS) для соединения TCP. Возвращаемое значение — это количество данных, которые наш TCP будет отправлять на другой конец соединения. Часто это значение равно MSS, анонсируемому другим концом соединения в его сегменте SYN, если наш TCP не выбирает меньшее значение, чем объявленный MSS собеседника. Если это значение получено до того, как сокет присоединился, возвращаемым значением будет значение по умолчанию, которое используется в том случае, когда параметр MSS не получен с другого конца соединения. Также помните о том, что значение меньше возвращаемого действительно может использоваться для соединения, если, например, задействуется параметр отметки времени (timestamp), поскольку в каждом сегменте он занимает 12 байт области, отведенной под параметры TCP.

Максимальное количество данных, которые TCP отправляет в каждом сегменте, также может изменяться во время существования соединения, если TCP поддерживает определение транспортной MTU. Если маршрут к собеседнику изменяется, это значение может увеличиваться или уменьшаться.

В табл. 7.2 мы отметили, что этот параметр сокета может быть также установлен приложением. Это возможно не во всех системах: изначально параметр был доступен только для чтения. 4.4BSD позволяет приложению только лишь уменьшать это значение, но мы не можем его увеличивать [128, с. 1023]. Поскольку этот параметр управляет количеством данных, которое TCP посылает в каждом сегменте, имеет смысл запретить приложению увеличивать значение. После установления соединения это значение задается величиной MSS, которую объявил собеседник, и мы не можем превысить его. Однако наш TCP всегда может отправить меньше данных, чем было анонсировано собеседником.

Параметр сокета TCP_NODELAY

Если этот параметр установлен, он отключает алгоритм Нагла (Nagle algorithm) (см. раздел 19.4 [111] и с. 858–859 [128]). По умолчанию этот алгоритм включен.

Назначение алгоритма Нагла — сократить число небольших пакетов в глобальной сети. Согласно этому алгоритму, если у данного соединения имеются неподтвержденные (outstanding) данные (то есть данные, которые отправил наш TCP и подтверждения которых он ждет), то небольшие пакеты не будут отправляться через соединение до тех пор, пока существующие данные не будут подтверждены. Под «небольшим» пакетом понимается любой пакет, меньший MSS. TCP будет по возможности всегда отправлять пакеты нормального размера. Таким образом, назначение алгоритма Нагла — не допустить, чтобы у соединения было множество небольших пакетов, ожидающих подтверждения.

Два типичных генератора небольших пакетов — клиенты Rlogin и Telnet, поскольку обычно они посылают каждое нажатие клавиши в отдельном пакете. В быстрой локальной сети мы обычно не замечаем действия алгоритма Нагла с этими клиентами, потому что время, требуемое для подтверждения небольшого пакета, составляет несколько миллисекунд — намного меньше, чем промежуток между вводом двух последовательных символов. Но в глобальной сети, где для подтверждения небольшого пакета может потребоваться секунда, мы можем заметить задержку в отражении символов, и эта задержка часто увеличивается при включении алгоритма Нагла.

Рассмотрим следующий пример. Мы вводим строку из шести символов

hello!
либо клиенту Rlogin, либо клиенту Telnet, промежуток между вводом символов составляет точно 250 мс. Время обращения к серверу (RTT) составляет 600 мс и сервер немедленно отправляет обратно отражение символа. Мы считаем, что сегмент ACK, подтверждающий получение клиентского символа, отправляется обратно клиенту с отражением символа, а сегменты ACK, которые клиент отправляет для подтверждения приема отраженного сервером символа, мы игнорируем. (Мы поговорим о задержанных сегментах ACK далее.) Считая, что алгоритм Нагла отключен, получаем 12 пакетов, изображенных на рис. 7.7.

Рис. 7.7. Шесть символов, отраженных сервером при отключенном алгоритме Нагла

Каждый символ отправляется в индивидуальном пакете: сегменты данных слева направо, а сегменты ACK справа налево.

Но если алгоритм Нагла включен (по умолчанию), у нас имеется 8 пакетов, показанных на рис. 7.8. Первый символ посылается как пакет, но следующие два символа не отправляются, поскольку у соединения есть небольшой пакет, ожидающий подтверждения. Эти пакеты отправляются, когда прошло 600 мс, то есть когда прибывает сегмент ACK, подтверждающий прием первого пакета, вместе с отражением первого символа. Пока второй пакет не будет подтвержден сегментом ACK в момент времени 1200, не будет отправлен ни один небольшой пакет.

Рис. 7.8. Пакеты, отправляемые при включенном алгоритме Нагла

Алгоритм Нагла часто взаимодействует с другим алгоритмом TCP: алгоритмом задержанного сегмента ACK (delayed ACK). Этот алгоритм заставляет TCP не отправлять сегмент ACK сразу же при получении данных — вместо этого TCP ждет в течение небольшого количества времени (типичное значение 50-200 мс) и только после этого отправляет сегмент ACK. Здесь делается расчет на то, что в течение этого непродолжительного времени появятся данные для отправки собеседнику, и сегмент ACK может быть вложен в пакет с этими данными. Таким образом можно будет сэкономить на одном сегменте TCP. Это обычный случай с клиентами Rlogin и Telnet, поэтому сегмент ACK клиентского символа вкладывается в отражение символа сервером.

Проблема возникает с другими клиентами, серверы которых не генерируют трафика в обратном направлении, в который может быть вложен сегмент ACK. Эти клиенты могут обнаруживать значительные задержки, поскольку TCP клиента не будет посылать никаких данных серверу, пока не истечет время таймера для задержанных сегментов ACK сервера. Таким клиентам нужен способ отключения алгоритма Нагла. Осуществить это позволяет параметр

TCP_NODELAY
.

Другой тип клиента, для которого нежелательно использование алгоритма Нагла и задержанных ACK TCP, — это клиент, отправляющий одиночный логический запрос своему серверу небольшими порциями. Например, будем считать, что клиент отправляет своему серверу 400-байтовый запрос, состоящий из 4 байт, задающих тип запроса, за которыми следуют 396 байт данных. Если клиент выполняет функцию

write
, отправляя 4 байт, и затем функцию
write
, отправляя остальные 396 байт, вторая часть не будет отправлена со стороны клиента, пока TCP сервера не подтвердит получение первых 4 байт. Кроме того, поскольку сервер не может работать с 4 байтами данных, пока не получит оставшиеся 396 байт, TCP сервера задержит сегмент ACK, подтверждающий получение 4 байт данных (то есть не будет данных от сервера клиенту, в которые можно вложить сегмент ACK). Есть три способа решить проблему с таким клиентом.

1. Использовать функцию

writev
(раздел 14.4) вместо двух вызовов функции
write
. Один вызов функции
writev
приводит к отправке только одного сегмента TCP в нашем примере. Это предпочтительное решение.

2. Скопировать 4 байт и 396 байт данных в один буфер и вызвать один раз функцию

write
для этого буфера.

3. Установить параметр сокета

TCP_NODELAY
и продолжать вызывать функцию
write
дважды. Это наименее желательное решение.

Упражнения 7.8 и 7.9 продолжают этот пример.

7.10. Параметры сокетов SCTP

Относительно большое количество параметров, определенных для сокетов SCTP (17 на момент написания этой книги), дают возможность разработчику приложения более точно контролировать его поведение. Параметр

level
для сокетов SCTP должен принимать значение
IPPROTO_SCTP
.

Несколько параметров, используемых для получения сведений об SCTP, требуют передачи данных ядру (например, идентификатора ассоциации или адреса собеседника). Не все реализации

getsockopt
поддерживают передачу данных в обе стороны. Интерфейс сокетов SCTP определяет функцию
sctp_opt_info
(раздел 9.11), которая устраняет эту проблему. В некоторых системах, где
getsockopt
поддерживает передачу данных в ядро, функция
sctp_opt_info
является не более, чем оболочкой для
getsockopt
. В других системах она может вызывать функцию
ioctl
или какую-либо иную, возможно, созданную специально для данного случая. Мы рекомендуем получать параметры сокетов SCTP при помощи
sctp_opt_info
, так как в этом случае обеспечивается максимальная переносимость. В табл. 7.2 соответствующие параметры отмечены знаком «
+
»:
SCTP_ASSOCINFO
,
SCTP_GET_PEER_ADDR_INFO
,
SCTP_PEER_ADDR_PARAMS
,
SCTP_PRIMARY_ADDR
,
SCTP_RTOINFO
и
SCTP_STATUS
.

Параметр сокета SCTP_ADAPTION_LAYER

При инициализации ассоциации любой собеседник может указать на наличие уровня-адаптора. Это указание должно представлять из себя 32-разрядное беззнаковое целое, которое может использоваться двумя приложениями для координации локального уровня-адаптора приложений. Параметр сокета позволяет получать или устанавливать указание на наличие уровня-адаптора, которое будет предоставляться данной конечной точкой будущим собеседникам. Чтобы получить соответствующее значение, установленное собеседником, приложение должно подписаться на события уровня-адаптора.

Параметр сокета SCTP_ASSOCINFO

Параметр сокета

SCTP_ASSOCINFO
выполняет три функции. Во-первых, он позволяет получать сведения о существующей ассоциации. Во-вторых, с его помощью можно изменять параметры существующей ассоциации. Наконец, в-третьих, через этот параметр можно задавать значения по умолчанию для будущих ассоциаций. При получении сведений о существующей ассоциации вместо
getsockopt
следует использовать
sctp_opt_info
. Вместе с параметром при вызове функции указывается структура
sctp_assocparams
:

struct sctp_assocparams {

 sctp_assoc_t sasoc_assoc_id;

 uint16_t sasoc_asocmaxrxt;

 uint16_t sasoc_number_peer_destinations;

 uint32_t sasoc_peer_rwnd;

 uint32_t sasoc_local_rwnd;

 uint32_t sasoc_cookie_life;

};

Поля структуры имеют следующий смысл:

sasoc_assoc_id
хранит идентификатор ассоциации. Если при вызове
setsockopt
параметр установлен в нуль, поля
sasoc_asocmaxrxt
и
sasoc_cookie_life
трактуются как новые значения по умолчанию для сокета. Вызов
getsockopt
вернет сведения об ассоциации, если при вызове указать ее идентификатор; если же поле оставить нулевым, будут возвращены значения по умолчанию;

sasoc_asocmaxrxt
хранит количество повторных передач без получения подтверждений. При превышении этого ограничения передача прекращается, ассоциация закрывается и SCTP сообщает приложению о недоступности собеседника;

sasoc_number_peer_destinations
хранит количество адресов собеседника. Этот параметр может быть только считан, но не установлен;

sasoc_peer_rwnd
хранит текущее рассчитанное окно приема собеседника, то есть количество байтов, которые могут быть переданы в данный момент. Это поле изменяется динамически. Когда приложение отправляет данные, значение поля уменьшается, когда удаленное приложение считывает полученные данные, значение увеличивается. Вызовом данного параметра сокета это значение изменено быть не может;

sasoc_local_rwnd
хранит размер локального окна приема, о котором SCTP оповещает собеседника. Это значение также изменяется динамически и зависит от параметра сокета
SO_SNDBUF
. Вызовом параметра
SCTP_ASSOCINFO
локальное окно изменено быть не может;

sasoc_cookie_life
хранит срок действия
cookie
, выданного собеседнику (в миллисекундах). Каждому
cookie
присваивается определенный срок действия, благодаря чему обеспечивается защита от атак, основанных на повторах. Значение по умолчанию равно 60 000 и может быть изменено установкой нужного значения в данном поле при условии, что в поле
sasoc_assoc_id
записано значение 0.

Рекомендации по настройке

sasoc_asocmaxrxt
для оптимальной производительности приводятся в разделе 23.11. Для защиты от атак, основанных на повторе, значение
sasoc_cookie_life
можно уменьшить, но при этом система окажется менее устойчивой к задержкам в процессе инициации. Прочие поля полезны для отладки программ.

Параметр сокета SCTP_AUTOCLOSE

Этот параметр позволяет получать и устанавливать время автоматического закрытия конечной точки SCTP. Это время задается в секундах и определяет длительность существования ассоциации SCTP, по которой не передаются никакие данные. Передача данных контролируется стеком SCTP. По умолчанию функция автоматического закрытия отключена.

Параметр предназначен для использования на интерфейсах SCTP типа «один-ко-многим» (см. главу 9). Положительное значение соответствует времени поддержания неиспользуемой ассоциации в секундах, а нулевое отключает функцию автоматического закрытия. Установка параметра влияет только на будущие ассоциации, все существующие ассоциации сохраняют старые значения.

Автоматическое закрытие может использоваться сервером для закрытия неиспользуемых ассоциаций без дополнительных затрат на хранение информации о состоянии. Однако разработчик сервера должен тщательно оценить максимальную продолжительность бездействия клиентов. Если значение параметра окажется недостаточно большим, ассоциации будут закрываться слишком рано.

Параметр сокета SCTP_DEFAULT_SEND_PARAM

SCTP поддерживает множество дополнительных параметров отправки, которые обычно передаются в виде вспомогательных данных или используются при вызове функции

sctp_sendmsg
(который часто реализуется как библиотечный вызов, передающий вспомогательные данные пользователя). Приложение, планирующее отправку большого количества сообщений с одинаковыми параметрами, может воспользоваться параметром
SCTP_DEFAULT_SEND_PARAM
для настройки значений параметров по умолчанию и тем самым избавиться от необходимости добавлять вспомогательные данные или вызывать
sctp_sendmsg
. На вход параметра поступает структура
sctp_sndrcvinfo
:

struct sctp_sndrcvinfo {

 u_int16_t sinfo_stream;

 u_int16_t sinfo_ssn;

 u_int16_t sinfo_flags;

 u_int32_t sinfo_ppid;

 u_int32_t sinfo_context;

 u_int32_t sinfo_timetolive;

 u_int32_t sinfo_tsn;

 u_int32_t sinfo_cumtsn;

 sctp_assoc_t sinfo_assoc_id;

};

Поля структуры определяются следующим образом:

sinfo_stream
задает поток, в который по умолчанию направляются все сообщения;

sinfo_ssn
игнорируется при установке значений параметров по умолчанию. При получении сообщений функцией
recvmsg
или
sctp_recvmsg
это поле содержит значение потокового последовательного номера (stream sequence number, SSN), помещенное собеседником в порцию данных;

sinfo_flags
устанавливает значения всех флагов для будущих сообщений. Допустимые значения флагов приводятся в табл. 7.5;

sinfo_ppid
задает значение идентификатора протокола SCTP для всех будущих передач данных;

sinfo_context
задает значение по умолчанию для поля
sinfo_context
, которое является локальной меткой для сообщений, которые не могли быть доставлены собеседнику;

sinfo_timetolive
определяет время жизни отправляемых сообщений. Поле времени жизни используется стеком SCTP для того, чтобы сбрасывать сообщения, задержавшиеся в буфере отправки на слишком большой срок и не переданные ни разу. Если обе конечные точки поддерживают режим частичной надежности, параметр времени жизни влияет и на количество попыток повторной передачи, ограничивая их срок;

sinfo_tsn
игнорируется при установке параметров по умолчанию. При получении сообщений функцией
recvmsg
или
sctp_recvmsg
это поле содержит значение транспортного последовательного номера (transport sequence number, TSN), помещенное собеседником в порцию данных SCTP;

sinfo_cumtsn
игнорируется при установке параметров по умолчанию. При получении сообщений функцией
recvmsg
или
sctp_recvmsg
это поле содержит значение кумулятивного транспортного последовательного номера, вычисленного локальным стеком SCTP для удаленного собеседника;

sinfo
_assoc_id содержит идентификатор ассоциации, для которой требуется установка параметров по умолчанию. Для сокетов типа «один-к-одному» это поле игнорируется.


Таблица 7.5. Допустимые значения флагов SCTP (поле sinfo_flags)

КонстантаОписание
MSG_ABORTВызывает аварийное завершение ассоциации
MSG_ADDR_OVERЗаставляет SCTP использовать указанный адрес вместо адреса по умолчанию
MSG_EOFКорректное завершение ассоциации после отправки сообщения
MSG_PR_BUFFERВключение частичной надежности в зависимости от буфера (если она вообще поддерживается)
MSG_PR_SCTPВключение частичной надежности доставки для данного сообщения (если поддерживается)
MSG_UNORDEREDУказывает, что данное сообщение использует сервис неупорядоченной доставки

Обратите внимание, что значения параметров по умолчанию используются только тогда, когда сообщение отправляется без собственной структуры

sctp_sndrcvinfo
. Если же эта структура добавляется во вспомогательные данные при отправке сообщений, заданные в ней значения имеют приоритет перед значениями по умолчанию. Параметр
SCTP_DEFAULT_SEND_PARAM
может использоваться для получения текущих значений по умолчанию при помощи функции
sctp_opt_info
.

Параметр сокета SCTP_DISАВLE_FRAGМENTS

В обычном режиме работы SCTP фрагментирует все сообщения, не помещающиеся в один пакет SCTP, разбивая их на несколько порций типа DATA. Установка параметра

SCTP_DISABLE_FRAGMENTS
отключает фрагментацию для данного отправителя. Если сообщение требует фрагментации, а фрагментация отключена, SCTP возвращает ошибку
EMSGSIZE
и не отсылает сообщение.

Параметр может использоваться приложениями, которые хотят самостоятельно управлять размерами сообщений, при условии, что любое из этих сообщений может поместиться в IP-пакет. Приложение должно быть готово обработать ошибку, обеспечив фрагментацию на уровне приложения или изменение размера сообщений.

Параметр сокета SCTP_EVENTS

Этот параметр сокета позволяет включать, выключать и определять состояние подписки на различные уведомления SCTP. Уведомление SCTP представляет собой сообщение, отправляемое стеком SCTP приложению. Сообщение считывается как и обычные данные, однако в поле

msg_flags
при вызове функции
recvmsg
должно находиться значение
MSG_NOTIFICATION
. Приложение, не готовое к использованию
recvmsg
или
sctp_recvmsg
, не должно включать подписку на события. Параметр позволяет управлять событиями восьми различных типов и передавать структуру
sctp_event_subscribe
. Нулевое значение соответствует отключению подписки, а единица — включению.

Структура

sctp_event_subscribe
определяется следующим образом:

struct sctp_event_subscribe {

 u_int8_t sctp_data_io_event;

 u_int8_t sctp_association_event;

 u_int8_t sctp_address_event;

 u_int8_t sctp_send_failure_event;

 u_int8_t sctp_peer_error_event;

 u_int8_t sctp_shutdown_event;

 u_int8_t sctp_partial_delivery_event;

 u_int8_t sctp_adaption_layer_event;

};

В табл. 7.6 описано назначение различных событий. Подробнее об уведомлениях вы узнаете в разделе 9.14.


Таблица 7.6. События SCTP

КонстантаОписание
sctp_data_io_eventВключение и отключение доставки sctp_sndrcvinfo с каждым вызовом recvmsg
sctp_association_eventВключение и отключение уведомлений о состоянии ассоциации
sctp_address_eventВключение и отключение уведомлений об адресах
sctp_send_failure_eventВключение и отключение уведомлений об ошибках доставки сообщений
sctp_peer_error_eventВключение и отключение уведомлений об ошибках протокола собеседника
sctp_shutdown_eventВключение и отключение уведомлений о завершении ассоциации
sctp_partial_delivery_eventВключение и отключение уведомлений о частичной доставке
sctp_adaption_layer_eventВключение и отключение уведомлений уровня-адаптера

Параметр сокета SCTP_GET_PEER_ADDR_INFO

Этот параметр позволяет получить информацию о собеседнике, которая включает окно приема, сглаженные значения RTT и MTU. Параметр может быть применен только к конкретному адресу собеседника. Вызывающее приложение заполняет поле

spinfo_address
структуры
sctp_paddrinfo
интересующим его адресом собеседника. Для максимальной переносимости рекомендуется работать с функцией
sctp_opt_info
, а не
getsockopt
. Формат структуры
sctp_paddrinfo
описан ниже:

struct sctp_paddrinfo {

 sctp_assoc_t spinfo_assoc_id;

 struct sockaddr_storage spinfo_address;

 int32_t spinfo_state;

 uint32_t spinfo_cwnd;

 u_int32_t spinfo_srtt;

 u_int32_t spinfo_rto;

 u_int32_t spinfo_mtu;

};

Приложению возвращаются следующие сведения:

spinfo_assoc_id
содержит информацию об идентификаторе ассоциации, которая доставляется также в уведомлении об установке ассоциации (
SCTP_COMM_UP
). Уникальный идентификатор ассоциации может использоваться для обращения к ней в большинстве функций SCTP;

spinfo_address
позволяет приложению указать конкретный адрес собеседника, для которого оно хочет получить сведения. По возвращении из
getsockopt
или
sctp_opt_info
значение структуры должно оставаться неизменным;

spinfo_state
может содержать одно или несколько значений (табл. 7.7).


Таблица 7.7. Состояния адреса собеседника SCTP

КонстантаОписание
SCTP_ACTIVEАдрес активен и доступен
SCTP_INACTIVEВ настоящий момент адрес недоступен
SCTP_ADDR_UNCONFIRMEDДоставка данных или проверочных сообщений на данный адрес не была подтверждена

Неподтвержденным считается адрес, перечисленный собеседником в списке действующих, но не проверенный локальным SCTP. Для проверки адреса требуется, чтобы отправленные на него данные или проверочные сообщения были подтверждены. Для непроверенного адреса не может быть указано корректное значение тайм-аута повторной передачи (RTO). Активными считаются адреса, доступные для передачи данных.

spinfo_cwnd
хранит текущий размер окна приема для данного адреса. Описание процедуры расчета параметра cwnd приводится в [117, с. 177];

spinfo_srtt
хранит текущую оценку сглаженного RTT для данного адреса;

spinfo_rto
хранит текущее значение тайм-аута повторной передачи для данного адреса;

spinfo_mtu
хранит текущую транспортную MTU, определенную по соответствующему алгоритму.

Параметр полезно использовать для получения идентификатора ассоциации по структуре с IP-адресом собеседника. Это будет продемонстрировано в главе 23. Кроме того, приложение может отслеживать функционирование всех адресов собеседника с несколькими интерфейсами и выбирать лучший из них в качестве адреса по умолчанию. Наконец, все эти сведения полезны для ведения журналов и отладки.

Параметр сокета SCTP_I_WANT_MAPPED_V4_ADDR

Этот флаг позволяет включать и отключать отображение адресов IPv4 для сокетов типа

AF_INET6
. Если параметр включен (а по умолчанию это именно так), все адреса IPv4 преобразуются в адреса IPv6 перед отправкой приложению. Если же параметр отключен, сокет SCTP не будет отображать адреса IPv4, а вместо этого будет просто передавать их в структуре
sockaddr_in
.

Параметр сокета SCTP_INITMSG

Параметр позволяет устанавливать и считывать параметры инициализации, по умолчанию применяемые к сокетам при отправке сообщения INIT. Вместе с параметром передается структура

sctp_initmsg
, определяемая следующим образом:

struct sctp_initmsg {

 uint16_t sinit_num_ostreams;

 uint16_t sinit_max_instreams;

 uint16_t sinit_max_attempts;

 uint16_t sinit_max_init_timeo;

};

Поля структуры определяются следующим образом:

sinit_num_ostreams
содержит количество исходящих потоков SCTP, запрашиваемое приложением. Это значение не подтверждается, пока не будет завершено рукопожатие, и может быть уменьшено в соответствии с возможностями собеседника;

sinit_max_instreams
отражает максимальное количество входящих потоков, которое готово обеспечить приложение. Это значение может быть перекрыто стеком SCTP, если оно превышает максимальное количество потоков, поддерживаемое самим стеком;

sinit_max_attempts
выражает количество попыток передачи начального сообщения INIT перед тем, как собеседник будет признан недоступным;

sinit_max_init_timeo
задает максимальный тайм-аут повторной передачи для сообщений INIT. Это значение используется вместо
RTO_MAX
в качестве ограничения сверху на тайм-аут повторной передачи. Выражается в миллисекундах.

Обратите внимание, что установленные в 0 поля структуры игнорируются сокетом SCTP. При использовании сокета типа «один-ко-многим» (см. раздел 9.2) приложение может передать структуру

sctp_initmsg
во вспомогательных данных при неявной установке ассоциации.

Параметр сокета SCTP_MAXBURST

Этот параметр позволяет приложению устанавливать и считывать максимальный размер набора пакетов (maximum burst size). SCTP никогда не отправляет более, чем

SCTP_MAXBURST
пакетов одновременно, что предотвращает переполнение сети. Ограничение может применяться либо путем уменьшения окна до текущего количества пакетов «в пути» (in flight) плюс максимальный размер набора, помноженный на транспортную MTU, либо в качестве отдельного параметра, если при каждой возможности отправки будет пересылаться не более
SCTP_MAXBURST
пакетов.

Параметр сокета SCTP_MAXSEG

Параметр позволяет приложению считывать и устанавливать максимальный размер фрагмента, аналогично

TCP_MAXSEG
(см. раздел 7.8).

Когда стек SCTP получает от приложения-отправителя сообщение, размер которого превышает значение этого параметра, это сообщение разбивается на несколько фрагментов, которые доставляются на вторую конечную точку по отдельности. Обычно SCTP создает фрагменты такого размера, чтобы они не превышали минимальную MTU для всех адресов собеседника. Параметр позволяет еще сильнее уменьшить это значение. Учтите, что стек SCTP может фрагментировать даже такое сообщение, размер которого не превышает

SCTP_MAXSEG
. Это произойдет в том случае, если MTU для одного из адресов собеседника окажется меньше значения
SCTP_MAXSEG
.

Параметр действует для всех адресов конечной точки и может влиять на несколько ассоциаций при работе с интерфейсами типа «один-ко-многим».

Параметр сокета SCTP_NODELAY

Установка параметра отключает алгоритм Нагла протокола SCTP. По умолчанию параметр выключен, то есть алгоритм Нагла включен. С протоколом SCTP этот алгоритм работает так же, как и с TCP, за тем исключением, что он пытается объединять порции данных, а не отдельные байты. Подробнее см. описание параметра

TCP_NODELAY
.

Параметр сокета SCTP_PEER_ADDR_PARAMS

Параметр позволяет приложению считывать и устанавливать различные параметры ассоциации. Приложение должно заполнить поле идентификатора ассоциации в структуре

sctp_paddrparams
и передать ее вместе с параметром сокета. Формат структуры приведен ниже:

struct sctp_paddrparams {

 sctp_assoc_t spp_assoc_id;

 struct sockaddr_storage spp_address;

 u_int32_t spp_hbinterval;

 u_int16_t spp_pathmaxrxt;

};

Поля структуры имеют следующий смысл:

spp_assoc_id
содержит идентификатор ассоциации, параметры которой считываются или устанавливаются. Если это значение равно нулю, приложение будет работать с параметрами по умолчанию, а не с конкретной ассоциацией;

spp_address
указывает IP-адрес, для которого запрашиваются или устанавливаются параметры. Если значение поля равно нулю, оно игнорируется;

spp_hbinterval
задает интервал между проверочными сообщениями (heartbeats). Значение
SCTP_NO_HB
отключает проверочные сообщения. Значение
SCTP_ISSUE_HB
приводит к внеочередной отправке проверочного сообщения. Все остальные значения задают интервал проверки в миллисекундах. При установке параметров по умолчанию задание константы
SCTP_ISSUE_HB
не допускается;

spp_hbpathmaxrxt
определяет максимальное количество повторных передач, после которых адресат считается недоступным (
INACTIVE
). Если основной адрес собеседника признается недоступным, в качестве нового основного адреса выбирается один из доступных адресов.

Параметр сокета SCTP_PRIMARY_ADDR

Параметр позволяет узнать или установить адрес, используемый локальной конечной точкой SCTP в качестве основного. Основной адрес используется в качестве адреса назначения во всех сообщениях, передаваемых собеседнику. Приложение должно заполнить структуру

sctp_setprim
идентификатором ассоциации и адресом собеседника.

struct sctp_setprim {

 sctp_assoc_t ssp_assoc_id;

 struct sockaddr_storage ssp_addr;

};

Поля структуры имеют следующий смысл:

ssp_assoc_id
указывает идентификатор ассоциации, для которой следует установить или считать основной адрес. В случае сокета типа «один-к-одному» это поле игнорируется;

ssp_addr
определяет основной адрес, который обязательно должен принадлежать собеседнику. Если используется функция
setsockopt
, значение поля трактуется как новый основной адрес собеседника.

Получение значения этого параметра для сокета типа «один-к-одному» с единственным локальным адресом эквивалентно вызову функции

getsockname
.

Параметр сокета SCTP_RTOINFO

Параметр используется для считывания и установки различных тайм-аутов для конкретной ассоциации или используемых по умолчанию для конечной точки. Для считывания параметров по соображениям переносимости следует использовать функцию

sctp_opt_info
, а не
getsockopt
. Перед вызовом необходимо заполнить структуру
sctp_rtoinfo
, которая определяется следующим образом:

struct sctp_rtoinfo {

 sctp_assoc_t srto_assoc_id;

 uint32_t srto_initial;

 uint32_t srto_max;

 uint32_t srto_min;

};

Поля структуры имеют следующий смысл:

srto_assoc_id
содержит либо идентификатор конкретной ассоциации, либо 0. В последнем случае работа осуществляется со значениями по умолчанию;

srto_initial
хранит начальное значение RTO для конкретного адреса собеседника. Это значение используется при отправке порции INIT. Измеряется поле в миллисекундах и по умолчанию равно 3000;

srto_max
содержит максимальное значение RTO, используемое при изменении таймера повторной передачи. Если рассчитанное значение оказывается больше максимального RTO, в качестве нового тайм-аута используется именно максимальное значение. По умолчанию это поле имеет значение 60 000 мс;

srto_min
содержит минимальное значение RTO, используемое при первом запуске таймера повторной передачи. Когда таймер RTO изменяется, новое значение обязательно сравнивается с минимальным. По умолчанию это поле имеет значение 1000 мс.

Запись 0 в поля

srto_initial
,
srto_max
и
srto_min
означает, что менять текущие параметры по умолчанию не требуется. Все значения измеряются в миллисекундах. Руководство по установке таймеров для достижения максимальной производительности приводится в разделе 23.11.

Параметр сокета SCTP_SET_PEER_PRIMARY_ADDR

Установка этого параметра приводит к отправке собеседнику сообщения, запрашивающего установку конкретного локального адреса в качестве основного. Процесс должен заполнить структуру

sctp_setpeerprim
и указать в ней идентификатор ассоциации и локальный адрес, который должен быть сделан основным. Этот адрес должен быть привязан к данной конечной точке. Структура
sctp_setpeerprim
определяется следующим образом:

struct sctp_setpeerprim {

 sctp_assoc_t sspp_assoc_id;

 struct sockaddr_storage sspp_addr;

};

Ниже приводится описание полей структуры.

sspp_assoc_id
указывает идентификатор ассоциации, для которой требуется установить новый основной адрес. При работе с сокетом типа «один-к-одному» это поле игнорируется;

sspp_addr
содержит локальный адрес, который должен использоваться собеседником в качестве основного.

Поддержка этой функции SCTP не является обязательной. Если локальная конечная точка не поддерживает параметр, процессу будет возвращена ошибка EOPNOTSUPP. Если же параметр не поддерживается удаленной конечной точкой, ошибка будет другой: EINVAL. Обратите внимание, что данный параметр не может использоваться для считывания основного адреса; он служит только для установки нового адреса в качестве основного.

Параметр сокета SCTP_STATUS

Этот параметр сокета служит для получения информации о текущем статусе ассоциации SCTP. Для обеспечения максимальной переносимости пользуйтесь функцией

sctp_opt_info
, а не
getaddrinfo
. Приложение должно предоставить структуру
sctp_status
, указав идентификатор ассоциации
sstat_assoc_id
. Структура будет заполнена информацией о выбранной ассоциации и возвращена приложению. Формат структуры
sctp_status
таков:

struct sctp_status {

 sctp_assoc_t sstat_assoc_id;

 int32_t sstat_state;

 u_int32_t sstat_rwnd;

 u_int16_t sstat_unackdata;

 u_int16_t sstat_penddata;

 u_int16_t sstat_instrms;

 u_int16_t sstat_outstrms;

 u_int32_t sstat_fragmentation_point;

 struct sctp_paddrinfo sstat_primary;

};

Поля структуры имеют следующий смысл:

sstat_assoc_id
содержит идентификатор ассоциации;

sstat_state
содержит константу, обозначающую состояние ассоциации (табл. 7.8). Подробное описание состояний конечной точки SCTP, чередующихся при установке и завершении ассоциации, приводится на рис. 2.8;

sstat_rwnd
содержит текущее вычисленное значение приемного окна собеседника;

sstat_unackdata
содержит количество неподтвержденных порций данных, ждущих ответа собеседника;

sstat_penddata
содержит количество непрочитанных порций данных, подготовленных локальной конечной точкой SCTP для приложения;

sstat_instrms
содержит количество потоков, используемых собеседником для передачи данных на данную конечную точку;

sstat_outstrms
содержит количество потоков, по которым данная конечная точка может передавать данные собеседнику;

sstat_fragmentation_point
содержит текущее значение границы фрагментации пользовательских сообщений, используемое локальной конечной точкой SCTP. Это значение обычно равняется минимальной MTU для всех адресатов или еще меньшей величине, установленной при помощи параметра
SCTP_MAXSEG
;

sstat_primary
содержит текущий основной адрес. Основной адрес используется по умолчанию для отправки данных собеседнику.


Таблица 7.8. Состояния SCTP

КонстантаОписание
SCTP_CLOSEDАссоциация закрыта
SCTP_COOKIE_WAITАссоциация отправила пакет INIT
SCTP_COOKIE_ECHOEDАссоциация отправила эхо-ответ cookie
SCTP_ESTABLISHEDАссоциация установлена
SCTP_SHUTDOWN_PENDINGАссоциация ждет отправки сообщения о завершении
SCTP_SHUTDOWN_SENTАссоциация отправила сообщение о завершении
SCTP_SHUTDOWN_RECEIVEDАссоциация получила сообщение о завершении
SCTP_SHUTDOWN_ACK_SENTАссоциация ждет пакета SHUTDOWN-COMPLETE

Эти параметры полезны для диагностики соединения и определения характеристик текущего сеанса. Например, функция

sctp_get_no_strms
в разделе 10.2 будет считывать
sstat_outstrms
для определения количества доступных для отправки данных потоков. Низкое значение
sstat_rwnd
или высокое значение
sstat_unackdata
позволяет сделать вывод о заполнении приемного буфера собеседника, так что приложение может вовремя замедлить передачу данных. Поле
sstat_fragmentation_point
может использоваться некоторыми приложениями для уменьшения количества пакетов, создаваемых SCTP, путем уменьшения размеров сообщений.

7.11. Функция fcntl

Сокращение

fcntl
означает «управление файлами» (file control). Эта функция выполняет различные операции управления дескрипторами. Перед описанием этой функции и ее влияния на сокет нам нужно составить некоторое более общее представление о ее возможностях. В табл. 7.9 приводятся различные операции, выполняемые функциями
fcntl
и
ioctl
и маршрутизирующими сокетами.


Таблица 7.9. Операции функций fcntl и ioctl и маршрутизирующих сокетов

ОперацияfcntlioctlМаршрутизирующий сокетPosix.1g
Установка сокета для неблокируемого ввода-выводаF_SETFL, O_NONBLOCKFIONBIOfcntl
Установка сокета для ввода-вывода, управляемого сигналомF_SETFL, O_ASYNCFIOASYNCfcntl
Установка владельца сокетаF_SETOWNSIOCSPGRP или FIOSETOWNfcntl
Получение владельца сокетаF_GETOWNSIOCGPGRP или FIOGETOWNfcntl
Получение текущего количества байтов в приемном буфере сокетаFIONREAD
Проверка, находится ли процесс на отметке внеполосных данныхSIOCATMARKsockatmark
Получение списка интерфейсовSIOCGIFCONFSysctl
Операции интерфейсовSIOC[GS]IFxxx
Кэш-операции ARPSIOCxARPRTM_xxx
Операции таблицы маршрутизацииSIOGxxxRTRTM_xxx

Первые шесть операций могут применяться к сокетам любым процессом, следующие две (операции над интерфейсами) используются реже, а последние две (ARP и таблица маршрутизации) выполняются администрирующими программами, такими как

ifconfig
и
route
. О различных операциях функции
ioctl
мы поговорим подробнее в главе 17, а о маршрутизирующих сокетах — в главе 18.

Существует множество способов выполнения первых четырех операций, но, как указано в последней колонке, стандарт POSIX определяет, что функция

fcntl
является предпочтительным способом. Отметим также, что POSIX предлагает функцию
sockatmark
(см. раздел 24.3) как наиболее предпочтительный способ тестирования на предмет пребывания процесса на отметке внеполосных данных. Оставшиеся операции с пустой последней колонкой не стандартизованы POSIX.

ПРИМЕЧАНИЕ

Отметим также, что первые две операции, устанавливающие сокет для неблокируемого ввода-вывода и для ввода-вывода, управляемого сигналом, традиционно применялись с использованием команд FNDELAY и FASYNC функции fcntl. POSIX определяет константы О_xxx.

Функция

fcntl
предоставляет следующие возможности, относящиеся к сетевому программированию:

■ Неблокируемый ввод-вывод. Мы можем установить флаг состояния файла

O_NONBLOCK
, используя команду
F_SETFL
для отключения блокировки сокета. Неблокируемый ввод-вывод мы описываем в главе 16.

■ Управляемый сигналом ввод-вывод. Мы можем установить флаг состояния файла

O_ASYNC
, используя команду
F_SETFL
, после чего при изменении состояния сокета будет генерироваться сигнал SIGIO. Мы рассмотрим это в главе 25.

■ Команда

F_SETOWN
позволяет нам установить владельца сокета (идентификатор процесса или идентификатор группы процессов), который будет получать сигналы
SIGIO
и
SIGURG
. Первый сигнал генерируется, если для сокета включен управляемый сигналом ввод-вывод (см. главу 25), второй — когда для сокета приходят новые внеполосные (out-of-band data) данные (см. главу 24). Команда
F_GETOWN
возвращает текущего владельца сокета.

ПРИМЕЧАНИЕ

Термин «владелец сокета» определяется POSIX. Исторически реализации, происходящие от Беркли, называли его «идентификатор группы процессов сокета», потому что переменная, хранящая этот идентификатор, — это элемент so_pgid структуры socket [128, с. 438].

#include 


int fcntl(int fd, int cmd, ... /* int arg */);

Возвращает: в случае успешного выполнения результат зависит от аргумента cmd, -1 в случае ошибки

Каждый дескриптор (включая сокет) имеет набор флагов, которые можно получить с помощью команды

F_GETFL
и установить с помощью команды
F_SETFL
. На сокет влияют следующие два флага:

O_NONBLOCK
— неблокируемый ввод-вывод;

O_ASYNC
— ввод-вывод, управляемый сигналом.

Позже мы опишем оба эти флага подробнее. Отметим, что типичный код, который устанавливает неблокируемый ввод-вывод с использованием функции

fcntl
, выглядит следующим образом:

int flags;


/* Делаем сокет неблокируемым */

if ((flags = fcntl(fd, F_GETFL, 0)) < 0)

 err_sys("F_GETFL error");

flags |= O_NONBLOCK;

if (fcntl(fd, F_SETFL, flags) < 0)

 err_sys("F_SETFL error");

Учтите, что вам может встретиться код, который просто устанавливает желаемый флаг:

/* Неправильный способ сделать сокет неблокируемым */

if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)

 err_sys("F_SETFL error");

Хотя при этом и устанавливается флаг отключения блокировки, также снимаются все остальные флаги состояния файла. Единственный корректный способ установить один из этих флагов состояния файла — получить текущие флаги, с помощью операции логического ИЛИ добавить новый флаг, а затем установить флаги.

Следующий код сбрасывает флаг отключения блокировки в предположении, что переменная

flags
была задана с помощью вызова функции
fcntl
, показанного ранее:

flags &= ~O_NONBLOCK;

if (fcntl(fd, F_SETFL, flags) < 0)

 err_sys("F_SETFL error");

Сигналы

SIGIO
и
SIGURG
отличаются от других тем, что они генерируются для сокета, только если сокету был присвоен владелец с помощью команды
F_SETOWN
. Целое значение аргумента
arg
для команды
F_SETOWN
может быть либо положительным, задающим идентификатор процесса, получающего сигнал, либо отрицательным, абсолютное значение которого — это идентификатор группы процессов, получающей сигнал. Команда
F_GETOWN
возвращает владельца сокета, так как возвращаемое значение функции
fcntl
— либо идентификатор процесса (положительное возвращаемое значение), либо идентификатор группы процессов (отрицательное значение, отличное от -1). Разница между заданием процесса и группы процессов, получающих сигнал, в том, что в первом случае сигнал будет получен только одиночным процессом, тогда как во втором случае его получают все процессы в группе.

Когда создается новый сокет с помощью функции socket, у него нет владельца. Сокет, создаваемый из прослушиваемого сокета, наследует от него принадлежность владельцу (как и многие другие параметры сокетов [128, с. 462-463].

7.12. Резюме

Параметры сокетов лежат в широком диапазоне от очень общих (

SO_ERROR
) до очень специфических (параметры заголовка IP). Наиболее общеупотребительные параметры сокетов, которые нам могут встретиться, — это
SO_KEEPALIVE
,
SO_RCVBUF
,
SO_SNDBUF
и
SO_REUSEADDR
. Последний должен всегда задаваться для сервера TCP до того, как сервер вызовет функцию
bind
(см. листинг 11.6). Параметр
SO_BROADCAST
и десять параметров сокетов многоадресной передачи предназначены только для приложений, передающих соответственно широковещательные или многоадресные сообщения.

Параметр сокета

SO_KEEPALIVE
устанавливается многими серверами TCP и автоматически закрывает наполовину открытое соединение. Замечательное свойство этого параметра в том, что он обрабатывается на уровне TCP, не требуя на уровне приложения наличия таймера, измеряющего период отсутствия активности. Однако недостаток этого параметра в том, что он не видит разницы между выходом собеседника из строя и временной потерей соединения с ним. SCTP предоставляет 17 параметров сокетов, с помощью которых приложение может управлять транспортным уровнем.
SCTP_NODELAY
и
SCTP_MAXSEG
аналогичны
TCP_NODELAY
и
TCP_MAXSEG
, и выполняют схожие функции. Остальные 17 параметров позволяют приложению более точно контролировать поведение стека SCTP. Большинство этих параметров будет рассмотрено в главе 23.

Параметр сокета

SO_LINGER
расширяет наши возможности в отношении контроля над функцией
close
 — мы можем отложить ее завершение на некоторое время. Кроме того, этот параметр позволяет нам отправить сегмент RST вместо обычной последовательности из четырех пакетов, завершающих соединение TCP. Следует соблюдать осторожность при отправке сегментов RST, поскольку в этом случае не наступает состояние TCP TIME_WAIT. Бывает, что этот параметр сокета не обеспечивает необходимой нам информации, и тогда требуется реализовать подтверждение на уровне приложения.

У каждого сокета TCP имеется буфер отправки и буфер приема, а у каждого сокета UDP есть буфер приема. Параметры сокета

SO_SNDBUF
и
SO_RCVBUF
позволяют нам изменять размеры этих буферов. Основное применение эти функции находят при передаче большого количества данных по каналам с повышенной пропускной способностью, которые представляют собой соединения TCP либо с широкой полосой пропускания, либо с большой задержкой, часто с использованием расширений из RFC 1323. Сокеты UDP, наоборот, могут стремиться увеличить размер приемного буфера, чтобы позволить ядру установить в очередь больше дейтаграмм, если приложение занято.

Упражнения

1. Напишите программу, которая выводит заданные по умолчанию размеры буферов отправки и приема TCP, UDP и SCTP, и запустите ее в системе, к которой у вас имеется доступ.

2. Измените листинг 1.1 следующим образом. Перед вызовом функции connect вызовите функцию

getsockopt
, чтобы получить размер приемного буфера сокета и MSS. Выведите оба значения. После успешного завершения функции извлеките значения тех же двух параметров сокета и выведите их. Изменились ли значения? Почему? Запустите программу, соединяющуюся с сервером в вашей локальной сети, и программу, соединяющуюся с сервером в удаленной сети. Изменяется ли MSS? Почему? Запустите также программу на разных узлах, к которым у вас есть доступ.

3. Запустите наш сервер TCP, приведенный в листингах 5.1 и 5.2, и наш клиент из листингов 5.3 и 5.4. Измените функцию

main
клиента, чтобы установить параметр сокета
SO_LINGER
перед вызовом функции
exit
, задав
l_onoff
равным 1, а
l_linger
 — равным 0. Запустите сервер, а затем запустите клиент. Введите строку или две на стороне клиента для проверки работоспособности, а затем завершите работу клиента, введя символ конца файла. Что происходит? После завершения работы клиента запустите программу
netstat
на узле клиента и посмотрите, проходит ли сокет через состояние TIME_WAIT.

4. Будем считать, что два клиента TCP запускаются одновременно. Оба устанавливают параметр сокета

SO_REUSEADDR
и затем с помощью функции
bind
связываются с одним и тем же локальным IP-адресом и одним и тем же локальным портом (допустим, 1500). Но один из клиентов соединяется с помощью функции connect с адресом 198.69.10.2, порт 7000, а второй — с адресом 198.69.10.2 (тот же IP-адрес собеседника), порт 8000. Опишите возникающую ситуацию гонок.

5. Получите исходный код для примеров в этой книге (см. предисловие) и откомпилируйте программу sock (см. раздел В.3). Сначала классифицируйте свой узел как узел, не поддерживающий многоадресную передачу, затем — как поддерживающий многоадресную передачу, но не поддерживающий параметр

SO_REUSEPORT
, и наконец, как узел, поддерживающий многоадресную передачу с предоставлением параметра
SO_REUSEPORT
. Попытайтесь запустить несколько экземпляров программы sock в качестве сервера TCP (параметр
-s
командной строки) на одном и том же порте, связывая универсальный адрес, один из адресов интерфейсов вашего узла и адрес закольцовки (loopback address). Нужно ли вам задавать параметр
SO_REUSEADDR
(параметр
командной строки)? Используйте программу
netstat
для просмотра прослушиваемых сокетов.

6. Продолжайте предыдущий пример, но запустите сервер UDP (параметр

-u
командной строки) и попытайтесь запустить два экземпляра, связанные с одними и теми же локальным IP-адресом и портом. Если ваша реализация поддерживает параметр
SO_REUSEPORT
, попытайтесь использовать ее (параметр
-T
командной строки).

7. Многие версии утилиты

ping
имеют флаг
-d
, задающий параметр сокета
SO_DEBUG
. В чем его назначение?

8. Продолжая пример в конце нашего обсуждения параметра сокета

TCP_NODELAY
, предположим, что клиент выполняет две операции записи с помощью функции
write
: первую для 4 байт данных и вторую для 396 байт. Также будем считать, что время задержки ACK — 100 мс, период RTT между клиентом и сервером равен 100 мс, а время обработки сервером каждого клиентского запроса — 50 мс. Нарисуйте временную диаграмму, показывающую взаимодействие алгоритма Нагла с задержанными сегментами ACK.

9. Снова выполните предыдущее упражнение, считая, что установлен параметр сокета

TCP_NODELAY
.

10. Снова выполните упражнение 8, считая, что процесс вызывает функцию

writev
один раз для обоих буферов (4-байтового и 396-байтового).

11. Прочтите RFC 1122 [10], чтобы определить рекомендуемый интервал для задержанных сегментов ACK.

12. В какой из версий наш сервер тратит больше времени — в листинге 5.1 или 5.2? Что происходит, если сервер устанавливает параметр сокета

SO_KEEPALIVE
, через соединение не происходит обмена данными, узел клиента выходит из строя и не перезагружается?

13. В какой из версий наш клиент тратит больше времени — в листинге 5.3 или 5.4? Что происходит, если клиент устанавливает параметр сокета

SO_KEEPALIVE
, через соединение не происходит обмена данными и узел сервера выходит из строя и не перезагружается?

14. В какой из версий наш клиент тратит больше времени — в листинге 5.3 или 6.2? Что происходит, если клиент устанавливает параметр сокета

SO_KEEPALIVE
, через соединение не происходит обмена данными и узел сервера выходит из строя и не перезагружается?

15. Будем считать, что и клиент, и сервер устанавливают параметр сокета

SO_KEEPALIVE
. Между собеседниками поддерживается соединение, но через это соединение не происходит обмена данными между приложениями. Когда проходят условленные 2 ч и требуется проверить наличие связи, сколькими сегментами TCP обмениваются собеседники?

16. Почти все реализации определяют константу

SO_ACCEPTCONN
в заголовочном файле
, но мы не описывали этот параметр. Прочтите [69], чтобы понять, зачем этот параметр существует.

Глава 8