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

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

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

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

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 создает канал связи с сервером.

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