Григорьев Антон Борисович
Шрифт:
Функция
send
для неблокирующего сокета также имеет некоторые специфические черты поведения. Они проявляются, когда свободное место в выходном буфере есть, но его недостаточно для хранения данных, которые программа пытается отправить с помощью этой функции. В этом случае функция send
, согласно документации, может скопировать в выходной буфер такой объем данных, для которого хватает места. При этом она вернет значение, равное этому объему (оно будет меньше, чем значение параметра len
, заданного программой). Оставшиеся данные программа должна отправить позже, вызвав еще раз функцию send
. Такое поведение функции send возможно только при использовании TCP. В случае UDP дейтаграмма никогда не разделяется на части, и если в выходном буфере не хватает места для всей дейтаграммы, то функция send
возвращает ошибку, a WSAGetLastError
— WSAEWOULDBLOCK
. Сразу отметим, что, хотя спецификация допускает частичное копирование функцией
send
данных в буфер сокета, на практике такое поведение наблюдать пока не удалось: все эксперименты показали, что функция send
всегда либо копирует данные целиком, расширяя при необходимости буфер, либо дает ошибку WSAEWOULDBLOCK
. Далее этот вопрос будет обсуждаться подробнее. Тем не менее при написании программ следует учитывать возможность частичного копирования, т.к. оно может появиться в тех условиях или в тех реализациях библиотеки сокетов, которые в наших экспериментах не были проверены. 2.1.16. Сервер на неблокирующих сокетах
В этом разделе мы создадим сервер, основанный на неблокирующих сокетах. Это будет наш первый сервер, не использующий функцию
ReadFromSocket
(см. листинг 2.13). Этот сервер (пример NonBlockingServer
на компакт-диске) состоит из одной нити, которая никогда не будет блокироваться сокетными операциями, т.к. все сокеты используют неблокирующий режим. На форме находится таймер, по сигналам которого сервер выполняет попытки чтения данных с сокетов всех подключившихся клиентов. Если данных нет, функция recv немедленно завершается с ошибкой WSAEWOULDBLOCK
, и сервер переходит к попытке чтения из следующего сокета. Запуск сервера (листинг 2.30) мало чем отличается от запуска многонитевого сервера (см. листинг 2.19). Практически вся разница заключается в том, что вместо запуска "слушающей" нити сокет переводится в неблокирующий режим и включается таймер.
Листинг 2.30. Инициализация сервера на неблокирующих сокетах
// Реакция на кнопку "Запустить" - запуск сервера
procedure TServerForm.BtnStartServerClick(Sender: TObject);
var
// Адрес, к которому привязывается слушающий сокет
ServerAddr: TSockAddr;
NonBlockingArg: u_long;
begin
// Формируем адрес для привязки.
FillChar(ServerAddr.sin_zero, SizeOf(ServerAddr.sin_zero), 0);
ServerAddr.sin_family := AF_INET;
ServerAddr.sin_addr.S_addr := INADDR_ANY;
try
ServerAddr.sin_port := htons(StrToInt(EditPortNumber.Text));
if ServerAddr.sin_port = 0 then
begin
MessageDlg('Номер порта должен находиться в диапазоне 1-65535',
mtError, [mbOK], 0);
Exit;
end;
// Создание сокета
FServerSocket := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if FServerSocket = INVALID_SOCKET then
begin
MessageDlg('Ошибка при создании сокета: '#13#10 + GetErrorString,
mtError, [mbOK], 0);
Exit;
end;
// Привязка сокета к адресу
if bind(FServerSocket, ServerAddr, SizeOf(ServerAddr)) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при привязке сокета к адреcу: '#13#10 +
GetErrorString, mtError, [mbOK], 0);
closesocket(FServerSocket);
Exit;
end;
// Перевод сокета в режим прослушивания
if listen(FServerSocket, SOMAXCONN) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при переводе сокета в режим прослушивания:'#13#10 +
GetErrorString, mtError, [mbOK], 0);
closesocket(FServerSocket);
Exit;
end;
// Перевод сокета в неблокирующий режим
NonBlockingArg := 1;