Если в потоковом сокете включить параметр TCP_CORK, то весь его вывод будет буферизироваться в одном TCP-сегменте, пока не произойдет одно из следующих событий: достижение максимального размера сегмента, выключение параметра TCP_CORK, закрытие сокета или завершение интервала в 200 миллисекунд с момента записи первого «закупоренного» байта. (Использование времени ожидания гарантирует, что данные будут переданы, даже если приложение забудет отключить TCP_CORK.)
Для включения и отключения параметра TCP_CORK служит системный вызов setsockopt() (см. раздел 57.9). Применение этого режима продемонстрировано на примере следующего кода, в котором реализован наш гипотетический HTTP-сервер (мы намеренно опустили проверку ошибок):
int optval;
/* Включаем TCP_CORK для 'sockfd' — последующий TCP-вывод закупоривается,
пока этот параметр не будет выключен. */
optval = 1;
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, sizeof(optval));
write(sockfd…); /* Записываем HTTP-заголовки */
sendfile(sockfd…); /* Отправляем содержимое страницы */
/* Отключаем TCP_CORK для 'sockfd' — закупоренный вывод начинает
передаваться в виде единого TCP-сегмента. */
optval = 0
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, sizeof(optval));
Мы могли бы избежать потенциальной передачи двух сегментов, создав в нашем приложении единый буфер данных, который можно было бы передавать с помощью одного вызова write(). (Как вариант, можно было бы воспользоваться вызовом writev(), чтобы объединить два разных буфера в одну групповую операцию.) Но если мы хотим выполнять передачу с нулевым копированием, которую обеспечивает вызов sendfile(), в сочетании с включением заголовка в первый сегмент передаваемого файла, то для этого нужно использовать параметр TCP_CORK.
В разделе 57.3 мы отмечали: флаг MSG_MORE обеспечивает похожее на TCP_CORK поведение, но для отдельных системных вызовов. Это не всегда является преимуществом. Мы можем включить для сокета параметр TCP_CORK и затем запустить программу, которая направляет вывод в унаследованный файловый дескриптор, не имея никакого представления о том, что использует данный параметр. Для сравнения, флаг MSG_MORE требует непосредственного изменения исходного кода программы.
В FreeBSD существует параметр, похожий на TCP_CORK; он называется TCP_NOPUSH.
Системные вызовы getsockname() и getpeername() возвращают локальный адрес, к которому привязан сокет, и, соответственно, адрес удаленного сокета на другом конце соединения.
#include
int getsockname(int
int getpeername(int
Оба вызова возвращают 0 при успешном завершении или -1 при ошибке
В обоих вызовах аргумент sockfd обозначает файловый дескриптор, ссылающийся на сокет, а addr — указатель на буфер подходящего размера, применяемый для возвращения структуры с адресом сокета. Размер и тип этой структуры зависят от домена сокета. С помощью аргумента addrlen возвращается итоговый результат. Перед вызовом он должен быть равен размеру буфера, на который указывает addr; при возвращении он содержит количество байтов, записанных в этот буфер.
Функция getsockname() возвращает семейство адресов сокета и адрес, к которому привязан данный сокет. Это может пригодиться, если он был привязан другой программой (например, inetd(8)), а его файловый дескриптор был сохранен на протяжении работы вызова exec().
Функция getsockname() тоже может оказаться полезной, если мы хотим определить номер динамического порта, который был назначен ядром сокету интернет-домена во время его автоматической привязки. Ядро выполняет эту привязку в следующих случаях:
• после вызова connect() или listen() для TCP-сокета, который ранее не был привязан к адресу с помощью операции bind();
• при первом вызове sendto() для UDP-сокета, ранее не привязанного к адресу;
• после вызова bind(), если в качестве номера порта (sin_port) был указан 0. В этом случае данный вызов определяет IP-адрес сокета, а ядро выбирает для него номер динамического порта.
Системный вызов getpeername() возвращает адрес удаленного сокета на другом конце соединения. Это может пригодиться в ситуации, когда серверу нужно получить адрес клиента, инициировавшего соединение. Данную информацию можно также получить при выполнении вызова accept(); но если сервер был запущен программой, которая вызвала accept() (например, inetd), то не сможет получить сведения об адресе, несмотря на наличие файлового дескриптора сокета.
Использование функций getsockname() и getpeername() продемонстрировано в листинге 57.3. Эта программа задействует функции, которые мы определили в листинге 55.9, для выполнения следующих шагов.