Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Малышев_Сетевое программирование 19.12.18

.pdf
Скачиваний:
2
Добавлен:
21.11.2025
Размер:
896.22 Кб
Скачать

71

Далее нужно разобраться с параметрами, которые будут передаваться в функцию WndProc при возникновении события. Выглядит эта функция:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

Параметры wParam и lParam содержат вспомогательную информацию (в зависимости от события). Для событий сети в параметре wParam хранится дескриптор сокета, на котором произошло событие. Таким образом, не нужно хранить весь массив созданных сокетов, их всегда можно получить в событии.

Параметр lParam состоит из двух слов: младшее определяет событие, а старшее – код ошибки.

Вот теперь можно переходить к рассмотрению реального примера по обработке быстрых клавиш [21]. Всю функцию _tWinMain можно увидеть в листинге 8.

Листинг 8.

//1.cpp: определяет точку входа для приложения.

#include "stdafx.h" #include "1.h"

#define MAX_LOADSTRING 100

//Глобальные переменные:

HINSTANCE hInst;

// текущий экземпляр

TCHAR szTitle[MAX_LOADSTRING]; // Текст строки заголовка

TCHAR szWindowClass[MAX_LOADSTRING];// имя класса главного

//окна

 

 

// Отправить объявления функций, включенных в этот модуль кода:

ATOM

MyRegisterClass(HINSTANCE hInstance);

BOOL

InitInstance(HINSTANCE, int);

 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM,

LPARAM);

//Основная функция - аналог int main() в консольном приложении: int APIENTRY _tWinMain(HINSTANCE hInstance,

//- дескриптор экземпляра приложения

HINSTANCE hPrevInstance,

//- в Win32 не используется

LPTSTR lpCmdLine,

//нужен для запуска окна в режиме командной строки

int nCmdShow)

// - режим отображения окна

{

//Отмена определения параметров hPrevInstance (предыдущий

//экземпляр приложения) и lpCmdLine (командная строка)

UNREFERENCED_PARAMETER(hPrevInstance);

72

UNREFERENCED_PARAMETER(lpCmdLine);

//TODO: разместите код здесь.

MSG msg;

HACCEL hAccelTable; // дескриптор таблицы быстрых клавиш

//Инициализация глобальных строк

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

LoadString(hInstance, IDC_MY, szWindowClass, MAX_LOADSTRING);

MyRegisterClass(hInstance);

// Выполнить инициализацию приложения: if (!InitInstance (hInstance, nCmdShow))

{

return FALSE;

}

hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));

// - загружает заданную таблицу быстрых клавиш

WSADATA wsd; // структура, в которую помещается детальная //информация о сокете

if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) // заполняет

//переменную типа WSADATA и загружает динамическую библиотеку

//сокетов, интерфейс Windows Socket

{

MessageBox(0, "Can't load WinSock", "Error", 0); // Не удается

//загрузить WinSock return 0;

}

SOCKET sServerListen, sClient; struct sockaddr_in localaddr, clientaddr; HANDLE hThread;

DWORD dwThreadId; int iSize;

sServerListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sServerListen == SOCKET_ERROR)

{

MessageBox(0, "Can't load WinSock", "Error", 0); // Не удается

//загрузить WinSock return 0;

}

ULONG ulBlock; ulBlock = 1;

73

if (ioctlsocket(sServerListen, FIONBIO, &ulBlock) == SOCKET_ERROR)

//неблокирующий режим

{

return 0;

}

localaddr.sin_addr.s_addr = htonl(INADDR_ANY); localaddr.sin_family = AF_INET; localaddr.sin_port = htons(5050);

if (bind(sServerListen, (struct sockaddr *)&localaddr, sizeof(localaddr)) == SOCKET_ERROR) // привязка сокета к порту и

//IP-адресу

{

MessageBox(0, "Can't bind", "Error", 0); // Невозможно привязать return 1;

}

WSAAsyncSelect(sServerListen, hWnd, WM_USER+1, FD_ACCEPT);

//серверный сокет

listen(sServerListen, 4); //режим прослушивания

// Цикл основного сообщения:

while (GetMessage(&msg, NULL, 0, 0))//извлекает сообщения для

//любого окна

{

if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

//обрабатывает быстрые клавиши для команд меню // То есть эта функция на первом этапе производит обработку

//сообщений и создает //сообщения более крупные по группе. Нам уже //можно не анализировать, что нажата //и отпущена клавиша, а просто //можно обрабатывать сообщение "введена буква". //Каждый процесс //должен использовать эту функцию для обработки сообщений.

{

TranslateMessage(&msg);

// - переводит сообщения формата виртуальных клавиш в сообщения //символы

DispatchMessage(&msg);

// - распределяет сообщение оконной процедуре

}

}

closesocket(sServerListen);

WSACleanup(); // завершает использование данного DLL и //прерывает обращение к функциям WinSock. При удачном выполнении //вернется ноль.

74

return (int) msg.wParam;

}

//Здесь должны быть: ФУНКЦИЯ: MyRegisterClass()

//ФУНКЦИЯ: InitInstance(HINSTANCE, int)

//ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)

Благодаря использованию функции WSAAsyncSelect весь код (без дополнительных потоков) расположен прямо в функции_tWinMain.

Функция _tWinMain – [17] имеет четыре параметра. Первый из них – HINSTANCE hInstance (строка 4). hInstance является дескриптором экземпляра окна (это некий код оконной процедуры, идентификатор, по которому ОС будет отличать её от остальных окон). Через него можно обращаться к окну в процессе работы в других функциях изменять параметры окна. HINSTANCE является одним из многочисленных типов данных определенных в WinAPI, таким же как int, например.

Следующий параметр: HINSTANCE hPrevInstance (строка 5). Как написано в комментариях, в Win32 он не используется (так как он создан для 3.x разрядной системы), из предыдущего понятно, что это дескриптор экземпляра окна.

Далее переменная типа LPSTR (строка 6) с именем lpCmdLine. Она используется в том случае, если окно запускается через командную строку

спрописью параметров.

Ипоследний параметр: целочисленный, определяет способ (режим) отображения окна. Нужен для функции ShowWindow. Например, с помощью него можно развернуть окно на весь экран, сделать его определённой высоты, прозрачным или поверх остальных.

// TODO: разместите код здесь.

MSG msg;

HACCEL hAccelTable; // дескриптор таблицы быстрых клавиш

Две функции с именем LoadString загружают текст из ресурсов строк [18]. В данном случае выполнится код загрузки ресурса.

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);

LoadString(hInstance, IDC_MY, szWindowClass, MAX_LOADSTRING);

Четыре параметра [18].

hInstance – указывает на экземпляр данной программы, потому что нужны ресурсы из данного проекта.

75

IDS_APP_TITLE – имя ресурса, который надо загрузить. Если сейчас дважды щелкнуть в ресурсах на разделе String Table, то откроется таблица строк, в которой в одной колонке будет имя строки, а во второй текст. Вот именно это имя и нужно указывать в этом параметре.

szTitle – переменная, в которую должно поместиться значение. В начале файла у нас объявлено две переменные с именами szTitle и szWindowClass:

TCHAR szTitle[MAX_LOADSTRING]; // Текст заголовка окна TCHAR szWindowClass[MAX_LOADSTRING]; // Имя класса главно-

го окна.

В качестве размера указано MAX_LOADSTRING. MAX_LOADSTRING – последний параметр, который указывает мак-

симальное количество загружаемых символов. Получается, что размер загружаемой строки равен размеру строки в переменной, и нельзя загрузить из ресурсов в переменную больше, чем выделено памяти.

После этого идет вызов функции MyRegisterClass(hInstance). В ней происходит заполнение структуры WNDCLASSEX.

WNDCLASSEX – это структура, которая используется при создании нового класса окна.

Далее выполняем инициализацию приложения, создание сокета и перевод его в неблокирующий режим; привязываем сокет к порту и IP- адресу:

// Выполнить инициализацию приложения: if (!InitInstance (hInstance, nCmdShow))

{

return FALSE;

}

hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));

// - загружает заданную таблицу быстрых клавиш

WSADATA wsd; // структура, в которую помещается детальная информация о сокете

if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) // заполняет

//переменную типа WSADATA и загружает динамическую библиотеку

//сокетов, интерфейс Windows Socket

{

MessageBox(0, "Can't load WinSock", "Error", 0); // Не удается

//загрузить WinSock return 0;

}

SOCKET sServerListen, sClient;

76

struct sockaddr_in localaddr, clientaddr; HANDLE hThread;

DWORD dwThreadId; int iSize;

sServerListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sServerListen == SOCKET_ERROR)

{

MessageBox(0, "Can't load WinSock", "Error", 0); // Не удается

//загрузить WinSock return 0;

}

ULONG ulBlock; ulBlock = 1;

if (ioctlsocket(sServerListen, FIONBIO, &ulBlock) == SOCKET_ERROR)

//неблокирующий режим

{

return 0;

}

localaddr.sin_addr.s_addr = htonl(INADDR_ANY); localaddr.sin_family = AF_INET; localaddr.sin_port = htons(5050);

if (bind(sServerListen, (struct sockaddr *)&localaddr, sizeof(localaddr)) == SOCKET_ERROR) // привязка сокета к порту и

//IP-адресу

{

MessageBox(0, "Can't bind", "Error", 0); // Невозможно привязать return 1;

}

Перед запуском прослушивания (listen) вызывается функция WSAAsyncSelect, чтобы выбрать созданный сокет и перевести его в неблокирующий (асинхронный) режим.

WSAAsyncSelect(sServerListen, hWnd, WM_USER+1, FD_ACCEPT);

//серверный сокет

listen(sServerListen, 4); //режим прослушивания

ВWSAAsyncSelect указываются следующие параметры:

sServerListen – переменная, которая указывает на созданный серверный сокет;

hWnd – указатель на главное окно программы, и именно ему будут передаваться сообщения;

WM_USER+1 – все пользовательские сообщения должны быть больше константы WM_USER. Меньшие значения могут ис-

77

пользоваться системой и вызвать конфликт. Такая конструкция применяется, чтобы явно показать необходимость использования такого сообщения. В реальных приложениях нужно создавать для этого константу с понятным именем и использовать ее. Это можно сделать следующим образом: #define WM_NETMESSAGE WM_USER+1;

FD_ACCEPT – событие, которое нужно обрабатывать. Серверный сокет готов принимать соединение со стороны клиента.

Далее выполняем основной цикл обработки сообщений:

while (GetMessage(&msg, NULL, 0, 0)) //извлекает сообщения для любого окна

{

if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))

//обрабатывает быстрые клавиши для команд меню // То есть эта функция на первом этапе производит обработку

//сообщений и создает сообщения более крупные по группе. Нам уже //можно не анализировать, что нажата и отпущена клавиша, а просто //можно обрабатывать сообщение "введена буква". Каждый процесс //должен использовать эту функцию для обработки сообщений.

{

TranslateMessage(&msg);

// - переводит сообщения формата виртуальных клавиш в //сообщения символы

DispatchMessage(&msg);

// - распределяет сообщение оконной процедуре

}

}

closesocket(sServerListen);

WSACleanup(); // завершает использование данного DLL и //прерывает обращение к функциям WinSock. При удачном выполнении //вернется ноль.

return (int) msg.wParam;

}

Основное будет происходить в функции WndProc. Начало функции, где нужно добавить код, показано в листинге:

//ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)

//НАЗНАЧЕНИЕ: обрабатывает сообщения в главном окне.

//Обработка сетевых сообщений в функции WndProc

// WM_COMMAND

- обработка меню приложения

// WM_PAINT - закрасить главное окно

// WM_DESTROY

- ввести сообщение о выходе и вернуться.

78

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc;

SOCKET ClientSocket; int ret;

char szRecvBuff[1024], szSendBuff[1024]; switch (message)

{

case WM_USER+1: // получение события switch (WSAGETSELECTEVENT(lParam))

{

case FD_ACCEPT:

ClientSocket = accept(wParam, 0, 0); // прием запроса на

//соединение

WSAAsyncSelect(ClientSocket, hWnd, WM_USER+1, FD_READ | FD_WRITE | FD_CLOSE);

break; case FD_READ:

ret = recv(wParam, szRecvBuff, 1024, 0); // чтение данных if (ret == 0)

break;

else if (ret == SOCKET_ERROR)

{

MessageBox(0, "Recive data filed", // ошибка получения

//данных

"Error", 0); break;

}

szRecvBuff[ret] = '\0';

strcpy(szSendBuff, "Command get OK"); // команда принята ret = send(wParam, szSendBuff,

sizeof(szSendBuff), 0); break;

case FD_WRITE:

//Ready to send data - готовность к отправке break;

case FD_CLOSE: closesocket(wParam); break;

79

// другие возможности: case WM_COMMAND:

wmId = LOWORD(wParam); wmEvent = HIWORD(wParam);

// Разобрать выбор в меню: switch (wmId)

{

case IDM_ABOUT: DialogBox(hInst,

MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break;

case IDM_EXIT: DestroyWindow(hWnd); break;

default:

return DefWindowProc(hWnd, message, wParam,

lParam);

}

break; case WM_PAINT:

hdc = BeginPaint(hWnd, &ps);

// TODO: добавьте любой код отрисовки...

EndPaint(hWnd, &ps); break;

case WM_DESTROY: PostQuitMessage(0); break;

default:

return DefWindowProc(hWnd, message, wParam, lParam);

}

return 0;

}

Здесь в самом начале добавлен оператор case, который проверяет, равно ли пойманное сообщение искомому сетевому сообщению WM_USER+1. Если это сетевое событие, то запускается перебор сетевых событий с помощью оператора switch, который сравнивает указанное в скобках значение с поступающими событиями:

switch (WSAGETSELECTEVENT(lParam))

Как известно, в параметре lParam находятся код ошибки и тип события. Чтобы получить событие, используется функция WSAGETSELECTEVENT. А затем проверяются необходимые нам события.

80

Если произошло соединение со стороны клиента, то выполняется следующий код:

case FD_ACCEPT:

ClientSocket = accept(wParam, 0, 0); WSAAsyncSelect(ClientSocket, hWnd, WM_USER+1, FD_READ | FD_WRITE | FD_CLOSE);

break;

Сначала принимается соединение с помощью функции accept. Результатом будет сокет, с помощью которого можно работать с клиентом. С этого сокета тоже нужно ловить события, поэтому вызываем функцию WSAAsyncSelect. Чтобы не плодить сообщения, используем в качестве третьего параметра значение WM_USER+1. Это не вызовет конфликтов, потому что серверный сокет обрабатывает только событие FD_ACCEPT, а у клиентского нас интересуют события чтения, записи данных и закрытия сокета.

Когда к серверу придут данные, поступит сообщение WM_USER+1, а

функция WSAGETSELECTEVENT (lParam) вернет значение FD_READ. В

этом случае читаются пришедшие данные (recv), а клиенту посылается текст "Command get OK":

case FD_READ:

ret = recv(wParam, szRecvBuff, 1024, 0); if (ret == 0)

break;

else if (ret == SOCKET_ERROR)

{

MessageBox(0, "Recive data filed", // ошибка получения данных "Error", 0);

break;

}

szRecvBuff[ret] = '\0';

strcpy(szSendBuff, "Command get OK"); //команда принята ret = send(wParam, szSendBuff, sizeof(szSendBuff), 0); break;

По событию FD_WRITE ничего не происходит, а только стоит комментарий. По событию FD_CLOSE закрывается сокет.

Рассмотренный пример с использованием функции select может работать только с одним клиентом. Чтобы добавить возможность одновременной обработки нескольких соединений, необходимо сформировать массив потоков, используемых для приема/передачи данных.