Приложение 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 , наше приложение преобразует этот адрес в символьную строку с помощью функции inet_ntoa :
lpAddr = inet_ntoa (addr.sin_addr );
Эта функция записывает полученную строку в статическую область памяти, принадлежащую системе Windows Sockets, поэтому для дальнейшего использования необходимо скопировать строку до следующего вызова любой функции программного интерфейса Windows Sockets.