Вот мы и подошли к наиболее эффективному (на мой взгляд) способу использования сокетов для событий Windows. Намного удобнее обмениваться данными между клиентом и сервером с использованием событий, как это происходит во всех компонентах Delphi. Мы просто должны создать сервер и какую-нибудь процедуру, которая будет вызываться в ответ на события, генерируемые сетевой подсистемой.

События для сетевых функций реализуются достаточно легко, и для этого нам нужно познакомиться с функцией WSAAsyncSel ect. Она требует от системы генерации сообщений Windows для сетевых событий. Функция описывается следующим образом:

function WSAAsyncSelect(
s: TSocket;
HWindow HWND.
wMsg: u_int;
1 Event: Longint ): Integer: stdcall;

Рассмотрим параметры, которые мы должны передать функции WSAAsyncSel ect:

• s - сокет, события которого нас интересуют;
• HWindow - окно, которому будут посылаться события;
• wMsg - системное событие, которое надо генерировать;

• 1 Event - какие именно события нас будут интересовать. В этом параметре можно указать любое сочетание из следующих значений:

О FD_READ - есть данные для чтения;
О FD_WRITE - можно передавать данные;
О FD_00B - прибыли срочные данные;
О FD_ACCEPT - есть запрос на соединение с сервером;
О FD_CONNECT - произошло соединение с сервером;

О FDCL0SE - сокет закрыт.

Давайте посмотрим, как использовать эту функцию на практике. Создайте новое приложение с одной кнопкой для запуска сервера. По нажатии этой кнопки нужно написать код из листинга 4.10.

Листинг 4.10. Запуск сервера

procedure TForml.bnStartClick(Sender: TObject): var wData: WSADATA, sServerListen: TSOCKET: localaddr sockaddr_in; iMode: Integer; begin // Загрузка WinSock

if WSAStartup(MAKEWORDd.l), wData) <> 0 then begin
MessageBoxCO. 'He могу загрузить WinSock', 'Ошибка'. 0);
exit;
end:

// Создание сокета

sServerListen .= socket(AF_INET. S0CK_STREAM. IPPR0T0JP):
if sServerListen = INVALID_SOCKET then
begin
MessageBoxCO. 'Ошибка создания сокета'. 'Ошибка'. 0)-exit;
end:

v // Переход в неблокирующий режим iMode := 1:

ioctlsocket(sServerListen. FIONBIO. iMode);

// Заполнение структуры адреса

localaddr.sin_addr.s_addr .= htonl(INADDR_ANY):
localaddr.sin_faimly := AF_INET:
localaddr.sin_port := htons(5050):

// Связывание сокета с локальным адресом

if bind (sServerListen. localaddr. sizeof(localaddr)) = SOCKETJRROR then begin
TestWi nSockError ('Bi nd');
exit, end.

// Прослушивание

WSAAsyncSelect(sServerListen. Handle. WMJETMESSAGE. FD_ACCEPT);
if TestFuncError(listen(sServerListen. 4). 'Listen') then exit;
end:

Практически весь код нам знаком и отличается от других наших серверов, написанных ранее, только тем, что перед вызовом функции listen мы вызываем WSAAsyncSel ect, а после этого уже ничего не надо делать. Не нужны дополнительные потоки или ожидания события с помощью sel ect. Все решается намного проще.

При вызове WSAAsyncSel ect мы указываем следующие параметры:

• s - созданный серверный сокет;
• hWnd - указатель на текущее окно, чтобы оно получало сообщения;
• wMsg - константа WM_NETMESSAGE. Сообщение с такой константой будет посылаться нашему окну;

• 1 Event - нас будет интересовать событие FD_ACCEPT, то есть событие подключения к серверу. Остальные события нас не инхересуют, потому что через этот сокет не будет происходить обмена данными.

Что это за константа WMNETMESSAGE? В начале модуля, после объявления подключаемых модулей (раздел uses) запишем ее описание:

const
WM_NETMESSAGE - WMJJSER+l;

Константа WMNETMESSAGE будет равна значению WMJJSER плюс единица. Что такое WMJJSER? Это число. Все числа меньше этого значения уже могут быть использованы для системных сообщений или зарегистрированы для будущего использования Большие значения можно использовать в своих программах, не боясь конфликтов с системой.

Теперь нам нужна процедура, которая будет "отлавливать" нужное сообщение. В разделе private нашего модуля запишем следующее:

private {Private declarations}
procedure WM_NetMsg (var M : TMessage):
message WM_NETMESSAGE;

Здесь объявлена новая процедура, которая является обработчиком события WM_ NETMESSAGE. Этого объявления достаточно, чтобы компилятор понял, на события какого типа должна вызываться процедура. Текст процедуры приведен в листинге 4.11.

Листинг 4.11. Обработчик сетевых событий

procedure TForml.WM_NetMsg(var M: TMessage): var
Cli entSocket•TSocket: iRet- Integer, sRecvBuff: array [0..255] of char;
begin
case M.LParam of

// Прибыло новое соединение

FD_ACCEPT:
begin
ClientSocket •= accept(m.wParam. ml, nil): WSAAsyncSelectCClientSocket. Handle. WMJJSER+l.
FD_READ or FD_WRITE or FD_CL0SE):
end:

// Прибыли данные

FD_READ:
begin
iRet := recv(m wParam. sRecvBuff. 1024, 0):

if (iRet = SOCKETJRROR) then begin MessageBoxCO. 'Ошибка получения данных' 'Внимание!!!', 0); exit:

end:
sRecvBuff := 'Command get - OK':
iRet := send(m.wParam. sRecvBuff, 16. 0):
if (iRet = SOCKETJRROR) then
begin
MessageBoxCO, 'Ошибка передачи данных'. 'Внимание!!!'. 0). exit;
end;
end;

// Готов к отправке

FDJJRITE:

begin // Здесь вы можете записывать данные end:

// Сокет закрыт

FDJLOSE:
begin
closesocketOn.wParam);
end: end;
end:

В этой процедуре скрыт очень интересный код. В качестве параметра функции передается переменная типа структуры TMessage. В ней нас будут интересовать два параметра:

• LParam - тип сетевого события;

• wParam - сокет, на котором произошло событие.

Благодаря наличию сокета в структуре TMessage одна процедура может обрабатывать события от разных сокетов. А это значит, что у нас может быть множество клиентов одновременно, и все они будут обрабатываться в одном месте и одним и тем же кодом.

Код процедуры - это один большой case, в котором параметр M.LParam сравнивается с константами событий. Когда к нашему серверу подключается клиент, этот параметр становится равным FDACCEPT. В этом случае выполняется следующий код:

ClientSocket .= accept(m.wParam, nil. nil). WSAAsyncSelect(CIientSocket. Handle. WMJJSER+l.
FDJJEAD or FDJJRITE or FD_CL0SE);

В первой строке мы принимаем входящее соединение. В качестве первого параметра функции accept указан m.wParam. Мы уже говорили, что в этом параметре хранится сокет, на котором произошло событие, и в данном случае это серверный сокет.

В результате выполнения функции accept образуется сокет для связи с клиентом. Тут же запрашиваем у системы разрешения отсылать нам события Windows и от сокета WSAAsyncSel ect. Событие будет то же самое, и во время выполнения не возникнет конфликтов, потому что для связи с клиентом нам нужны другие сообщения - чтение, запись и закрытие сокета. В примере мы будем использовать только чтение и закрытие.

Сохранять сокет в какой-то глобальной переменной не имеет смысла, потому что весь код для связи с клиентом будет находиться только в процедуре обработки сетевых сообщений WMJJetMsg. В ней нужный сокет всегда можно получить с помощью m.wParam. Это очень удобно, потому что не нужны дополнительные переменные или массивы.

Когда сокет готов к чтению (процедура получила сообщение FDJJEAD), мы читаем сетевые данные с сокета и передаем их уже знакомым нам способом. В данном случае никаких изменений нет.

Во всех предыдущих наших примерах мы не закрывали сокеты Эта операция очень проста - по событию FD_CL0SE выполняется код closesocket(m.wParam).

ПРИМЕЧАНИЕ -

Исходный код рассмотренного здесь примера находится на компакт-диске в каталоге Sources\ch04\Events.

4.13.2. Пример использования функции select || Оглавление || 4.13.4. Когда и что использовать?


Delphi в шутку и всерьез: что умеют хакеры



Новости за месяц

  • Декабрь
    2021
  • Пн
  • Вт
  • Ср
  • Чт
  • Пт
  • Сб
  • Вс
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31