Сетевое программирование в .NET, страница 14

    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

#pragma comment (lib, "Ws2_32.lib")

#define WIN32_LEAN_AND_MEAN   // Exclude rarely-used stuff from Windows headers

#include <iostream>

#include <string>

#include <tchar.h>

#include <winsock2.h>

using namespace std;

Важным моментом является подключение библиотеки Ws2_32.lib (WinSock). Найдите две строки кода, которые делают это. Замените весь код серверного приложения (файла WinSockServer.cpp) на тот, что приведен ниже. В нем реализуется алгоритм вызова API-функций, соответствующий серверной части диаграммы.

#include "stdafx.h"

void main()

{

WSADATA data;

int res = WSAStartup (MAKEWORD(2,2), &data); // Initialize WSA (Winsock Applications)

if (res != NO_ERROR)

  cout << "Error at WSAStartup()";

SOCKET sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

if (sock == INVALID_SOCKET)

{

  cout << "\nError at socket(): " << WSAGetLastError();

  WSACleanup();

  return;

}