Лабораторная работа № 6.




Написание простейших сетевых приложений

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

 

Краткие сведения из теории.

ИНТЕРФЕЙС WINDOWS SOCKETS, написание приложения сервер-клиент

Сокеты, датаграммы и каналы связи

В локальных и глобальных сетях существует два принципиально разных способа передачи данных.

Первый из них предполагает посылку пакетов данных от одного узла другому (или сразу нескольким узлам) без получения подтверждения о доставке и даже без гарантии того, что передаваемые пакеты будут получены в правильной последовательности. Примером такого протокола может служить протокол UDP (User Datagram Protocol), который используется в сетях TCP/IP, или протокол IPX, который является базовым в сетях Novell NetWare.

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

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

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

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

Для сокета необходимо указать три параметра. Это IP адрес, связанный с сокетом, номер порта, для которого будут выполняться операции передачи данных, а также тип сокета. Существуют сокеты двух типов. Первый тип предназначен для передачи данных в виде датаграмм, второй - с использованием каналов связи.

Инициализация приложения и завершение его работы

В процессе инициализации приложение должно зарегистрировать себя в библиотеке WSOCK32.DLL, которая предоставляет приложениям интерфейс Windows Sockets в среде операционных систем Microsoft Windows

Для инициализации необходимо вызвать функцию WSAStartup, определенную следующим образом:

int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData);

В параметре wVersionRequested вы должны указать версию интерфейса Windows Sockets, необходимую для работы вашего приложения. Старший байт параметра указывает младший номер версии (minor version), младший байт - старший номер версии (major version).

Перед вызовом функции WSAStartup параметр lpWSAData должен содержать указатель на структуру типа WSADATA, в которую будут записаны сведения о конкретной реализации интерфейса Windows Sockets.

В случае успеха функция WSAStartup возвращает нулевое значение

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

rc = WSAStartup (MAKEWORD(1, 1), &WSAData);

if(rc!= 0)

{

MessageBox(NULL, "WSAStartup Error", "Error", MB_OK);

return FALSE;

}

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

int WSACleanup (void);

 

Создание и инициализация сокета

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

Сокет создается с помощью функции socket, имеющей следующий прототип:

SOCKET socket (int af, int type, int protocol);

Параметр af определяет формат адреса. Для этого параметра вы должны указывать значение AF_INET, что соответствует формату адреса, принятому в Internet.

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

Можно указывать сокеты следующих двух типов:

SOCK_STREAM Сокет будет использован для передачи данных через канал связи с использованием протокола TCP

SOCK_DGRAM Передача данных будет выполняться без создания каналов связи через датаграммный протокол UDP

Что же касается параметра protocol, то вы можете указать для него нулевое значение.

В случае успеха функция socket возвращает дескриптор, который нужно использовать для выполнения всех операций над данным сокетом. Если же произошла ошибка, эта функция возвращает значение INVALID_SOCKET.

Фрагмент кода, в котором создается сокет для передачи данных с использованием протокола TCP:

srv_socket = socket(AF_INET, SOCK_STREAM, 0);

if(srv_socket == INVALID_SOCKET)

{

MessageBox(NULL, "socket Error", "Error", MB_OK);

return;

}

Для освобождения ресурсов приложение должно закрывать сокеты, которые ему больше не нужны, вызывая функцию closesocket:

int closesocket (SOCKET sock);

Перед использованием вы должны задать параметры сокета.

Для этого вы должны подготовить структуру типа sockaddr, определение которой показано ниже:

struct sockaddr

{

u_short sa_family;

char sa_data[14];

};

typedef struct sockaddr SOCKADDR;

typedef struct sockaddr *PSOCKADDR;

typedef struct sockaddr FAR *LPSOCKADDR;

Для работы с адресами в формате Internet используется другой вариант этой структуры, в котором детализируется формат поля sa_data:

struct sockaddr_in

{

short sin_family;

u_short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

typedef struct sockaddr_in SOCKADDR _IN;

typedef struct sockaddr_in *PSOCKADDR _IN;

typedef struct sockaddr_in FAR *LPSOCKADDR _IN;

Поле sin_family определяет тип адреса. Вы должны записать в это поле значение AF_INET, которое соответствует типу адреса, принятому в Internet:

srv_address.sin_family = AF_INET;

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

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

Особенностью поля sin_port является использование так называемого сетевого формата данных. Этот формат отличается от того, что принят в процессорах с архитектурой Intel, а именно, младшие байты данных хранятся по старшим адресам памяти. Напомним, что архитектура процессоров Intel подразумевает хранение старших байтов данных по младшим адресам.

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

Для выполнения преобразований из обычного формат в сетевой и обратно в интерфейсе Windows Sockets предусмотрен специальный набор функций. В частности, для заполнения поля sin_port нужно использовать функцию htons, выполняющую преобразование 16-разрядных данных из формата Intel в сетевой формат.

Ниже мы показали, как инициализируется поле sin_port в приложении SERVER, описанном далее:

 

#define SERV_PORT 5000

srv_address.sin_port = htons(SERV_PORT);

 

Вернемся снова к структуре sockaddr_in.

Поле sin_addr этой структуры представляет собой структуру in_addr:

 

struct in_addr

{

union

{

struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;

struct { u_short s_w1,s_w2; } S_un_w;

u_long S_addr;

} S_un;

};

#define s_addr S_un.S_addr

#define s_host S_un.S_un_b.s_b2

#define s_net S_un.S_un_b.s_b1

#define s_imp S_un.S_un_w.s_w2

#define s_impno S_un.S_un_b.s_b4

#define s_lh S_un.S_un_b.s_b3

 

При инициализации сокета в этой структуре вы должны указать адрес IP, с которым будет работать данный сокет.

Если сокет будет работать с любым адресом (например, вы создаете сервер, который будет доступен из узлов с любым адресом), адрес для сокета можно

указать следующим образом:

 

srv_address.sin_addr.s_addr = INADDR_ANY;

 

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

Датаграммный протокол UDP позволяет посылать пакеты данных одновременно всем рабочим станциям в широковещательном режиме. Для этого вы должны указать адрес как INADDR_BROADCAST.

Если вам известен адрес в виде четырех десятичных чисел, разделенных точкой (именно так его вводит пользователь), то вы можете заполнить поле

адреса при помощи функции inet_addr:

 

dest_sin.sin_addr.s_addr = inet_addr ("200.200.200.201");

 

В случае ошибки функция возвращает значение INADDR_NONE, что можно использовать для проверки.

Обратное преобразование адреса IP в текстовую строку можно при необходимости легко выполнить с помощью функции inet_ntoa, имеющей

следующий прототип:

 

char FAR * inet_ntoa (struct in_addr in);

 

При ошибке эта функция возвращает значение NULL.

Однако чаще всего пользователь работает с доменными именами, используя сервер DNS или файл HOSTS. В этом случае вначале вы должны воспользоваться функцией gethostbyname, возвращающей адрес IP, а затем записать полученный адрес в структуру sin_addr:

 

PHOSTENT phe;

phe = gethostbyname ("ftp.microsoft.com");

if(phe == NULL)

{

closesocket (srv_socket);

MessageBox(NULL, "gethostbyname Error", "Error", MB_OK);

return;

}

memcpy((char FAR *)&(dest_sin.sin_addr),

phe->h_addr, phe->h_length);

 

В случае ошибки функция gethostbyname возвращает NULL. При этом причину ошибки можно выяснить, проверив код возврата функции WSAGetLastError.

Если же указанный узел найден в базе DNS или в файле HOSTS, функция gethostbyname возвращает указатель на структуру hostent, описанную ниже:

 

struct hostent

{

char FAR * h_name; // имя узла

char FAR * FAR * h_aliases; // список альтернативных имен

short h_addr type; // тип адреса узла

short h_length; // длина адреса

char FAR * FAR * h_addr _list; // список адресов

#define h_addr h_addr_list[0] // адрес

};

typedef struct hostent *PHOSTENT;

typedef struct hostent FAR *LPHOSTENT;

 

Искомый адрес находится в первом элемента списка h_addr _list[0], на который можно также ссылаться при помощи h_addr. Длина поля адреса находится в поле h_length.

Привязка адреса к сокету

После того как вы подготовили структуру SOCKADDR, записав в нее параметры сокета (в частности, адрес), следует выполнить привязку адреса к сокету при помощи функции bind:

 

int bind (SOCKET sock, const struct sockaddr FAR * addr, int namelen);

 

Параметр sock должен содержать дескриптор сокета, созданного функцией socket.

В поле addr следует записать указатель на подготовленную структуру SOCKADDR, а в поле namelen - размер этой структуры.

В случае ошибки функция bind возвращает значение SOCKET_ERROR.

Пример вызова функции bind показан ниже:

 

if(bind (srv_socket, (LPSOCKADDR)&srv_address,

sizeof(srv_address)) == SOCKET_ERROR)

{

closesocket (srv_socket);

MessageBox(NULL, "bind Error", "Error", MB_OK);

return;

}

Создание канала связи

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

Сторона сервера

Рассмотрим процедуру создания канала связи со стороны сервера.

Прежде всего вы должны переключить сокет в режим приема для выполнения ожидания соединения с клиентом при помощи функции listen:

 

int listen(SOCKET sock, int backlog);

 

Через параметр sock функции необходимо передать дескриптор сокета, который будет использован для создания канала. Параметр backlog задает максимальный размер очереди для ожидания соединения (можно указывать значения от 1 до 5). Очередь содержит запросы на установку соединений для каждой пары значений (адрес IP, порт).

Пример вызов функции listen:

 

if(listen(srv_socket, 1) == SOCKET_ERROR)

{

closesocket (srv_socket);

MessageBox(NULL, "listen Error", "Error", MB_OK);

return;

}

 

Далее необходимо выполнить ожидание соединения. Это можно выполнить двумя различными способами.

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

Функция accept имеет следующий прототип:

 

SOCKET accept (SOCKET sock, struct sockaddr FAR * addr,

int FAR * addrlen);

 

Через параметр sock необходимо указать дескриптор сокета, который находится в режиме приема для выполнения ожидания.

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

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

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

 

#define WSA_ACCEPT (WM_USER + 1)

// При попытке установки соединения главное окно приложения

// получит сообщение 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;

}

 

В данном случае ожидание соединения выполняется для сокета srv_socket. Последний параметр функции имеет значение FD_ACCEPT. Это означает, что при попытке создания канала связи функция окна с идентификатором hWnd получит сообщение WSA_ACCEPT, определенное в вашем приложении.

Обработчик этого сообщения может выглядеть, например, следующим образом:

 

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;

}

}

 

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

Сторона клиента

Рассмотрим процедуру установки канала связи со стороны клиента, использованную нами в приложении CLIENT, исходные тексты которого будут приведены ниже.

Для установки соединения в приложении используется функция SetConnection:

 

SOCKADDR _IN dest_sin;

void SetConnection(HWND hWnd)

{

PHOSTENT phe;

 

// Создаем сокет

srv_socket = socket(AF_INET, SOCK_STREAM, 0);

if(srv_socket == INVALID_SOCKET)

{

MessageBox(NULL, "socket Error", "Error", MB_OK);

return;

}

 

// Устанавливаем адрес IP и номер порта

dest_sin.sin_family = AF_INET;

 

// Определяем адрес узла

phe = gethostbyname ("localhost ");

if(phe == NULL)

{

closesocket (srv_socket);

MessageBox(NULL, "gethostbyname Error", "Error", MB_OK);

return;

}

 

// Копируем адрес узла

memcpy((char FAR *)&(dest_sin.sin_addr), phe->h_addr,

phe->h_length);

 

// Копируем номер порта

dest_sin.sin_port = htons(SERV_PORT);

 

// Устанавливаем соединение

if(connect(srv_socket, (PSOCKADDR)&dest_sin,

sizeof(dest_sin)) < 0)

{

closesocket (srv_socket);

MessageBox(NULL, "connect Error", "Error", MB_OK);

return;

}

}

 

Вначале с помощью функции socket эта функция создает сокет. Затем выполняется заполнение адресной информацией структуры dest_sin.

Обратите внимание, что для получения адреса IP мы воспользовались функцией gethostbyname, указав ей имя узла localhost.

Это имя отображается в файле HOSTS на адрес 127.0.0.1:

1. localhost

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

После заполнения структуры с адресной информацией функция connect создает канал связи с сервером.

5.5. Передача и прием данных

После того как канал создан, можно начинать передачу данных. Для передачи данных при помощи протокола гарантированной доставки TCP вы можете воспользоваться функциями send и recv, которые входят в программный интерфейс Windows Sockets.

Функция передачи данных send имеет три параметра - дескриптор сокета sock, на котором выполняется передача, адрес буфера buf, содержащего передаваемое сообщение, размер этого буфера bufsize и флаги flags:

 

int send (SOCKET sock, const char FAR* buf, int bufsize, int flags);

 

В нашем приложении CLIENT мы передаем данные серверу следующим образом:

 

char szBuf[80];

lstrcpy(szBuf, "Test string");

send (srv_socket, szBuf, lstrlen(szBuf), 0);

 

Параметры функции recv, предназначенной для приема данных, аналогичны параметрам функции send:

 

int recv (SOCKET sock, char FAR * buf, int bufsize, int flags);

 

Заметим, что функции recv и send возвращают количество, соответственно, принятых и переданных байт данных. Приложение, которое принимает данные, должно вызывать функцию recv в цикле до тех пор, пока не будут приняты все переданные данные. При этом на один вызов функции send может приходиться несколько вызовов функции recv.

В случае ошибки обе эти функции возвращают значение SOCKET_ERROR.

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

Наше приложение SERVER демонстрирует асинхронный прием данных.

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

#define WSA_NETEVENT (WM_USER + 2)

rc = WSAAsyncSelect (srv_socket, hWnd, WSA_NETEVENT,

FD_READ | FD_CLOSE);

 

При необходимости выполнения асинхронной посылки данных вы можете указать функции WSAAsyncSelect еще и параметр FD_WRITE.

Если функция WSAAsyncSelect выполнилась успешно, она возвращает нулевое значение, при ошибке - значение SOCKET_ERROR.

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

 

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);

}

}

 

Отметим, что параметр wParam содержит дескриптор сокета, на котором выполняется передача данных, а параметр lParam - код события, которое произошло в сети.

Приложение SERVER

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

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

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

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

#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, которой можно пользоваться для

// регистрации класса окна

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 сохраняет идентификатор приложения и затем проверяет, не было ли это приложение уже запущено.

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

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

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

Обработчик сообщения 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 создается автоматически и содержит описание идентификаторов ресурсов приложения.

Файл 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

Файл 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

 

///////////////////////////////////////////////////////

 

#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

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-04-27 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: