Если на момент вызова функции accept
accept
в цикле до тех пор, пока она не завершится успехом, а вызвать ее один раз и ждать, когда подключится клиент. С другой стороны, это создает проблемы тем серверам, которые должны взаимодействовать с несколькими клиентами. Действительно, пусть функция accept
успешно завершилась и в распоряжении программы оказались два сокета: находящийся в режиме ожидания новых подключений и созданный для обслуживания уже существующего подключения. Если вызвать accept, то программа не сможет продолжить работу до тех пор, пока не подключится еще один клиент, а это может произойти через очень длительный промежуток времени или вообще никогда не случится. Из-за этого программа не сможет обрабатывать вызовы уже подключившегося клиента. С другой стороны, если функцию acсept
не вызывать, сервер не сможет обнаружить подключение новых клиентов. Средства для решения этой проблемы есть как у стандартных сокетов, так и у сокетов Windows, и далее мы их рассмотрим. Но существует довольно популярный способ ее решения средствами не библиотеки сокетов, а операционной системы. Он заключается в использовании отдельной нити для обслуживания каждого из клиентов. Каждый раз, когда клиент подключается, функция accept
передает управление программе, возвращая новый сокет. Здесь сервер может породить новую нить, которая предназначена исключительно для обмена данными с новым клиентом. Старая нить после этого снова вызывает accept для старого сокета, а новая — функции recv
и send
для нового сокета. Такой метод решает заодно и проблемы, связанные с тем, что функции send
и recv
также могут блокировать работу программы и помешать обмену данными с другими клиентами. В данном случае будет блокирована только одна нить, обменивающаяся данными с одним из клиентов, а остальные нити продолжат свою работу. Далее мы рассмотрим пример сервера, работающего по такой схеме.Рис. 2.3.
Последовательность действий клиента и сервера при использовании TCPТо, что функция recv
ReadFromSocket
), которая эти проблемы решает (листинг 2.13). Суть этой функции в том, что она вызывает recv до тех пор, пока не будет получено требуемое количество байтов или пока не возникнет ошибка. Тот код, который получает и анализирует приходящие данные, использует уже не recv
, a ReadFromSocket
, которая гарантирует, что будет возвращено столько байтов, сколько требуется.ReadFromSocket
// Функция читает Cnt байтов в буфер Buffer из сокета S
// Учитывается, что может потребоваться несколько операций чтения,
// прежде чем будет прочитано нужное число байтов.
// Возвращает:
// 1 — в случае успешного чтения
// 0 - в случае корректного закрытия соединения удаленной стороной
// -1 — в случае ошибки чтения
function ReadFromSocket(S: TSocket; var Buffer; Cnt: Integer): Integer;
var
Res, Total: Integer;
begin
// Total содержит количество принятых байтов
Total := 0;
// Читаем байты в цикле до тех пор, пока не будет прочитано Cnt байтов
repeat
// На каждой итерации цикла нам нужно прочитать
// не более чем Cnt - Total байтов, т.е. не более
// чем нужное количество минус то, что уже прочитано
// на предыдущих итерациях. Очередную порцию данных
// помещаем в буфер со смещением Total.
Res := recv(S, (PChar(@Buffer) + Total)^, Cnt - Total, 0);
if Res = 0 then
begin
// Соединение закрыто удаленной стороной
Result := 0;
Exit;
end;
if Res < 0 then
begin
// Произошла ошибка при чтении
Result := -1;
Exit;
end;
Inc(Total, Res);
until Total >= Cnt;
Result:= 1;
end;