Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Глобальные сети компьютеров. Практическое введение в Internet, E-Mail, FTP, WWW и HTML, программирование для Windows Sockets

© Александр Фролов, Григорий Фролов
Том 23, М.: Диалог-МИФИ, 1993, 283 стр.

[Назад] [Содеожание] [Дальше]

5.8. Приложение 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.

[Назад] [Содеожание] [Дальше]