Практическое введение в Internet, E-Mail, FTP, WWW и HTML

Приложение SERVERD


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

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

Сервер UDP должен создать сокет с помощью функции socket и привязать к нему адрес IP, вызвав функцию bind . Клиент UDP выполняет создание и инициализацию сокетов аналогичным образом с помощью все тех же функций socket и bind.

Такие известные вам из предыдущих приложений функции, как connect, listen и

accept в приложениях UDP использовать не нужно.

Для обмена данными приложения UDP вызывают функции send to и recv from, аналогичные функциям send и recv, но имеющие одно отличие - при вызове этих функций им необходимо задавать дополнительные параметры, имеющие отношение к адресам узлов. Функции sendto нужно указать адрес, по которому будет отправлен пакет данных, а функции

recvfrom - указатель на структуру, в которую будет записан адрес отправителя пакета.

В нашей книге мы привели исходные тексты приложений SERVERD и CLIENTD, которые выполняют те же задачи, что и только что рассмотренные приложения SERVER и CLIENT, но при этом они передают данные при помощи датаграммного протокола UDP .

Исходный текст приложения SERVERD

приведен в листинге 5.7.

Листинг 5.7. Файл serverd/serverd.c

#include <windows.h> #include <windowsx.h> #include <winsock.h> #include <commctrl.h> #include "resource.h"

// ----------------------------------------------------- // Описание функций // -----------------------------------------------------



// Функция главного окна LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

// Функция для обработки сообщения WM_CREATE BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);

// Функция для обработки сообщения WM_DESTROY void WndProc_OnDestroy(HWND hWnd);


// Функция для обработки сообщения WM_COMMAND void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify);

// Функция для обработки сообщения WM_SIZE void WndProc_OnSize(HWND hWnd, UINT state, int cx, int cy);

// Запуск сервера void ServerStart(HWND hWnd);

// Останов сервера void ServerStop(HWND hWnd);

// Обработка сообщения WSA_NETEVENT void WndProc_OnWSANetEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Порт сервера #define SERV_PORT 5000

#define IDS_STATUSBAR 802

// Определение кодов сообщений #define WSA_NETEVENT (WM_USER + 1)

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

// Идентификатор приложения HINSTANCE hInst;

// Название приложения char szAppName[] = "WServerUDP ";

// Заголовок главного окна приложения char szAppTitle[] = "Windows Socket UDP Server Demo";

// Идентификатор органа Statusbar HWND hwndSb;

// Сокет сервера SOCKET srv_socket ;

// ----------------------------------------------------- // Функция WinMain // -----------------------------------------------------

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;

hInst = hInstance;

// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если окно приложения было свернуто в пиктограмму, // восстанавливаем его if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE);

// Выдвигаем окно приложения на передний план SetForegroundWindow(hWnd); return FALSE; }

// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);



wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1); wc.lpszClassName = szAppName;

// Вызываем функцию RegisterClassEx, которая выполняет // регистрацию окна if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;

InitCommonControls();

// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);

// Отображаем окно ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }

// ----------------------------------------------------- // Функция WndProc // -----------------------------------------------------

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Вызываем обработчик сообщения WSA_NETEVENT case WSA_NETEVENT: WndProc_OnWSANetEvent(hWnd, msg, wParam, lParam); break;

HANDLE_MSG(hWnd, WM_CREATE , WndProc_OnCreate); HANDLE_MSG(hWnd, WM_COMMAND , WndProc_OnCommand); HANDLE_MSG(hWnd, WM_SIZE , WndProc_OnSize); HANDLE_MSG(hWnd, WM_DESTROY , WndProc_OnDestroy);

default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }

// ----------------------------------------------------- // Функция WndProc_OnCreate // -----------------------------------------------------

BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { int rc; WSADATA WSAData; char szTemp[128];

// Инициализация и проверка версии Windows Sockets rc = WSAStartup (MAKEWORD(1, 1), &WSAData); if(rc != 0) { MessageBox(NULL, "WSAStartup Error", "Error", MB_OK); return FALSE; }

// Отображаем описание и версию системы Windows Sockets // в окне органа управления Statusbar wsprintf(szTemp, "Server use %s %s", WSAData.szDescription,WSAData.szSystemStatus);



hwndSb = CreateStatusWindow(WS_CHILD | WS_VISIBLE | WS_BORDER | SBARS_SIZEGRIP, szTemp, hWnd, IDS_STATUSBAR);

return TRUE; }

// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { // Освобождение ресурсов, полученных для // работы с Windows Sockets WSACleanup ();

// Завершение цикла обработки сообщений PostQuitMessage(0); return FORWARD_WM_DESTROY (hWnd, DefWindowProc); }

// ----------------------------------------------------- // Функция WndProc_OnSize // -----------------------------------------------------

#pragma warning(disable: 4098) void WndProc_OnSize( HWND hWnd, UINT state, int cx, int cy) { SendMessage(hwndSb, WM_SIZE , cx, cy); return FORWARD_WM_SIZE (hWnd, state, cx, cy, DefWindowProc); }

// ----------------------------------------------------- // Функция WndProc_OnCommand // -----------------------------------------------------

#pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case IDM_EXIT:

// Уничтожение главного окна прилоджения DestroyWindow(hWnd); break;

case IDM_START:

// Запуск сервера ServerStart(hWnd); break;

case IDM_STOP:

// Останов сервера ServerStop(hWnd); break;

default: MessageBox(NULL, "Unknown command", "Error", MB_OK); }

return FORWARD_WM_COMMAND (hWnd, id, hwndCtl, codeNotify, DefWindowProc); }

// ----------------------------------------------------- // Функция ServerStart // -----------------------------------------------------

void ServerStart(HWND hWnd) { struct sockaddr_in srv_address; int rc;

// Создаем сокет сервера для работы с потоком данных srv_socket = socket(AF_INET , SOCK_DGRAM, 0); if(srv_socket == INVALID_SOCKET) { MessageBox(NULL, "socket Error", "Error", MB_OK); return; }

// Устанавливаем адрес IP и номер порта srv_address.sin_family = AF_INET ; srv_address.sin_addr .s_addr = INADDR_ANY ; srv_address.sin_port = htons(SERV_PORT);



// Связываем адрес IP с сокетом if(bind (srv_socket , (LPSOCKADDR )&srv_address, sizeof(srv_address)) == SOCKET_ERROR ) { // При ошибке закрываем сокет closesocket (srv_socket); MessageBox(NULL, "bind Error", "Error", MB_OK); return; }

// Если на данном сокете начнется передача данных от // клиента, в главное окно приложения поступит // сообщение WSA_NETEVENT. rc = WSAAsyncSelect (srv_socket , hWnd, WSA_NETEVENT, FD_READ ); if(rc > 0) { closesocket (srv_socket); MessageBox(NULL, "WSAAsyncSelect Error", "Error", MB_OK); return; }

// Выводим в окна Statusbar сообщение о запуске сервера SendMessage(hwndSb, SB_SETTEXT, 0, (LPARAM)"Server started"); }

// ----------------------------------------------------- // Функция ServerStop // -----------------------------------------------------

void ServerStop(HWND hWnd) { // Отменяем приход любых извещений в главную функцию // окна при возникновении любых событий, связанных // с системой Windows Sockets WSAAsyncSelect (srv_socket , hWnd, 0, 0);

// Если сокет был создан, закрываем его if(srv_socket != INVALID_SOCKET) { closesocket (srv_socket); }

// Выводим в окна Statusbar сообщение об останове сервера SendMessage(hwndSb, SB_SETTEXT, 0, (LPARAM)"Server stopped"); }

// ----------------------------------------------------- // Функция WndProc_OnWSANetEvent // -----------------------------------------------------

void WndProc_OnWSANetEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { char szTemp[256]; int rc; SOCKADDR _IN addr; int nAddrSize; char szBuf[80]; LPSTR lpAddr;

if(WSAGETSELECTEVENT(lParam) == FD_READ ) { // Принимаем данные rc = recv from((SOCKET)wParam, szTemp, 256, 0, (PSOCKADDR )&addr, &nAddrSize);

if(rc) { szTemp[rc] = '\0'; strcpy(szBuf, "Received from ");

// Преобразовываем адрес IP удаленного клиента // в текстовую строку lpAddr = inet_ntoa (addr.sin_addr ); strcat(szBuf, lpAddr);

// Отображаем адрес удаленного клиента // и полученную от него строку MessageBox(NULL, szTemp, szBuf, MB_OK); } return; } }



Приложение SERVERD во многом напоминает приложение SERVER, поэтому мы рассмотрим только отличия.

Первое отличие заключается в том, что при запуске сервера тип создаваемого сокета указывается как SOCK_DGRAM:

srv_socket = socket(AF_INET , SOCK_DGRAM, 0);

Далее выполняется инициализация сокета и его привязка к адресу, для чего вызывается функция bind . Эта операция, как и в случае протокола TCP, не обязательна.

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

rc = WSAAsyncSelect (srv_socket , hWnd, WSA_NETEVENT, FD_READ );

На этом инициализация сервера завершается.

Обработчик сообщения WSA_NETEVENT читает полученный пакет с помощью функции recv from:

SOCKADDR _IN addr; int nAddrSize; rc = recv from((SOCKET)wParam, szTemp, 256, 0, (PSOCKADDR )&addr, &nAddrSize);

В качестве предпоследнего параметра этой функции передается адрес структуры типа SOCKADDR _IN, куда функция записывает адрес узла, приславшего пакет. Последний параметр функции recv from должен содержать размер указанной структуры.

Ниже мы привели возможные коды ошибок для функции recv from.

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEFAULT Слишком малое значение параметра, определяющего размер буфера для приема данных
WSAEINTR Работа функции была отменена при помощи функции WSACancelBlockingCall
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEINVAL Сокет не был подключен функцией bind
WSAENOTSOCK Указанный дескриптор не является дескриптором сокета
WSAESHUTDOWN Сокет был закрыт функцией shutdown
WSAEWOULDBLOCK Сокет отмечен как неблокирующий, но запрошенная операция приведет к блокировке
WSAEMSGSIZE Размер датаграммы слишком большой, поэтому соответствующий блок данных не помещается в буфер. Принятый блок данных был обрезан
WSAECONNABORTED Сбой из-за слишком большой задержки или по другой причине
WSAECONNRESET Сброс соединения удаленным узлом
Заметим, что при обмене данных с использованием протокола UDP на каждый вызов функции send to должен приходиться один вызов функции recv from. Если же вы передается данные через канал с использованием протокола TCP, на один вызов функции send может приходиться несколько вызовов функции recv.

Для отображения адреса узла, пославшего пакет UDP , наше приложение преобразует этот адрес в символьную строку с помощью функции inet_ntoa :

lpAddr = inet_ntoa (addr.sin_addr );

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


Содержание раздела