Сетевое программирование в .NET. Расшифровка IP-адреса (IP address specification). Обеспечение надежности доставки пакетов, страница 14

Совместно с этой структурой (как в Windows, так и в Unix) используется структура sockaddr_in. В ней хранятся пары (сетевой адрес, порт). Последнее поле sin_zero служит для выравнивания размера структуры (так чтобы она могла быть преобразована в структуру sockaddr).

struct sockaddr_in

{

short sin_family;           // Address family. Оно должно быть равно AF_INET

unsigned short sin_port;  // IP port

struct in_addr sin_addr;  // IP address

char sin_zero[8];           // Padding to make structure the same size as SOCKADDR

};

С помощью sockaddr_in любой (локальный или удаленный) адрес (а точнее, пара типа endpoint) могут быть зааданы или переданы в какую-либо из функций Windows Sockets. Поле sin_addr в свою очередь является структурой типа in_addr. Она определена следующим образом:

struct in_addr

{

union

{

struct { unsigned char s_b1, s_b2, s_b3, s_b4; } S_un_b;

struct { unsigned short s_w1, s_w2; } S_un_w;

unsigned long S_addr;

} S_un;

};

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

sockaddr_in addr;               // Объявление структуры

addr.sin_family = AF_INET;    // Установка типа

addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Установка адреса

addr.sin_port = htons (27015);     // Установка номера порта

Как вы догадались, вспомогательная функция inet_addr преобразовывает текстовое представление адреса в формате Ipv4 (IP-адрес, разделяемый точками) в адрес, отдельные поля которого записаны в структуре in_addr. Функция htons также служит для преобразования 16-битового числа в одном формате в такое же число, но в другом формате. Дело в том, что протокол TCP/IP использует порядок следования байт: big-endian (старший справа), и он не соответствует порядку следования байт при задании номера порта (старший слева).

Этот курьез—результат невнимания к проблемам стандартизации. Например, hex-код латинской буквы 'A' (U+0041) в процессоре Intel представлен в виде: 4100 (Little-endian), а в протоколе TCP в виде: 0041 (Big-endian).

При передаче данных между узлами сети надо учитывать тип разъема (SOCK_STREAM или SOCK_DGRAM). Первый тип, как указано ранее, ориентирован на соединение (connection-oriented socket), а второй—нет (сonnectionless socket). Следующая диаграмма показывает последовательность вызова функций на стороне клиента и сервера, используемая в первом типе разъемов.

После создания разъема серверное приложение привязывает (bind) к нему локальный IP-адрес и номер порта, затем оно переходит в режим ожидания соединения с другим (клиентским) приложением. Этот переход представляет собой двухшаговую процедуру:

¨  Сначала вызывается функция listen, перводящая разъем в режим прослушивания сообщений из порта,

¨  Затем циклически вызывается функция accept, которая выявляет попытку клиента получить соединение и прекращает цикл прослушивания.

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

Практическая реализация сетевого соединения Winsock

Рассмотрим, как осуществить соединение и обмен данными между двумя приложениями средствами Winsock. В нашем примере алгоритм обработки получаемых данных будет примитивным.

¨  Создайте новое пустое решение (Blank Solution) с именем WinSock,

¨  Добавьте в него проект типа Visual C++ Win32 с именем WinSockServer.

¨  Добавьте в решение еще один проект типа Visual C++ Win32 с именем WinSockClient.

¨  Замените коды файлов stdafx.h (в обоих проектах) на тот, что приведен ниже.

#pragma once