5. Интерфейс Windows Sockets

Во второй главе нашей книги мы рассказали вам о том, как установить соединение с использованием протокола TCP/IP. Вы также научились работать в Internet в среде операционных систем Microsoft Windows 95, Microsoft Windows NT, а также OS/2 Warp Connect.

Нашей следующей задачей будет освоение основ программирования для сетей TCP/IP. Теперь мы расскажем вам о том, как составлять сетевые приложения, выполняющие обмен данными по глобальным и локальным сетям с использованием данного протокола. При этом из-за недостатка места в книге мы ограничимся описанием программного интерфейса Windows Sockets, который имеется в операционных системах Microsoft Windows 95 и Microsoft Windows NT.

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

В восьмом томе "Библиотеки системного программиста", который называется "Локальные сети персональных компьютеров. Использование протоколов IPX, SPX, NETBIOS "  мы приводили соновные сведения об использовании протоколов IPX, SPX и NETBIOS в программах, передающих данные по сети Novell NetWare. Интерфейс Windows Sockets позволяет передавать данные не только с использованием протокола TCP/IP, но и других протоколов, например, IPX/SPX. Вы можете узнать об этом подробнее из документации, которая поставляется в составе SDK для программного интерфейса WIN32.

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

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

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

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

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

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

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

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

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

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

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

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

int WSAStartup (WORD wVersionRequested, LPWSADATA  lpWSAData); 

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

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

В случае успеха функция WSAStartup возвращает нулевое значение. Если происходит ошибка, возвращается одно из следующих значений:

Значение Описание
WSASYSNOTREADY Сетевое программное обеспечение не готово для работы
WSAVERNOTSUPPORTED Функция не поддерживается данной реализацией интерфейса Windows Sockets
WSAEINVAL Библиотека DLL, обеспечивающая интерфейсe Windows Sockets, не соответствуетверсии, указанной приложением указанной в параметре wVersionRequested

Ниже мы представили фрагмент исходного текста приложения SERVER, которое будет описано ниже, выполняющий инициализацию интерфейса 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);
 

В операционных системах Microsoft Windows 95 и Microsoft Windows NT версии 3.51 встроена система Windows Sockets версии 1.1, поэтому именно это значение мы указали при вызове функции WSAStartup.

В следующих нескольких строках только что приведенного фрагмента кода содержимое двух полей структуры типа WSADATA отображается в окне органа управления Statusbar.

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

typedef struct WSAData 
{
  WORD           wVersion;
  WORD           wHighVersion;
  char           szDescription[WSADESCRIPTION_LEN+1];
  char           szSystemStatus[WSASYS_STATUS_LEN+1];
  unsigned short iMaxSockets;
  unsigned short iMaxUdpDg;
  char FAR *     lpVendorInfo;
} WSADATA ;
typedef WSADATA  FAR *LPWSADATA;
 

Использованные выше поля szDescription и szSystemStatus после вызова функции WSAStartup содержат, соответственно, описание конкретной реализации интерфейса Windows Socket и текущее состояние этого интерфейса в виде текстовых строк.

В полях wVersion и wHighVersion записаны, соответственно, версия спецификации Windows Socket, которую будет использовать приложение, и версия спецификации, которой соответствует конкретная реализация интерфейса Windows Socket.

Приложение может одновременно создавать несколько сокетов, например, для использования в разных подзадачах одного процесса. В поле iMaxSockets хранится максимальное количество сокетов, которое можно получить для одного процесса.

В поле iMaxUdpDg записан максимальный размер пакета данных, который можно переслать с использованием датаграммного протокола UDP.

И, наконец, поле lpVendorInfo содержит указатель на дополнительную информацию, формат которой зависит от фирмы-изготовителя конкретной реализации системы Windows Sockets.

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

int WSACleanup (void); 

Эта функция может возвратить нулевое значение при успехе или значение SOCKET_ERROR в случае ошибки.

Для получения кода ошибки вы должны воспользоваться функцией с именем WSAGetLastError :

int WSAGetLastError (void); 

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

Если ошибка возникла при выполнении функции WSACleanup, функция WSAGetLastError может вернуть одно из следующих значений:

Значение Описание
WSANOTINITIALISED Интерфейс Windows Sockets не был проинициализирован функцией WSAStartup
WSAENETDOWN Сбой сетевого программного обеспечения
WSAEINPROGRESS Во время вызыва функции WSACleanup выполнялась одна из блокирующих функций интерфейса Windows Sockets

Сделаем небольшие пояснения относительно последней ошибки, приведенной в этом списке, и имеющей код WSAEINPROGRESS.

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

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

После инициализации интерфейса 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. Для анализа причины ошибки вы должны вызвать функцию WSAGetLastError, которая в данном случае может вернуть один из следующих кодов ошибки:

Код ошибки Описание
WSANOTINITIALISED Интерфейс Windows Sockets не был проинициализирован функцией WSAStartup
WSAENETDOWN Сбой сетевого программного обеспечения
WSAEAFNOSUPPORT Указан неправильный тип адреса
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEMFILE Израсходован весь запас свободных дескрипторов
WSAENOBUFS Нет памяти для создания буфера
WSAEPROTONOSUPPORT Указан неправильный протокол
WSAEPROTOTYPE Указанный протокол несовместим с данным типом сокета
WSAESOCKTNOSUPPORT Указанный тип сокета несовместим с данным типом адреса

Ниже мы привели фрагмент кода, в котором создается сокет для передачи данных с использованием протокола 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); 

Ниже мы перечислили коды ошибок для этой функции :

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции closesocket необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEINTR Работа функции была отменена при помощи функции WSACancelBlockingCall

Параметры сокета

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

Для этого вы должны подготовить структуру типа 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. Дальнейший анализ причин ошибки следует выполнять при помощи функции WSAGetLastError. Возможные коды ошибок перечислены ниже:

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEADDRINUSE Указанный адрес уже используется
WSAEFAULT Значение параметра namelen меньше размера структуры sockaddr
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEAFNOSUPPORT Этот протокол не может работать с указанным семейством адресов
WSAEINVAL Сокет уже привязан к адресу
WSAENOBUFS Установлено слишком много соединений
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом

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

if(bind (srv_socket, (LPSOCKADDR   )&srv_address, 
  sizeof(srv_address)) == SOCKET_ERROR )
{
  closesocket  (srv_socket);
  MessageBox(NULL, "bind  Error", "Error", MB_OK);
  return;
}
 

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

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

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

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

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

int listen(SOCKET sock, int backlog); 

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

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

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEADDRINUSE Указанный адрес уже используется
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEINVAL Сокет еще не был привязан к адресу или уже находится в подключенном состоянии
WSAEISCONN Сокет уже находится в подключенном состоянии
WSAEMFILE Недостаточно дескрипторов файлов
WSAENOBUFS Нет места для размещения буфера
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом
WSAEOPNOTSUPP Функция listen не работает с сокетом указанного типа

Ниже мы привели пример вызов функции 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.

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEFAULT Значение параметра addrlen меньше размера структуры адреса
WSAEINTR Работа функции была отменена при помощи функции WSACancelBlockingCall
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEINVAL Перед вызовом функции accept не была вызывана функция listen
WSAEMFILE Нет доступных дескрипторов
WSAENOBUFS Установлено слишком много соединений
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом
WSAEOPNOTSUPP Данный тип сокета нельзя использовать при вызове функций, ориентированных на работу с каналом связи
WSAEWOULDBLOCK Сокет отмечен как неблокирующий и в настоящее время нет каналов связи, которые нужно устанавливать

Вместо того чтобы ожидать соединение, вызывая в цикле функцию 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. Для анализа причин возникновения ошибки следует воспользоваться функцией WSAGetLastError.

Приведем список кодов ошибок, которые могут возникать при вызове команды send:

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEACCES Указанный адрес является широковещательным (broadcast ), однако перед вызовом функции не был установлен соответствующий флаг
WSAEINTR Работа функции была отменена при помощи функции WSACancelBlockingCall
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEFAULT Параметр buf указан неправильно (он не указывает на адресное пространство, принадлежащее приложению)
WSAENETRESET Необходимо сбросить соединение
WSAENOBUFS Возникла блокировка буфера
WSAENOTCONN Сокет не подсоединен
WSAENOTSOCK Указанный в параметре дескриптор не является сокетом
WSAESHUTDOWN Сокет был закрыт функцией shutdown
WSAEWOULDBLOCK Сокет отмечен как неблокирующий, но запрошенная операция приведет к блокировке
WSAEMSGSIZE Был использован сокет типа SOCK_DGRAM (предназначенный для передачи датаграмм). При этом размер пакета данных превышает максимально допустимый для данной реализации интерфейса Windows Sockets
WSAEINVAL Сокет не был подключен функцией bind
WSAECONNABORTED Сбой из-за слишком большой задержки или по другой причине
WSAECONNRESET Сброс соединения удаленным узлом

При выполнении функции recv могут возникать следующие ошибки:

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

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

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

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEINVAL Сокет не был подключен функцией bind
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets

Дополнительный код ошибки можно получить из параметра lParam при помощи макрокоманды WSAGETSELECTERROR.

При использовании параметра FD_CONNECT возможно появление следующих ошибок:

Код ошибки Описание
WSAEADDRINUSE Указанный адрес уже используется
WSAEADDRNOTAVAIL Указанный адрес не доступен
WSAEAFNOSUPPORT Для данного сокета нельзя использовать указанное семейство адресов
WSAECONNREFUSED Попытка установления канала связи была отвергнута
WSAEDESTADDRREQ Необходимо указать адрес получателя пакета
WSAEFAULT Неправильно указан параметр namelen
WSAEINVAL Сокет уже подключен к адресу
WSAEISCONN Сокет уже подсоединен
WSAEMFILE Больше нет доступных дескрипторов
WSAENETUNREACH Из данного узла и в данное время невозможно получить доступ к сети
WSAENOBUFS Нет места для размещения буфера
WSAENOTCONN Сокет на подключен
WSAENOTSOCK Указан дескриптор файла, а не сокета
WSAETIMEDOUT При попытке установления канала связи возникла задержка во времени

Если используется параметр FD_CLOSE, может возникнуть одна из следующих ошибок:

Код ошибки Описание
WSAENETDOWN Сбой в сети
WSAECONNRESET Сброс соединения удаленным узлом
WSAECONNABORTED Сбой из-за слишком большой задержки или по другой причине

В том случае, когда указаны параметры FD_READ, FD_WRITE, FD_OOB, или FD_ACCEPT, может возникнуть ошибка с кодом WSAENETDOWN.

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

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
 

5.7. Приложение CLIENT

Исходные тексты приложения CLIENT, предназначенного для совместного использования с только что описанным приложением SERVER, приведены в листинге 5.4.

После запуска этого приложения вы должны создать канал связи с приложением SERVER, выбрав из меню File строку Connect, после чего можно посылать сообщение Test message, выбирая из этого же меню строку Send Message.

Сервер, получив сообщение, отобразит его на экране в отдельной диалоговой панели.

Листинг 5.4. Файл client/client.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 SetConnection(HWND hWnd);

// Передача сообщения
void SendMsg(HWND hWnd);

// Порт сервера
#define SERV_PORT 5000

#define IDS_STATUSBAR 802

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

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

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

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

// Идентификатор органа управления Statusbar 
HWND hwndSb;

// Сокет клиента
SOCKET srv_socket ;

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

// Адрес сервера
SOCKADDR _IN dest_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)
  {
    // Для сообщения 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
// -----------------------------------------------------

#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_CONNECT:
      
      // Установка соединения с сервером
      SetConnection(hWnd);
      break;

    case IDM_SEND:
      
      // Посылка сообщения серверу
      SendMsg(hWnd);
      break;

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

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

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

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 ");
  
  // Адрес удаленного узла
  //phe = gethostbyname ("frolov");
  
  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_addr.s_addr = inet_addr ("200.200.200.201");

  // Копируем номер порта
  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;
  }

  // В случае успеха выводим сообщение об установке
  // соединения с узлом
  SendMessage(hwndSb, SB_SETTEXT, 0, 
    (LPARAM)"Connected");
}

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

void SendMsg(HWND hWnd)
{
	char szBuf[80];
	lstrcpy(szBuf, "Test string");

  // Посылаем сообщение
  send (srv_socket, szBuf, lstrlen(szBuf), 0);
}
 

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

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

phe = gethostbyname ("localhost "); 

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

//phe = gethostbyname ("frolov"); 

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

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

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

Идентификаторы ресурсов приложения CLIENT определены в файле resource.h, представленном в листинге 5.5.

Листинг 5.5. Файл client/resource.h

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by CLIENT.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
#define IDM_CONNECT                     40004
#define IDM_SEND                        40005

// 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         40006
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif
 

Файл client.rc (листинг 5.6) содержит определения ресурсов приложения.

Листинг 5.6. Файл client/client.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     "client.ico"
IDI_APPICON_SM          ICON    DISCARDABLE     "clientsm.ico"

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

IDR_MENU1 MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Connect",                    IDM_CONNECT
        MENUITEM "S&end message",               IDM_SEND
        MENUITEM SEPARATOR
        MENUITEM "&Exit",                       IDM_EXIT
    END
END

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



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

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

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.

5.9. Приложение CLIENTD

Исходные тексты приложения CLIENTD, предназначенного для совместной работы с приложением SERVERD, представлены в листинге 5.8. Так как это приложение очень похоже на приложение CLIENT, мы опишем только отличия.

Листинг 5.8. Файл clientd/clientd.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 SetConnection(HWND hWnd);

// Передача сообщения
void SendMsg(HWND hWnd);

// Порт сервера
#define SERV_PORT 5000

#define IDS_STATUSBAR 802

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

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

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

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

// Идентификатор органа управления Statusbar 
HWND hwndSb;

// Сокет клиента
SOCKET srv_socket ;

// Адрес сервера
SOCKADDR _IN dest_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 = 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)
  {
    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_CONNECT:
      
      // Установка соединения с сервером
      SetConnection(hWnd);
      break;

    case IDM_SEND:
      
      // Посылка сообщения серверу
      SendMsg(hWnd);
      break;

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

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

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

void SetConnection(HWND hWnd)
{
  PHOSTENT  phe;
	
  // Создаем сокет 
  srv_socket  = socket(AF_INET, SOCK_DGRAM, 0);
  if(srv_socket  == INVALID_SOCKET)
  {
    MessageBox(NULL, "socket  Error", "Error", MB_OK);
    return;
  }

  // Связываем адрес IP с сокетом  
  dest_sin.sin_family = AF_INET ;
  dest_sin.sin_addr.s_addr = INADDR_ANY ;
  dest_sin.sin_port = 0;
   
  if(bind (srv_socket, (LPSOCKADDR   )&dest_sin, 
    sizeof(dest_sin)) == SOCKET_ERROR )
  {
    // При ошибке закрываем сокет
    closesocket  (srv_socket);
    MessageBox(NULL, "bind  Error", "Error", MB_OK);
    return;
  }
  
  // Устанавливаем адрес IP и номер порта
  dest_sin.sin_family = AF_INET ;

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

  // Адрес локального узла для отладки
  phe = gethostbyname ("localhost ");
  
  // Адрес удаленного узла
  //  phe = gethostbyname ("maxsinev");
  
  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_addr.s_addr = inet_addr ("200.200.200.201");

  // Копируем номер порта
  dest_sin.sin_port = htons(SERV_PORT);

  // В случае успеха выводим сообщение об установке
  // соединения с узлом
  SendMessage(hwndSb, SB_SETTEXT, 0, 
    (LPARAM)"Connected");
}

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

void SendMsg(HWND hWnd)
{
  char szBuf[80];
  lstrcpy(szBuf, "Test string");

  // Посылаем сообщение
  send to(srv_socket, szBuf, lstrlen(szBuf), 0,
    (PSOCKADDR  )&dest_sin, sizeof(dest_sin));
}
 

Функция SetConnection создает сокет типа SOCK_DGRAM, так как передача данных будет выполняться с использованием протокола UDP :

srv_socket  = socket(AF_INET, SOCK_DGRAM, 0); 

Далее выполняется привязка сокета к адресу с помощью функции bind. При этом указывается нулевое значение порта и адрес INADDR_ANY, так как на данном этапе эти параметры не имеют значения.

Затем функция SetConnection записывает адрес сервера в структуру dest_sin. Этот адрес потребуется для передачи сообщений серверу.

При использовании протокола UDP и если не создан канал между приложениями, для передачи данных следует использовать функцию send to:

send to(srv_socket, szBuf, lstrlen(szBuf), 0,
    (PSOCKADDR  )&dest_sin, sizeof(dest_sin)); 

В качестве предпоследнего параметра этой фукнции нужно передать адрес заполненной структуры, содержащей адрес узла, куда будет посылаться пакет данных. Через последний параметр функции send to необходимо передать размер указанной структуры.

Привдедем список возможных кодов ошибок для функции send to:

Код ошибки Описание
WSANOTINITIALISED Перед использованием функции необходимо вызвать функцию WSAStartup
WSAENETDOWN Сбой в сети
WSAEACCES Не был установлен флаг широковещательного адреса
WSAEINTR Работа функции была отменена при помощи функции WSACancelBlockingCall
WSAEINPROGRESS Выполняется блокирующая функция интерфейса Windows Sockets
WSAEFAULT Неправильно указан адрес буфера, содержащего передаваемые данные
WSAENETRESET Необходимо сбросить соединение
WSAENOBUFS Произошло зацикливание буферов
WSAENOTSOCK Указанный дескриптор не является дескриптором сокета
WSAESHUTDOWN Сокет был закрыт функцией shutdown
WSAEWOULDBLOCK Сокет отмечен как неблокирующий, но запрошенная операция приведет к блокировке
WSAEMSGSIZE Размер датаграммы больше, чем это допускается данной реализацией интерфейса Windows Sockets
WSAECONNABORTED Сбой из-за слишком большой задержки или по другой причине
WSAECONNRESET Сброс соединения удаленным узлом
WSAEADDRNOTAVAIL Указанный адрес недоступен
WSAEAFNOSUPPORT Данный тип сокета не может работать с указанным семейством адресов
WSAEDESTADDRREQ Необходимо указать адрес получателя датаграммы
WSAENETUNREACH В данное время и из данного узла невозможно получить доступ к сети

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