Addr.sin_port := htons(5514);
Addr.sin_addr.S_addr := INADDR_ANY;
FillChar(Addr.sin_zero, SizeOf(Addr.sin_zero), 0);
bind(Sockets[0], Addr, SizeOf(TSockAddr));
listen(Sockets[0], SCMAXCONN);
while True do
begin
// 1. Формирование множества сокетов
FD_ZERO(FDSet);
for I := 0 to High(Sockets) do FDSET(Sockets[1], FDSet);
// 2. Проверка готовности сокетов
select(0, @FDSet, nil, nil, nil);
// 3. Чтение запросов клиентов тех сокетов, которые готовы к этому
I := 1;
while I <= High(Sockets) do
begin
if FD_ISSET(Sockets[I], FDSet) then if recv(Sockets[I], ...) <= 0 then
begin
// Связь разорвана, нужно закрыть сокет
// и удалить его из массива
closesocket(Sockets[I]);
for J := I to High(Sockets) - 1 do Sockets[J] := Sockets[J + 1];
Dec(I);
SetLength(Sockets, Length(Sockets) -1);
end
else
begin
// Получены данные от клиента, нужно ответить
send(Sockets[I], ...);
end;
Inc(I);
end;
// 4. Проверка подключения нового клиента
if FD_ISSET(Sockets[0], FDSet) then
begin
// Подключился новый клиент
SetLength(Sockets, Length(Sockets) + 1);
Len := SizeOf(TSockAddr);
Sockets[High(Sockets)] := accept(Sockets[0], @Addr, @Len)
end;
end;
end;
Как и в предыдущих примерах, код для краткости не содержит проверок успешности завершения функций. Еще раз напоминаем, что в реальном коде такие проверки необходимы.
Теперь разберем программу по шагам. Создание сокета, привязка к адресу и перевод в режим ожидания подключений вам уже знакомы, поэтому мы на них останавливаться не будем. Отметим только, что вместо переменной типа TSocket
accept
. После перевода сокета в режим ожидания подключения начинается бесконечный цикл, состоящий из четырех шагов.На первом шаге цикла создаётся множество сокетов, в которое добавляются все сокеты, содержащиеся в массиве. В этом месте в примере пропущена важная проверка того, что сокетов в массиве не больше 64-х. Если их будет больше, то попытки добавить лишние сокеты в множество будут проигнорированы функцией FD_SET
accept
нужно вызывать для нового сокета closesocket
. Второй способ — это увеличение количества сокетов в множестве, как это было описано ранее. В этом случае все равно остается та же проблема, хотя если сделать число сокетов в множестве достаточно большим, она практически исчезает. И наконец, можно разделить сокеты на несколько порций, для каждой из которых вызывать select отдельно. Это потребует усложнения примера, потому что сейчас в функции select
мы используем бесконечное ожидание. При разбиении сокетов на порции это может привести к тому, что из-за отсутствия готовых сокетов в первой порции программа не сможет перейти к проверке второй порции, в которой готовые сокеты, может быть, есть. Пример разделения сокетов на порции будет рассмотрен в следующем разделе.При создании множества оно сначала очищается, а потом в него в цикле добавляются сокеты. Для любителей кратких решений есть существенно более быстрый способ формирования множества, при котором не потребуются ни циклы, ни FD_ZERO
FD_SET
:Move((PChar(Sockets) - 4)^, FDSet, Length(Sockets) * SizeOf(TSocket) + SizeOf(Integer));