Примечание
Использование одной процедуры завершения для всех трех этапов и распознавание в ней этапов с помощью поля Phase
в случае перекрытого ввода-вывода также возможно. Рекомендуем написать такой вариант сервера в качестве самостоятельного упражнения.
Поле Overlapped
содержит структуру TWSAOverlapped
, которой программа непосредственно не пользуется, она только передает указатель на эту структуру в функции WSARecv
и WSASend
. Напомним, что одновременно может выполняться несколько операций перекрытого ввода-вывода, но у каждой из этих операций должен быть свой экземпляр TWSAOverlapped
. Гак как в нашем случае с одним клиентом в каждый момент времени может выполняться не более одной операции, мы создаем по одному экземпляру TWSAOverlapped
на каждого клиента.
Функция для перекрытого подключения клиентов существует — это AcceptEx
, с которой мы познакомимся в разд. 2.2.12 . Но она неудобна при работе совместно с WSARecv
и WSASend
, особенно в таком строго типизированном языке, как Delphi. Поэтому подключение клиентов мы будем отслеживать с помощью уже опробованной технологии асинхронных сокетов на сообщениях. Код запуска сервера OverlappedServer выглядит идентично коду запуска AsyncSelectServer (см. листинг 2.30): точно так же создается сокет, ставится в режим прослушивания, а затем его событие FD_ACCEPT
привязывается к сообщению WM_ACCEPTMESSAGE
.
Сам обработчик WM_ACCEPTMESSAGE
выглядит теперь следующим образом (листинг 2.77).
Листинг 2.77. Обработчик сообщения WM_ ACCEPTMESSAGE
procedure TServerForm.WMAcceptMessage(var Msg: TWMSocketMessage);
var
NewConnection: PConnection;
// Сокет, который создается для вновь подключившегося клиента
ClientSocket: TSocket;
// Адрес подключившегося клиента
ClientAddr: TSockAddr;
// Длина адреса
AddrLen: Integer;
// Аргумент для перевода сокета в неблокирующий режим
Arg: u_long;
// Буфер для операции перекрытого чтения
Buf: TWSABuf;
NumBytes, Flags: DWORD;
begin
// Страхуемся от "тупой" ошибки
if Msg.Socket <> FServerSocket then
raise ESocketError.Create(
'Внутренняя ошибка сервера - неверный серверный сокет');
// Обрабатываем ошибку на сокете, если она есть
if Msg.SockError <> 0 then
begin
MessageDlg('Ошибка при подключении клиента:'#13#10 +
GetErrorString(Msg.SockError) +
#13#10'Сервер будет ocтановлен', mtError, [mbOK], 0);
ClearConnections;
closesocket(FServerSocket);
OnStopServer;
Exit;
end;
// Страхуемся от ещё одной "тупой" ошибки
if Msg.SockEvent <> FD_ACCEPT then
raise ESocketError.Create(
'Внутренняя ошибка сервера - неверное событие на сокете');
AddrLen := SizeOf(TSockAddr);
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
// Новый сокет наследует свойства слушающего сокета.
// В частности, он работает в асинхронном режиме,
// и его событие FD_ACCEPT связано с сообщением WM_ACCEPTMESSAGE.
// Так как нам это совершенно не нужно, отменяем асинхронный
// режим и делаем сокет блокирующим.
if WSAAsyncSelect(ClientSocket, Handle, 0, 0) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при отмене асинхронного режима ' +
'подключившегося сокета:'#13#10 + GetErrorString,
mtError, [mbOK], 0);
closesocket(ClientSocket);
Exit;
end;
Arg := 0;
if ioctlsocket(ClientSocket, FIONBIO, Arg) = SOCKET_ERROR then
begin
MessageDlg('Ошибка при переводе подключившегося сокета ' +
'в блокирующий режим:'#13#10 + GetErrorString,
mtError, [mbOK], 0);
closesocket(ClientSocket);
Exit;
end;
// Создаем запись для нового подключения и заполняем ее
New(NewConnection);
NewConnection.ClientSocket := ClientSocket;
NewConnection.ClientAddr :=
Format('%u.%u.%u.%u:%u, [
Читать дальше
Конец ознакомительного отрывка
Купить книгу