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

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

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

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

5.6. Приложение SERVER

В этом разделе мы представим вам исходные тексты приложения SERVER, которое выполняет прием сообщений от приложения CLIENT с использованием протокола гарантированной доставки TCP и канала связи. При необходимости вы сможете самостоятельно организовать передачу данных в обратном направлении.

Вы можете запускать приложения SERVER и CLIENT как на одном, так и на разных компьютерах, соединенных локальной или глобальной сетью TCP/IP. В случае запуска этих приложений на одном и том же компьютере в качестве адреса IP используется локальный тестовый адрес 127.0.0.1 .

Создавая проект для этого, а также всех остальных приложений, приведенных в нашей книге, вы должны указать, что для разрешения внешних ссылок необходимо использовать библиотеки объектных модулей wsock32.lib и comctl32.lib . Первая из них нужна для работы с программным интерфейсом Windows Sockets, вторая - для работы с органом управления Statusbar .

Для подключения указанных библиотек из меню Build системы разработки Microsoft Visual C++ версии 4.0 нужно выбрать строку Settings. На экране появится блокнот project Settings, который следует открыть на странице Link. Затем вы должны дописать названия библиотек в поле Object/library modules и нажать кнопку OK.

Исходный текст приложения SERVER представлен в листинге 5.1.

Листинг 5.1. Файл server/server.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_ACCEPT
void WndProc_OnWSAAccept(HWND hWnd, UINT msg, 
                         WPARAM wParam, LPARAM lParam);

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

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

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

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

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

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

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

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

// Длина использованного сокета
int acc_sin_len;

// Адрес использованного сокета
SOCKADDR _IN acc_sin;

// Локальный сокет
SOCKADDR _IN local_sin;

// -----------------------------------------------------
// Функция 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	и wc.hIconSm определены в структуре
  // WNDCLASSEX, которой можно пользоваться для
  // регистрации класса окна в Windows 95
  wc.cbSize = sizeof(WNDCLASSEX);

  // Поле wc.hIconSm задает идентификатор маленькой
  // пиктограммы, которая будет отображаться в левой
  // части заголовка окна (в области системного меню).
  // Загружаем пиктограмму из ресурсов приложения при
  // помощи функции LoadImage, так как функция
  // LoadIcon может загрузить только обычную пиктограмму
  wc.hIconSm = LoadImage(hInst,
    MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0);
  
  // Завершаем заполнение структуры WNDCLASSEX
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC)WndProc;
  wc.cbClsExtra  = 0;
  wc.cbWndExtra  = 0;
  wc.hInstance = hInst;
  
  // Для загрузки обычной пиктограммы вы можете
  // использовать функцию LoadImage
  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))

    // В случае ошибки пытаемся зарегистрировать окно
    // функцией RegisterClass
    if(!RegisterClass((LPWNDCLASS)&wc.style))
	  return FALSE;
    
  // Инициализация библиотеки органов управления
  // общего назначения. Необходима для работы с
  // органом управления Statusbar 
  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_ACCEPT
    case WSA_ACCEPT:
      WndProc_OnWSAAccept(hWnd, msg, wParam, lParam);
      break;
    
    // Вызываем обработчик сообщения WSA_NETEVENT
    case WSA_NETEVENT:
      WndProc_OnWSANetEvent(hWnd, msg, wParam, lParam);
      break;
    
    // Для сообщения WM_CREATE  назначаем обработчик,
    // расположенный в функции WndProc_OnCreate
    HANDLE_MSG(hWnd, WM_CREATE , WndProc_OnCreate);

    // Для сообщения WM_COMMAND  назначаем обработчик,
    // расположенный в функции WndProc_OnCommand
    HANDLE_MSG(hWnd, WM_COMMAND , WndProc_OnCommand);

    // Для сообщения WM_SIZE  назначаем обработчик,
    // расположенный в функции WndProc_OnSize
    HANDLE_MSG(hWnd, WM_SIZE , WndProc_OnSize);

    // Для сообщения WM_DESTROY  назначаем обработчик,
    // расположенный в функции WndProc_OnDestroy
    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
// -----------------------------------------------------

// Отключаем предупреждающее сообщение о том, что
// функция типа void возвращает управление при помощи
// оператора return. Этот оператор нужен для
// использования макрокоманды FORWARD_WM_LBUTTONDOWN
#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_STREAM, 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;
  }

  // Устанавливаем сокет в режим приема для
  // выполнения ожидания соединения с клиентом
  if(listen(srv_socket , 1) == SOCKET_ERROR )
  {
    closesocket  (srv_socket);
    MessageBox(NULL, "listen Error", "Error", MB_OK);
    return;
  }

  // При попытке установки соединения главное окно приложения
  // получит сообщение WSA_ACCEPT
  rc = WSAAsyncSelect (srv_socket , hWnd, WSA_ACCEPT, FD_ACCEPT );
  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_OnWSAAccept
// -----------------------------------------------------

void WndProc_OnWSAAccept(HWND hWnd, UINT msg, 
                         WPARAM wParam, LPARAM lParam)
{
  int rc;

  // При ошибке отменяем поступление извещений
  // в главное окно приложения
  if(WSAGETSELECTERROR(lParam) != 0)
  {
    MessageBox(NULL, "accept  Error", "Error", MB_OK);
    WSAAsyncSelect (srv_socket , hWnd, 0, 0);
    return;
  }

  // Определяем размер адреса сокета
  acc_sin_len = sizeof(acc_sin);

  // Разрешаем установку соединения
  srv_socket  = accept (srv_socket, (LPSOCKADDR   )&acc_sin,
    (int FAR *)&acc_sin_len);

  if(srv_socket  == INVALID_SOCKET)
  {
    MessageBox(NULL, "accept  Error, invalid socket ",
      "Error", MB_OK);
    return;
  }

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

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

void WndProc_OnWSANetEvent(HWND hWnd, UINT msg, 
                       WPARAM wParam, LPARAM lParam)
{
  char szTemp[256];
  int rc;
  
  // Если на сокете выполняется передача данных,
  // принимаем и отображаем эти данные в виде
  // текстовой строки
  if(WSAGETSELECTEVENT(lParam) == FD_READ )
  {
    rc = recv ((SOCKET)wParam, szTemp, 256, 0);
    if(rc)
    {
      szTemp[rc] = '\0';
      MessageBox(NULL, szTemp, "Reсeived data", MB_OK);
    }
    return;
  }
  
  // Если соединение завершено, выводми об этом сообщение
  else if(WSAGETSELECTEVENT(lParam) == FD_CLOSE )
  {
    MessageBox(NULL, "Connection closed", "Server", MB_OK);
  }
}

Функция WinMain сохраняет идентификатор приложения и затем проверяет, не было ли это приложение уже запущено. При этом используется техника, описанная нами в 22 томе "Библиотеки системного программиста", который называется "Операционная система Windows 95 для программиста".

Далее выполняется обычная регистрация класса главного окна приложения, инициализируется библиотека орагнов управления общего назначения и создается главное окно приложения. После этого окно отображается на экране и запускается цикл обработки сообщений.

Функция окна WndProc обрабатывает как стандартные сообщения WM_CREATE , WM_COMMAND , WM_SIZE , WM_DESTROY , так и сообщения WSA_ACCEPT и WSA_NETEVENT. Первое из них возникает при установке канала связи с клиентом, второе - при поступлении данных от клиента и при разрыве канала связи.

Обработчик сообщения WM_CREATE инициализирует библиотеку Windows Sockets и создает орган управления Statusbar . В окне этого органа управления отображается текущая версия и описание состояния системы Windows Sockets. Если вы не знакомы с указанным органам управления, отсылаем вас к упомянутому 22 тому "Библиотеки системного программиста".

Обработчик сообщения WM_DESTROY вызывает функцию WSACleanup , освобождающую ресурсы, полученные для приложения у системы Windows Sockets и затем завершает цикл обработки сообщений.

Единственное назначение обработчика сообщений WM_SIZE заключается в изменении размеров окна органа управления Statusbar при изменении размеров главного окна приложения.

Обработчик сообщения WM_COMMAND получает управление, когда пользователь выбирает одну из строк в меню File главного меню приложения. Если пользователь выберет строку Start server, будет вызвана функция ServerStart, назначение которой очевидно из ее названия. Аналогично, при выборе строки Stop server будет вызвана функция ServerStop. Если же из меню File выбрать строку Exit, будет уничтожено главное окно приложения.

Функция ServerStart создает сокет для работы с потоком данных и инициализирует его. При этом мы используем произвольно выбранный порт с номером 5000.

Далее функция выполняет привязку сокета к адресу и переключает сокет в режим приема для выполнения ожидания соединения с клиентом, т. е. выполняет описанные нами ранее действия, необходимые для создания канала связи.

Затем вызывается функция WSAAsyncSelect , которой в качестве последнего параметра передается значение FD_ACCEPT , а в качестве предпоследнего - значение WSA_ACCEPT. В результате при поступлении от клиента запроса на создание канала связи функция главного окна приложения получит сообщение WSA_ACCEPT.

Перед возвратом управления функция ServerStart выводит сообщение о запуске сервера в окне органа управления Statusbar .

Функция ServerStop отменяет все извещения, поступающие в главное окно приложения при возникновении событий в сети, вызывая функцию WSAAsyncSelect с нулевым значением двух последних параметров:

WSAAsyncSelect (srv_socket , hWnd, 0, 0);

Затем она закрывает сокет, вызывая функцию closesocket , и выводит в окне органа управления Statusbar сообщение о завершении работы сервера.

Функция WndProc_OnWSAAccept обрабатывает сообщение WSA_ACCEPT, выполняя описанную нами ранее процедуру создания канала связи.

И, наконец, функция WndProc_OnWSANetEvent выполняет прием строки сообщения, полученной от клиента с отображением этой строки на экране в диалоговой панели.

Файл resource.h, показанный в листинге 5.2 создается автоматически и содержит описание идентификаторов ресурсов приложения.

Листинг 5.2. Файл server/resource.h


//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by server.rc
//
#define IDI_APPICON                     101
#define IDI_APPICON_SM                  102
#define IDR_MENU1                       105
#define IDM_START                       40001
#define IDM_EXIT                        40002
#define IDM_STOP                        40003

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        106
#define _APS_NEXT_COMMAND_VALUE         40004
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Ресурсы приложения определены в файле server.rc, который представлен в листинге 5.3.

Листинг 5.3. Файл server/server.rc


//Microsoft Developer Studio generated resource script.
//
#include "resource.h"

#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"

/////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS

/////////////////////////////////////////////////////////////////////
// Russian resources

#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
#ifdef _WIN32 
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
#pragma code_page(1251)
#endif //_WIN32 

#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//

1 TEXTINCLUDE DISCARDABLE 
BEGIN
    "resource.h\0"
END

2 TEXTINCLUDE DISCARDABLE 
BEGIN
    "#include ""afxres.h""\r\n"
    "\0"
END

3 TEXTINCLUDE DISCARDABLE 
BEGIN
    "\r\n"
    "\0"
END

#endif    // APSTUDIO_INVOKED

/////////////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_APPICON             ICON    DISCARDABLE     "server.ico"
IDI_APPICON_SM          ICON    DISCARDABLE     "serversm.ico"

/////////////////////////////////////////////////////////////////////
//
// Menu
//

IDR_MENU1 MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Start server",               IDM_START
        MENUITEM "S&top server",                IDM_STOP
        MENUITEM SEPARATOR
        MENUITEM "&Exit",                       IDM_EXIT
    END
END

#endif    // Russian resources
/////////////////////////////////////////////////////////////////////

#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//

/////////////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

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