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

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

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

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

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;
}
[Назад] [Содеожание] [Дальше]