// ***** Описание на C++ *****
int WSARecvFrom(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd, LPDWORD lpFlags, struct sockaddr FAR *lpFrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine;
// ***** Описание на Delphi *****
function WSARecvFrom(S: TSocket; lpBuffers: PWSABuf; dwBufferCount: DWORD; var NumberOfBytesRecvd: DWORD; var Flags: DWORD; lpFrom: PSockAddr; lpFromLen: PInteger; lpOverlapped: FWSAOverlapped; lpCompletionRoutine: TWSAOverlappedCompletionRoutine): Integer;
Параметры lpFrom и lpFromLen этой функции, служащие для получения адреса отправителя, эквивалентны соответствующим параметрам функции recvfrom, с которой мы уже хорошо знакомы. В остальном WSARecvFrom ведет себя так же, как WSARecv, поэтому мы не будем останавливаться на ней.
Для отправки данных в режиме перекрытого ввода-вывода существуют функции WSASend и WSASendTo, имеющие следующие прототипы (листинг 2.75).
Листинг 2.75. Функции WSASend и WSASendTo
// ***** Описание на C++ *****
int WSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
int WSASendTo(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfBytesSent, DWORD dwFlags, const struct sockaddr FAR *lpTo, int iToLen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
// ***** Описание на Delphi *****
function WSASend(S: TSocket; lpBuffers: PWSABuf; dwBufferCount: DWORD; var NumberOfBytesRecvd: DWORD; Flags: DWORD; lpOverlapped: PWSAOverlapped; lpCompletionRoutine: TWSAOverlappedCompletionRoutine): Integer;
function WSASendTo(S: TSocket; lpBuffers: PWSABuf; dwBufferCount: DWORD; var NumberOfBytesRecvd: DWORD; Flags: DWORD; var AddrTo: TSockAddr; ToLen: Integer; lpOverlapped: PWSAOverlapped; lpCompletionRoutine: TWSAOverlappedCompletionRoutine): Integer;
Если вы разобрались с функциями WSARecv, send и sendto, то смысл параметров функций WSASend и WSASendTo должен быть вам очевиден, поэтому подробно разбирать мы их не будем. Но отметим, что флаги передаются по значению, и функции не могут изменять их.
Потребность в перекрытом вводе-выводе при отправке данных возникает достаточно редко. Но функции WSASend/WSASendTo могут оказаться удобными при подготовке многокомпонентных пакетов, которые, например, имеют фиксированный заголовок и финальную часть. Для таких пакетов можно один раз подготовить буферы с заголовком и с финальной частью и, пользуясь возможностью отправки данных из несвязных буферов, при отправке каждого пакета менять только его среднюю часть.
2.2.10. Сервер, использующий перекрытый ввод-вывод
В этом разделе мы рассмотрим создание сервера на основе перекрытого ввода-вывода на основе процедур завершения (пример кода с использованием событий есть в MSDN в описании функций WSARecv — и WSASend). Перекрытый ввод-вывод лучше подходит для обмена в режиме "запрос-ответ", поэтому мы вновь вернемся к первоначальному протоколу, который не предусматривает отправку сервером сообщений по собственному усмотрению. На компакт-диске этот пример называется OverlappedServеr.
Как обычно, для каждого соединения создается экземпляр записи TConnection, которая на этот раз выглядит так, как показано в листинге 2.76.
Листинг 2.76. Тип TConnection
// Информация о соединении с клиентом:
// ClientSocket - сокет, созданный для взаимодействия с клиентом
// ClientAddr - строковое представление адреса клиента
// MsgSite - длина строки, получаемая от клиента
// Msg - строка, получаемая от клиента или отправляемая ему
// Offset - количество байтов, уже полученных от клиента
// или отправляемых ему на данном этапе
// BytesLeft - сколько байтов осталось получить от клиента
// или отправить ему на данном этапе
// Overlapped - структура для выполнения перекрытой операции
PConnection = ^TConnection;
TConnection = record
ClientSocket: TSocket;
ClientAddr: string;
MsgSize: Integer;
Msg: string;
Offset: Integer;
BytesLeft: Integer;
Overlapped: TWSAOverlapped;
end;
Основное отличие этого варианта типа TConnection от того, что применялся ранее в примерах NonBlockingServer и AsyncSelectServer (см. разд. 2.1.16 и 2.2.6, а также листинг 2.31) — это отсутствие поля Phase, которое хранит этап взаимодействия с клиентом. Разумеется, в программе OverlappedServer взаимодействие с клиентом также разбивается на три этапа, но реализуется другой способ для того, чтобы различать этапы — для каждого этапа создается своя процедура завершения.
Примечание
Использование одной процедуры завершения для всех трех этапов и распознавание в ней этапов с помощью поля 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);