// В ходе обработки проверяется наличие вновь подключившихся клиентов
// а также осуществляется обмен данными с клиентами
procedure TServerForm.TimerReadTimer(Sender: TObject);
var
// Сокет, который создается для вновь подключившегося клиента
ClientSocket: TSocket;
// Адрес подключившегося клиента
ClientAddr: TSockAddr;
// Длина адреса
AddrLen: Integer;
// Вспомогательная переменная для создания нового подключения
NewConnection: PConnection;
I: Integer;
begin
AddrLen := SizeOf(TSockAddr);
// Проверяем наличие подключении. Так как сокет неблокирующий,
// accept не будет блокировать нить даже в случае отсутствия
// подключений.
ClientSocket := accept(FServerSocket, @ClientAddr, @AddrLen);
if ClientSocket = INVALID_SOCKET then
begin
// Если произошедшая ошибка - WSAEWOULDBLOCK, это просто означает,
// что на данный момент подключений нет, а вообще все в порядке,
// поэтому ошибку WSAEWOULDBLOCK мы просто игнорируем. Прочие же
// ошибки могут произойти только в случае серьезных проблем,
// которые требуют остановки сервера.
if WSAGetLastError <> WSAEWOULDBLOCK then
begin
MessageDlg('Ошибка при подключении клиента:'#13#10 +
GetErrorString + #13#10'Сервер будет остановлен', mtError, [mbOK], 0);
ClearConnections;
closesocket(FServerSocket);
OnStopServer;
end;
end
else
begin
// Создаем запись для нового подключения и заполняем ее
New(NewConnection);
NewConnection.ClientSocket := ClientSocket;
NewConnection.СlientAddr :=
Format('%u.%u.%u.%u:%u', [
Ord(ClientAddr.sin_addr.S_un_b.s_b1),
Ord(ClientAddr.sin_addr.S_un_b.s_b2),
Ord(ClientAddr.sin_addr.S_un_b.s_b3),
Ord(ClientAddr.sin_addr.S_un_b.s_b4),
ntohs(ClientAddr.sin_port)]);
NewConnection.Phase := tpReceiveLength;
NewConnection.Offset := 0;
NewConnection.BytesLeft := SizeOf(Integer);
// Добавляем запись нового соединения в список
FConnections.Add(NewConnection);
AddMessageToLog('Зафиксировано подключение с адреса ' +
NewConnection.ClientAddr);
end;
// Обрабатываем все существующие подключения.
// Цикл идет от конца списка к началу потому, что в ходе
// обработки соединение может быть удалено из списка.
for I := FConnections.Count - 1 downto 0 do processConnection(I);
end;
Обратите внимание, что сокет, созданный функцией accept
, нигде не переводится в неблокирующий режим. Это связано с тем, что такой сокет наследует свойства слушающего сокета, поэтому он в данном случае сразу создается неблокирующим.
Собственно взаимодействие сервера с клиентом вынесено в метод ProcessConnection
(листинг 2.33). который осуществляет чтение данных от клиента и отправку данных в соответствии с этапом, на котором остановилось взаимодействие. При реализации этого метода необходимо просто аккуратно следить за тем, куда и сколько данных нужно передать.
ProcessConnection
// Обработка клиента. Index задает индекс записи в списке