NewConnection.ClientSocket:= ClientSocket;
NewConnection.ClientAddr:=
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.Offset:= 0;
NewConnection.BytesLeft:= SizeOf(Integer);
NewConnection.Overlapped.hEvent:= 0;
// Добавляем запись нового соединения в список
FConnections.Add(NewConnection);
AddMessageToLog('Зафиксировано подключение с адреса ' +
NewConnection.ClientAddr);
// Начинаем перекрытый обмен с сокетом.
// Начинаем, естественно, с чтения длины строки,
// в качестве принимающего буфера используем NewConnection.MsgSize
Buf.Len:= NewConnection.BytesLeft;
Buf.Buf:= @NewConnection.MsgSize;
Flags:= 0;
if WSARecv(NewConnection.ClientSocket, @Buf, 1, NumBytes, Flags,
@NewConnection.Overlapped, ReadLenCompleted) = SOCKET_ERROR then
begin
if WSAGetLastError <> WSA_IO_PENDING then
begin
AddMessageToLog('Клиент ' + NewConnection.ClientAddr +
' — ошибка при чтении длины строки: ' + GetErrorString);
RemoveConnection(NewConnection);
end;
end;
end;
end;
После того как сокет для взаимодействия с подключившимся клиентом создан, следует отменить для него асинхронный режим, унаследованный от слушающего сокета, т. к. при перекрытом вводе-выводе этот режим не нужен. Затем, после создания экземпляра TConnection
и добавления его в список, запускается первая операция перекрытого чтения с помощью функции WSARecv
. Об окончании этой операции будет сигнализировать вызов функции ReadLenCompleted
, которая передана в WSARecv
в качестве параметра.
Как мы уже говорили ранее, в программе OverlappedServer
есть три разных функции завершения: ReadLenCompleted
, ReadMsgCompleted
и SendMsgCompleted
. Последовательность работы с ними такая: сначала для чтения длины строки вызывается WSARecv
, в качестве буфера передастся Connection.MsgSize
, в качестве функции завершения — ReadLenCompleted
(это мы уже видели в листинге 2.77). Когда вызывается ReadLenCompleted
, это значит, что операция чтения уже завершена и прочитанная длина находится в Connection.MsgSize
. Поэтому в функции ReadLenCompleted
выделяем нужный размер для строки Connection.Msg
и запускаем следующую операцию перекрытого чтения — с буфером Connection.Msg
и функцией завершения ReadMsgCompleted
. В этой функции полученная строка показывается пользователю, формируется ответ, и запускается следующая операция перекрытого ввода-вывода — отправка строки клиенту. В качестве буфера в функцию WSASend
передаётся Connection.Msg
, а в качестве функции завершения — SendMsgCompleted
. В функции SendMsgCompleted
вновь вызывается WSARecv
с буфером Connection.MsgSize
и функцией завершения ReadLenCompleted
, и таким образом сервер возвращается к первому этапу взаимодействия с клиентом.
Описанную простую последовательность действий портит то, что из-за возможной отправки данных по частям можно столкнуться с ситуацией, когда функция завершения вызвана для уведомления о том, что получена или отправлена часть данных. Чтобы получить остальную их часть, необходимо вновь вызвать функцию чтения или записи с той же функцией завершения, а указатель на буфер должен при этом указывать на оставшуюся незаполненной часть переменной, в которую помещаются данные. С учетом этого, а также необходимости обработки ошибок, функции завершения выглядят так, как показано в листинге 2.78.
// Функция ReadLenCompleted используется в качестве функции завершения