Запись со сбором
Как видите, существуют приложения, которые, действительно, должны отключать алгоритм Нейгла, но в основном это делается из-за проблем с производительностью, причина которых в отправке логически связанных данных серией из Дельных операций записи. Есть много способов собрать данные, чтобы послать вместе. Наконец всегда можно скопировать различные порции данных в один буфер, которые потом и передать операции записи. Но, как объясняется в совете 26 к такому методу следует прибегать в крайнем случае. Иногда можно организовать хранение данных в одном месте, как и сделано в листинге 2.15. Чаще однако иные находятся в нескольких несмежных буферах, а хотелось бы послать их одной операцией записи.
Для этого и в UNIX, и в Winsock предусмотрен некоторый способ. К сожалению, эти способы немного отличаются. В UNIX есть системный вызов writev и парный ему вызов readv. При использовании writev вы задаете список буферов, из которых должны собираться данные. Это решает исходную задачу: можно размещать данные в нескольких буферах, а записывать их одной операцией, исключив тем самым интерференцию между алгоритмами Нейгла и отложенного подтверждения.
#include <sys/uio.h>
ssize_t writev (int fd, const struct iovec *iov, int cnt);
ssize_t readv(int fd, const struct iovec *iov, int cnt);
Возвращаемое значение: число переданных байт или -1 в случае ошибки.
Параметр iov- это указатель на массив структур iovec, в которых хранятся указатели на буферы данных и размеры этих буферов:
struct iovec {
char *iov_base; /* Адрес начала буфера. */
size_t iov_len; /* Длина буфера. */
};
Примечание: Это определение взято из системы FreeBSD. Теперь во многих системах адрес начала буфера определяется так:
void *iov_base; /* адрес начала буфера */
Третий параметр, cnt - это число структур iovec в массиве (иными словами, количество буферов).
У вызовов writev и readv практически общий интерфейс. Их можно использовать для любых файловых дескрипторов, а не только для сокетов.
Чтобы это понять, следует переписать клиент (листинг 3.23), работающий с записями переменной длины (листинг 2.15), с использованием writev.
Листинг 3.23. Клиент, посылающий записи переменной длины с помощью writev
1 #include "etcp.h"
2 #include <sys/uio.h>
3 int main( int argc, char **argv)
4 {
5 SOCKET s;
6 int n;
7 char buf[128];
8 struct iovec iov[ 2 ];
9 INIT();
10 s = tcp_client( argv[ 1 ], argv[ 2 ] ) ;
11 iov[ 0 ].iov_base = ( char * )&n;
12 iov[ 0 ].iov_len = sizeof( n ) ;
13 iov[ 1 ].iov_base = buf;
14 while ( fgets( buf, sizeof( buf ), stdin ) != NULL )
15 {
16 iov[ 1 ].iov_len = strlent buf );
17 n = htonl ( iov[ 1 ].iov_len ) ;
18 if ( writev( s, iov, 2 ) < 0 )
19 error( 1, errno, "ошибка вызова writev" );
20 }
21 EXIT( 0 ) ;
22 }
Инициализация
9- 13 Выполнив обычную инициализацию клиента, формируем массив iov. Поскольку в прототипе writev имеется спецификатор const для структур, на которые указывает параметр iov, то есть гарантия, что массив iov не будет изменен внутри writev, так что большую часть параметров можно задавать вне цикла while.
Цикл обработки событий
14-20 Вызываем fgets для чтения одной строки из стандартного ввода, вычисляем ее длину и записываем в поле структуры из массива iov. Кроме того, длина преобразуется в сетевой порядок байт и сохраняется в переменной n.
Если запустить сервер vrs (совет 6) и вместе с ним клиента vrcv, то получатся те же результаты, что и раньше.
В спецификации Winsock определен другой, хотя и похожий интерфейс.
#include <winsock2.h>
int WSAAPI WSAsend (SOCKET s, LPWSABUF, DWORD cnt, LPDWORD sent, DWORD flags, LPWSAOVERLAPPED ovl, LPWSAOVERLAPPED_COMPLETION_ROUTINE func );
Возвращаемое значение: 0 в случае успеха, в противном случае SOCKET_ERROR.
Последние два аргумента используются при вводе/выводе с перекрытием, и в данном случае не имеют значения, так что обоим присваивается значение NULL. параметр buf указывает на массив структур типа WSABUF, играющих ту же роль, Что структуры iovec в вызове writev.
typedef struct _WSABUF {
u_longlen; /* Длина буфера. */
char FAR * buf; /* Указатель на начало буфера. */
} WSABUF, FAR * LPWSABUF;
Параметр sent - это указатель на переменную типа DWORD, в которой хранится число переданных байт при успешном завершении вызова. Параметр flags аналогичен одноименному параметру в вызове send.
Версия клиента, посылающего сообщения переменной длины, на платформе Windows выглядит так (листинг 3.24):
Листинг 3.24. Версия vrcv для Winsock
1 #include "etcp.h"
2 int main( int argc, char **argv )
3 {
4 SOCKET s;
5 int n;
6 char buf[ 128 ] ;
7 WSABUF wbuf[ 2 ];
8 DWORD sent;
9 INIT();
10 s = tcp_client( argv[ 1 ], argv[ 2 ] ) ;
11 wbuf[ 0 ].buf = ( char * )&n;
12 wbuf[ 0 ].len = sizeof( n );
13 wbuf[ 1 ].buf = buf;
14 while ( fgets( buf, sizeof( buf ), stdin ) != NULL )
15 {
16 wbuff 1 ].len = strlen( buf );
17 n = htonl ( wbuff 1 ].len );
18 if ( WSASend( s, wbuf, 2, &sent, 0, NULL, NULL ) < 0 )
19 error( 1, errno, "ошибка вызова WSASend" );
20 }
21 EXIT( 0 );
22 }
Как видите, если не считать иного обращения к вызову записи со сбором, то Winsock-версия идентична UNIX-версии.