IP pukaki.60391 > tekapo.55555:. ack 1 win 5840
Указанными тремя сегментами являются SYN, SYN/ACK и ACK, применяемые для трехэтапного согласования (см. рис. 57.5).
В следующей сессии клиент отправляет серверу два сообщения размером 16 и 32 бита, на каждое из которых сервер отвечает 4-байтным сообщением:
IP pukaki.60391 > tekapo.55555: P 1:17(16) ack 1 win 5840
IP tekapo.55555 > pukaki.60391:. ack 17 win 1448
IP tekapo.55555 > pukaki.60391: P 1:5(4) ack 17 win 1448
IP pukaki.60391 > tekapo.55555:. ack 5 win 5840
IP pukaki.60391 > tekapo.55555: P 17:49(32) ack 5 win 5840
IP tekapo.55555 > pukaki.60391:. ack 49 win 1448
IP tekapo.55555 > pukaki.60391: P 5:9(4) ack 49 win 1448
IP pukaki.60391 > tekapo.55555:. ack 9 win 5840
Для каждого сегмента с данными в противоположном направлении передается подтверждение ACK.
В конце мы выводим сегменты, обмен которыми происходит во время разрыва соединения (сначала закрывается клиентский сокет, а затем серверный):
IP pukaki.60391 > tekapo.55555: F 49:49(0) ack 9 win 5840
IP tekapo.55555 > pukaki.60391:. ack 50 win 1448
IP tekapo.55555 > pukaki.60391: F 9:9(0) ack 50 win 1448
IP pukaki.60391 > tekapo.55555:. ack 10 win 5840
В представленном выше выводе показано, что во время разрыва соединения было передано четыре сегмента (см. рис. 57.6).
Параметры сокета влияют на различные аспекты его работы. В этой книге мы опишем всего лишь несколько из них. В издании [Stevens et al., 2004] содержится углубленное описание большинства стандартных параметров сокета. Дополнительные подробности, относящиеся к Linux, см. на страницах tcp(7), udp(7), ip(7), socket(7) и unix(7) руководства.
Чтобы задать и извлечь параметры сокета, можно использовать системные вызовы setsockopt() и getsockopt().
#include
int getsockopt(int
socklen_t *
int setsockopt(int
socklen_t
Оба вызова возвращают 0 при успешном завершении или -1 при ошибке
В обоих вызовах, приведенных выше, аргумент sockfd является файловым дескриптором, ссылающимся на сокет.
Аргумент level определяет протокол, к которому применяется параметр сокета, например IP или TCP. Для большинства параметров, описанных в нашей книге, данный аргумент должен быть равен SOL_SOCKET; это значит, что параметр действует на уровне программного интерфейса сокета.
Аргумент optname обозначает имя параметра, чье значение мы хотим установить или получить. Аргумент optval является указателем на буфер, в котором возвращается значение параметра; это могут быть целое число или структура в зависимости от того, какой параметр указан.
Аргумент optlen хранит размер буфера (в байтах), на который указывает optval. В вызов setsockopt() данный аргумент передается по значению. В вызове getsockopt() он используется для возвращения результата. Его следует предварительно инициализировать с помощью размера вышеупомянутого буфера; во время возвращения ему присваивается количество байтов, записанных в этот буфер.
Как отмечается в разделе 57.11, файловый дескриптор сокета, возвращаемый вызовом accept(), наследует от слушающего сокета значения его установленных параметров.
Параметры сокета привязываются к дескриптору открытого файла (см. рис. 5.2). Это значит, что дескрипторы, продублированные в результате вызова dup() (или ему подобного) либо fork(), будут иметь одинаковые параметры сокета.
В качестве примера воспользуемся параметром SO_TYPE, который позволяет узнать тип сокета:
int optval;
socklen_t optlen;
optlen = sizeof(optval);
if (getsockopt(sfd, SOL_SOCKET, SO_TYPE, &optval, &optlen) == -1)
errExit("getsockopt");
После данного вызова аргумент optval будет содержать тип сокета, допустим, SOCK_STREAM или SOCK_DGRAM. Это может пригодиться в программах, которые наследуют файловый дескриптор на время выполнения exec() (например, если они запущены с помощью inetd) и не знают, с сокетом какого типа имеют дело.
Параметр SO_TYPE является одним из тех, что доступны только для чтения. Их значение нельзя изменить, задействуя вызов setsockopt().
Параметр сокета SO_REUSEADDR имеет сразу несколько назначений (см. главу 7 книги [Stevens et al., 2004]). Но нас интересует только одно из них, довольно распространенное: предотвращение ошибки EADDRINUSE («этот адрес уже занят»), когда после перезапуска TCP-сервер привязывает сокет к порту, уже используемому другим TCP-соединением. Существует два сценария, в которых это обычно происходит.
• Предыдущий экземпляр сервера, подключенный к клиенту, выполнил активное закрытие — либо с помощью вызова close(), либо в результате сбоя (например, если был завершен по сигналу). В этом случае один из концов соединения остается в состоянии TIME_WAIT до истечения времени ожидания длиной 2MSL.