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). который осуществляет чтение данных от клиента и отправку данных в соответствии с этапом, на котором остановилось взаимодействие. При реализации этого метода необходимо просто аккуратно следить за тем, куда и сколько данных нужно передать.
Листинг 2.33. Метод ProcessConnection
// Обработка клиента. Index задает индекс записи в списке
procedure TServerForm.ProcessConnection(Index: Integer);
var
// Вспомогательная переменная, чтобы не приводить каждый раз
// FConnections[Index] к PConnection
Connection: PConnection;
// Результат вызова recv и send
Res: Integer;
// Вспомогательная процедура, освобождающая ресурсы, связанные
// с клиентом и удаляющая запись подключения из списка
procedure RemoveConnection;
begin
closesocket(Connection.ClientSocket);
Dispose(Connection);
FConnections.Delete(Index);
end;
begin
Connection := PConnection(PConnections[Index]);
// Проверяем, на каком этапе находится взаимодействие с клиентом.
// Используется оператор if, а не case, потому, что в случае case
// выполняется только одна альтернатива, а в нашем случае в ходе
// выполнения этапа он может завершиться, и взаимодействие
// перейдет к следующему этапу. Использование if позволяет выполнить
// все три этапа, если это возможно, а не один из них.
if Connection.Phase = tpReceiveLength then
begin
// Этап получения от клиента длины строки. При выполнении этого
// этапа сервер получает от клиента длину строки и размещает ее
// в поле Connection.MsgSize. Здесь приходится учитывать, что
// теоретически даже такая маленькая (4 байта) посылка может
// быть разбита на несколько пакетов, поэтому за один раз этот
// этап не будет завершен, и второй раз его придется продолжать,
// загружая оставшиеся байты. Connection.Offset — количество
// уже прочитанных на данном этапе байтов - одновременно является
// смещением, начиная с которого заполняется буфер.
Res := recv(Connection.ClientSocket, (PChar(@Connection.MsgSize) +
Connection.Offset)^, Connection.BytesLeft, 0);
if Res > 0 then
begin
// Если Res > 0, это означает, что получено Res байтов.
// Соответственно, увеличиваем на Res количество прочитанных
// на данном этапе байтов и на такую же величину уменьшаем
Читать дальше
Конец ознакомительного отрывка
Купить книгу