В листинге 2.23 приведен пример кода TCP-сервера, взаимодействующего с несколькими клиентами в рамках одной нити и работающего по простой схеме "запрос-ответ".
Листинг 2.23. Пример сервера, использующего select
var
Sockets: array of TSocket;
Addr: TSockAddr;
Data: TWSAData;
Len, I, J: Integer;
FDSet: TFDSet;
begin
WSAStartup($101, Data);
SetLength(Sockets, 1);
Sockets[0] := socket(AF_INET, SOCK_STREAM, 0);
Addr.sin_family := AF_INET;
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));
Почему такая конструкция будет работать, предлагаем разобраться самостоятельно, изучив по справке Delphi, как хранятся в памяти динамические массивы, а по MSDN — структуру типа FDSET
. Тем же, кто по каким-то причинам не захочет разбираться, настоятельно рекомендуем никогда и ни при каких обстоятельствах не использовать такую конструкцию, потому что в неумелых руках она превращается в мину замедленного действия, из-за которой ошибки могут появиться в самых неожиданных местах программы.
Читать дальше
Конец ознакомительного отрывка
Купить книгу