Windows sockets network programming на русском

ГЛАВА 12

ГЛАВА 12

Сетевое программирование с помощью сокетов Windows

Именованные каналы пригодны для организации межпроцессного взаимодействия как в случае процессов, выполняющихся на одной и той же системе, так и в случае процессов, выполняющихся на компьютерах, связанных друг с другом локальной или глобальной сетью. Эти возможности были продемонстрированы на примере клиент-серверной системы, разработанной в главе 11, начиная с программы 11.2.

Однако как именованные каналы, так и почтовые ящики (в отношении которых для простоты мы будем использовать далее общий термин — «именованные каналы», если различия между ними не будут играть существенной роли) обладают тем недостатком, что они не являются промышленным стандартом. Это обстоятельство усложняет перенос программ наподобие тех, которые рассматривались в главе 11, в системы, не принадлежащие семейству Windows, хотя именованные каналы не зависят от протоколов и могут выполняться поверх многих стандартных промышленных протоколов, например TCP/IP.

Возможность взаимодействия с другими системами обеспечивается в Windows поддержкой сокетов (sockets) Windows Sockets — совместимого и почти точного аналога сокетов Berkeley Sockets, де-факто играющих роль промышленного стандарта. В этой главе использование API Windows Sockets (или «Winsock») показано на примере модифицированной клиент-серверной системы из главы 11. Результирующая система способна функционировать в глобальных сетях, использующих протокол TCP/IP, что, например, позволяет серверу принимать запросы от клиентов UNIX или каких-либо других, отличных от Windows систем.

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

Привлекая средства обеспечения взаимодействия между разнородными системами, ориентированные на стандарты, интерфейс Winsock открывает перед программистами возможность доступа к высокоуровневым протоколам и приложениям, таким как ftp, http, RPC и СОМ, которые в совокупности предоставляют богатый набор высокоуровневых моделей, обеспечивающих поддержку межпроцессного сетевого взаимодействия для систем с различной архитектурой.

В данной главе указанная клиент-серверная система используется в качестве механизма демонстрации интерфейса Winsock, и в процессе того, как сервер будет модифицироваться, в него будут добавляться новые интересные возможности. В частности, нами будут впервые использованы точки входа DLL (глава 5) и внутрипроцессные серверы DLL. (Эти новые средства можно было включить уже в первоначальную версию программы в главе 11, однако это отвлекло бы ваше внимание от разработки основной архитектуры системы.) Наконец, дополнительные примеры покажут вам, как создаются безопасные реентерабельные многопоточные библиотеки.

Поскольку интерфейс Winsock должен соответствовать промышленным стандартам, принятые в нем соглашения о правилах присвоения имен и стилях программирования несколько отличаются от тех, с которыми мы сталкивались в процессе работы с описанными ранее функциями Windows. Строго говоря, Winsock API не является частью Win32/64. Кроме того, Winsock предоставляет дополнительные функции, не подчиняющиеся стандартам; эти функции используются лишь в случае крайней необходимости. Среди других преимуществ, обеспечиваемых Winsock, следует отметить улучшенную переносимость результирующих программ на другие системы.

Читайте также

Глава 17 Работа в сети с помощью сокетов

Глава 17
Работа в сети с помощью сокетов
По мере того, как компьютерный мир все шире объединяется в единую сеть, важность сетевых приложений все больше и больше возрастает. Система Linux предлагает программный интерфейс сокетов Беркли (Беркли), который уже стал стандартным

Глава 1 Введение в сетевое программирование

Глава 1
Введение в сетевое программирование

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

Глава 6 Программирование с помощью стандартных функций ввода-вывода

Глава 6
Программирование с помощью стандартных функций ввода-вывода
До сих пор мы использовали существующие инструменты, чтобы разрабатывать новые, но сейчас уже достигнут разумный предел в создании новых средств с помощью shell, sed и awk. В этой главе нам предстоит написать

Глава 6 Сетевое оборудование

Глава 6
Сетевое оборудование
Какое бы количество компьютеров ни планировалось подключить к сети, для того чтобы такое подключение стало возможным вообще, требуется некоторое оборудование. Мало того, чем больше компьютеров – тем больше такого оборудования потребуется.В

Глава 6 Сетевое общение

Глава 6
Сетевое общение

– Секреты ICQ
– Альтернативы ICQ
– Сеть FIDO
– IP-телефония. Программы для голосового общения
Общение – один из самых популярных способов использования Интернета. Существует очень много инструментов сетевого общения, и каждый человек может найти

27.3. Программирование сокетов

27.3. Программирование сокетов

27.3.1. Что такое сокет?
Сокет — это двунаправленный канал между двумя компьютерами в сети, который обеспечивает конечную точку соединения. «Двунаправленный» означает, что данный могут передаваться в двух направлениях — от клиента к серверу и

Программирование баз данных с помощью Access

Программирование баз данных с помощью Access
Прежде чем вы приступите к программированию базы данных в Access, вам следует ознакомиться со всеми отличиями Access от остальных VBA-приложений. Эти различия осложняют перевод программы, созданной с помощью Access, в другое VBA-приложение.*

Глава 18. Сетевое программирование

Глава 18. Сетевое программирование
Если торговец в разговоре с вами произносит слово «сеть», скорее всего, он желает всучить свою визитную карточку. Но в устах программиста это слово обозначает электронное взаимодействие физически удаленных машин — неважно, находятся

ГЛАВА 12. Отображение типов, динамическое связывание и программирование с помощью атрибутов

ГЛАВА 12. Отображение типов, динамическое связывание и программирование с помощью атрибутов
Как показано в предыдущей главе, компоновочные блоки являются базовыми элементами установки и среде .NET. С помощью интегрированного обозревателя объектов в Visual Studio 2005 можно

Программирование с помощью атрибутов

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

Программирование с помощью таймеров обратного вызова

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

ГЛАВА 19. Создание окон с помощью System.Windows.Forms

ГЛАВА 19. Создание окон с помощью System.Windows.Forms
Если вы прочитали предыдущие 18 глав, вы должны иметь солидную базу дли использования языка программирования C# и архитектуры .NET. Вы, конечно же, можете применить полученные знания для построения консольных приложений следующего

Глава 11 Сетевое взаимодействие

Глава 11 Сетевое взаимодействие
• Краткое описание сетевых компонентов• Простой обмен данными• Слежение за компьютером по сети• Многопользовательский разговорникОрганизация надежного сетевого взаимодействия между приложениями или компонентами одного

Глава 12 Программирование с помощью процедур VBA

Глава 12 Программирование с помощью процедур VBA
Как уже говорилось, VBA – это объектно-ориентированный язык программирования. В свое время он был разработан специально для записи макросов в Microsoft Excel 5.0. Затем он начал использоваться и другими приложениями Microsoft Office, в

Программирование приложений с помощью VBA

Программирование приложений с помощью VBA
Что может делать пользователь с помощью VBA? Ответ очень прост: все. К сожалению, ни в одной книге нельзя рассмотреть все вероятные сферы программирования, в которых вы найдете применение возможностям VBA. Поэтому ограничимся тем, что

И так, что же такое Winsock и с чем его едят? Если сказать в «двух словах», то Winsock это интерфейс, который упрощает разработку сетевых приложений под Windows. Всё что нам нужно знать, это то что Winsock представляет собою интерфейс между приложением и транспортным протоколом, выполняющим передачу данных.

Не будем вдаваться в детали внутренней архитектуры, ведь нас интересует не то, как он устроен внутри, а то, как использовать функции, предоставляемые Winsock пользователю для работы. Наша задача — на конкретных примерах разобраться с механизмом действия WinsockAPI. «Для чего это можно использовать? Ведь существуют библиотеки, упрощающие работу с сетями и имеющие простой интерфейс?» — спросите вы. Я отчасти согласен с этим утверждением, но по-моему полностью универсальных библиотек, ориентированных под все задачи существовать не может. Да и к тому же, намного приятней разобраться во всём самому, не чувствуя неловкости перед «чёрным ящиком» принципа работы которого не понимаешь, а лишь используешь как инструмент :) Весь материал рассчитан на новичков. Я думаю с его освоением не будет никаких проблем. Если вопросы всё-таки возникнут, пишите на pepper@anotherd.com. Отвечу всем. Для иллюстрации примеров будем использовать фрагменты кода Microsoft VC++. Итак, приступим!

Итак, первый вопрос — если есть Winsock, то как его использовать? На деле всё не так уж и сложно. Этап первый — подключение библиотек и заголовков.

#include «winsock.h» или #include «winsock2.h» — в зависимости от того, какую версию Winsock вы будете использовать
Так же в проект должны быть включены все соответствующие lib-файлы (Ws2_32.lib или Wsock32.lib)

Теперь мы можем спокойно использовать функции WinsockAPI. (полный список функций можно найти в соответствующих разделах MSDN).

Для инициализации Winsock вызываем функцию WSAStartup

int WSAStartup( WORD wVersionRequested, (in) LPWSADATA lpWSAData (out) );

Параметр WORD wVersionRequested — младший байт — версия, старший байт — под.версия, интерфейса Winsock. Возможные версии — 1.0, 1.1, 2.0, 2.2… Для «сборки» этого параметра используем макрос MAKEWORD. Например: MAKEWORD (1, 1) — версия 1.1. Более поздние версии отличаются наличием новых функций и механизмов расширений. Параметр lpWSAData — указатель на структуру WSADATA. При возврате из функции данная структура содержит информацию о проинициализированной нами версии WinsockAPI. В принципе, ёё можно игнорировать, но если кому-то будет интересно что же там внутри — не поленитесь, откройте документацию ;)

Так это выглядит на практике:

   WSADATA ws;
   //…
   if (FAILED (WSAStartup (MAKEWORD( 1, 1 ), &ws) ) )
   {
      // Error…
      error = WSAGetLastError();
      //…
   }

При ошибке функция возвращает SOCKET_ERROR. В таком случае можно получить расширенную информацию об ошибке используя вызов WSAGetLastError(). Данная функция возвращает код ошибки (тип int)

Итак, мы можем приступить к следующему этапу — создания основного средства коммуникации в Winsock- сокета (socket). С точки зрения WinsockAPI сокет — это дескриптор, который может получать или отправлять данные. На практике всё выглядит так: мы создаём сокет с определёнными свойствами и используем его для подключения, приёма/передачи данных и т.п. А теперь сделаем небольшое отступление… Итак, создавая сокет мы должны указать его параметры: сокет использует TCP/IP протокол или IPX (если TCP/IP, то какой тип и т.д.). Так как следующие разделы данной статьи будут ориентированы на TCP/IP протокол, то остановимся на особенностях сокетов использующих этот протокол. Мы можем создать два основных типа сокетов работающих по TCP/IP протоколу — SOCK_STREAM и SOCK_DGRAM (RAW socket пока оставим в покое :) ). Разница в том, что для первого типа сокетов (их еще называют TCP или connection-based socket), для отправки данных сокет должен постоянно поддерживать соединение с адресатом, при этом доставка пакета адресату гарантирована. Во втором случае наличие постоянного соединения не нужно, но информацию о том, дошел ли пакет, или нет — получить невозможно (так называемые UDP или connectionless sockets). И первый и второй типы сокетов имеют своё практическое применение. Начнём наше знакомство с сокетами с TCP (connection-based) сокетов.

Для начала объявим его:

   SOCKET s;

Создать сокет можно с помощью функции socket

SOCKET socket ( int af (in),          // протокол (TCP/IP, IPX…)
                int type (in),        // тип сокета (SOCK_STREAM/SOCK_DGRAM)
                int protocol (in)     // для Windows приложений может быть 0
              );

Пример:

   if (INVALID_SOCKET == (s = socket (AF_INET, SOCK_STREAM, 0) ) )
   {
      // Error…
      error = WSAGetLastError();
      // …
   }

При ошибке функция возвращает INVALID_SOCKET. В таком случае можно получить расширенную информацию об ошибке используя вызов WSAGetLastError().

В предыдущем примере мы создали сокет. Что же теперь с ним делать? :) Теперь мы можем использовать этот сокет для обмена данными с другими клиентами winsock-клиентами и не только. Для того, что бы установить соединение с другой машиной необходимо знать ее IP адрес и порт. Удалённая машина должна «слушать» этот порт на предмет входящих соединений (т.е. она выступает в качестве сервера). В таком случае наше приложение это клиент.

Для установки соединения используем функцию connect.

int connect(SOCKET s,                             // сокет (наш сокет)
            const struct sockaddr FAR *name,  // адрес
               int namelen                    // длинна адреса
           );

Пример:

   // Объявим переменную для хранения адреса
   sockaddr_in s_addr;

   // Заполним ее:
   ZeorMemory (&s_addr, sizeof (s_addr));
   // тип адреса (TCP/IP)
   s_addr.sin_family = AF_INET;
   //адрес сервера. Т.к. TCP/IP представляет адреса в числовом виде, то для перевода
   // адреса используем функцию inet_addr.
   s_addr.sin_addr.S_un.S_addr = inet_addr («193.108.128.226»);
   // Порт. Используем функцию htons для перевода номера порта из обычного в //TCP/IP представление.
   s_addr.sin_port = htons (1234);

   // Дальше выполняем соединение:
   if (SOCKET_ERROR == ( connect (s, (sockaddr *) &s_addr, sizeof (s_addr) ) ) )
   {
      // Error…
      error = WSAGetLastError();
      // …
   }

При ошибке функция возвращает SOCKET_ERROR.
Теперь сокет s связан с удаленной машиной и может посылать/принимать данные только с нее.

Для того что бы послать данные используем функцию send

int send(SOCKET s,              // сокет- отправитель
         const char FAR *buf,   // указатель на буффер с данными
         int len,               // длинна данных
         int flags              // флаги (может быть 0)
        );

Пример использования данной функции:

   if (SOCKET_ERROR == ( send (s, (char* ) & buff), 512, 0 ) )
   {
      // Error…
      error = WSAGetLastError();
      // …
   }

При ошибке функция возвращает SOCKET_ERROR.
Длинна пакета данных ограничена самим протоколом. Как узнать максимальную длину пакета данных мы рассмотрим в следующий раз. Возврат из функции не происходит до тех пор, пока данные не будут отправлены.

Принять данные от машины с которой мы предварительно установили соединение позволяет функция recv.

int recv(SOCKET s,         // сокет- получатель
         char FAR *buf,    // адрес буфера для приёма данных
         int len,          // длинна буфера для приёма данных
         int flags         // флаги (может быть 0)
        );

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

   int actual_len = 0;

   if (SOCKET_ERROR == (actual_len = recv (s, (char* ) & buff), max_packet_size, 0 ) )
   {
      // Error…
      error = WSAGetLastError();
      // …
   }

Если данные получены, то функция возвращает размер полученного пакета данных (а примере — actual_len) При ошибке функция возвращает SOCKET_ERROR. Заметьте, что функции send/recv будут ждать пока не выйдет тайм-аут или не отправится/придет пакет данных. Это соответственно вызывает задержку в работе программы. Как этого избежать читайте в следующих выпусках.

Процедура закрытия активного соединения происходит с помощью функций shutdown и closesocket. Различают два типа закрытия соединений: abortive и graceful. Первый вид — это экстренное закрытие сокета (closesocket). В таком случае соединение разрывается моментально. Вызов closesocket имеет мгновенный еффект. После вызова closesocket сокет уже недоступен. Как закрыть сокет с помощью shutdown/closesocket читайте в следующих выпусках, так как эта тема требует более полного знания Winsock.

int shutdown(SOCKET s,     // Закрываемый сокет
             int how       // Способ закрытия
           );

int closesocket(SOCKET s   // Закрываемый сокет
               );

Пример:

   closesocket (s);

Как видите, рассмотренный нами Winsock-механизм обмена данными очень прост. От программиста требуется лишь выработать свой «протокол» общения удалённых машин и реализовать его с помощью данных функций. Конечно, рассмотренные нами примеры не отражают всех возможностей Winsock. В наших статьях мы постараемся рассмотреть наиболее важные на наш взгляд особенности работы с Winsock. Stay tuned. :)
В следующем выпуске читайте:

  • Пишем простейшее winsock приложение.
  • UDP сокеты — приём/доставка негарантированных пакетов
  • Решаем проблему «блокировки» сокетов.

Используйте Windows TCP Sockets в C ++

Programming Windows TCP Sockets in C++ for the Beginner

Примечание переводчика: эта статья была создана автором в 2006 году, а автора, найденного в документе Word, зовут Кэмерон Флинт. Для новичков, впервые знакомых с сетевым программированием, это очень хорошее вводное учебное пособие, эта статья очень ясна от теории к коду. Перед прочтением этой статьи практически не требуется никаких оснований, лучше всего, если вы немного знакомы с рабочим столом MFC или Win32. Подобные статьи в Китае трудно найти, поэтому я заменил комментарии в этой статье и код на китайский, а также создал проект VS. Информация об окне и подсказке в исходном коде просто обрабатывается. Скриншот выглядит следующим образом:

这里写图片描述

Вы можете получить все ресурсы по следующей ссылке:
github.com ИлиCSDN скачать
Если вы считаете, что эта статья полезна для вас, пожалуйста, поблагодарите меня!

  • Используйте Windows TCP Sockets в C ++
    • Введение
    • Поток, порт, сокет
    • Порядок байтов
    • Открыть Winsock
    • Инициализировать сокет
    • Подключиться к удаленному хосту — как клиент
    • Получите соединение как сервер
    • Асинхронный сокет
    • Отправить данные и получить данные
    • постскриптум
    • соглашение

Введение

Прежде чем мы начнем, нам нужно включить winsock.h и связать libws2_32.a с проектом, прежде чем мы сможем использовать API, необходимые для TCP / IP. Если это невозможно, пожалуйста, используйте во время выполненияLoadLibrary()Загрузите ws2_32.dll или аналогичный метод

я использую#pragma comment(lib, "Ws2_32.lib")Вместо вышеуказанного метода

Весь код в этой статье написан и протестирован с использованием «Bloodshed Dev-C ++ 4.9.8.0», но в целом он должен работать с любым компилятором, требующим очень мало модификаций

Поток, порт, сокет

«Нить» — это идентификационное имя соединения между вашим компьютером и удаленным компьютером, а нить подключена только к сокету.

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

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

порт служба
7 Ping
13 Time
15 Netstat
22 SSH
23 Telnet (по умолчанию)
25 SMTP (Отправить почту)
43 Whois (запрос информации)
79 Палец (запрос информации о сервере)
80 HTTP (веб-страница)
110 POP (получение почты)
119 NNTP
513 CLOGIN (для IP-спуфинга)

Если вы хотите использовать порт без назначенного сервиса, от 1000 до 6535 должно подойти

  • IE браузер отправляет и получает данные через порт 80
  • QQ и другое программное обеспечение для обмена мгновенными сообщениями обычно используют более высокие неназначенные порты, чтобы избежать помех
  • При отправке писем общайтесь с удаленным почтовым сервером через порт 25
  • При получении почты почтовый клиент использует порт 110 для получения почты с почтового сервера.

IP-адрес — это идентификатор, назначенный каждому компьютеру в сети, который можно просмотреть с помощью команды ipconfig в Windows.

Поскольку использование цифровых логотипов на веб-сайтах не способствует воспоминаниям людей, старшие братья предложили решение «доменного имени».www.baidu.comТакое простое имя заменяет IP-адрес. Когда мы вводим эти доменные имена в браузер, мы ищем IP-адрес доменного имени через маршрутизатор. После успешного получения (или определения хоста) браузер подключится к адресу, на котором расположен сервер.

Для выполнения этой задачи есть два API. Прежде чем устанавливать соединение, вам, скорее всего, потребуется преобразовать доменное имя в IP-адрес — для этого необходимо, чтобы компьютер был подключен к Интернету.

// Возвращаем IP-адрес доменного имени
DECLARE_STDCALL_P(struct hostent *) gethostbyname(const char*);

// Преобразуем адрес типа string в IP-адрес
// Эта функция возвращает адрес в правильном порядке байтов, поэтому нам не нужно выполнять преобразование (см. Ниже)  

unsigned long PASCAL inet_addr(const char*);

Порядок байтов

Поскольку компьютерные и сетевые протоколы Intel используют противоположный порядок байтов, мы должны преобразовать каждый порт и IP-адрес в порядок байтов сетевого протокола перед отправкой запроса, иначе это вызовет путаницу. Если вы не измените направление, порт 25 в конечном итоге не станет портом 25. Поэтому, когда мы общаемся с сервером, мы должны убедиться, что мы используем тот же язык, что и сервер

К счастью, Microsoft предоставляет некоторые API для изменения порядка байтов IP или порта

u_long PASCAL htonl(u_long); // Host-> Сеть длинная
u_long PASCAL ntohl(u_long); // Network-> Host long

u_short PASCAL htons(u_short); // Host-> Network short
u_short PASCAL ntohs(u_short); // Network-> Host short

нота: «Хост» — это компьютер, который прослушивает и принимает соединение, «Сеть» — гость, подключенный к хосту.

Например, когда мы указываем порт, который мы хотим слушать или подключаться, мы должны использоватьhtons()Функция преобразует числа в сетевой порядок байтов. Конечно, если вы используетеinet_addr()Функция преобразует IP-адрес строкового типа в IP-адрес, тогда полученный IP-адрес соответствует правильному порядку байтов в сети, поэтому нет необходимости использоватьhtonl()функция

Открыть Winsock

Первый шаг в использовании сокетов Windows — включить Winsock API. Существует две версии Winsock. Вторая версия — это последняя версия и версия, которую мы хотим указать.

#define SCK_VERSION1            0x0101
#define SCK_VERSION2            0x0202

int PASCAL WSAStartup(WORD,LPWSADATA);
int PASCAL WSACleanup(void);

// Когда функция вернется, этот typedef будет заполнен информацией о версии winsock


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 *       lpVendorInfo;
}
WSADATA;

typedef WSADATA *LPWSADATA;

Нам нужно только вызвать эти две функции, вызываемые при инициализации WinsockWSAStartup()Вызывается, когда задача выполненаWSACleanup(), Но не закрывайте Winsock до тех пор, пока задача не будет завершена, поскольку это приведет к отмене всех подключений к программе и прослушивающих портов.

Инициализировать сокет

Чтобы заполнить правильные параметры, переданные функции для открытия Socket, функция возвращает дескриптор сокета. Этот дескриптор очень удобен, мы можем использовать его для манипуляций с сокетами в любое время

Хорошая привычка — закрывать все открытые сокеты до выхода из программы. перечислитьWSACleanup()Вызывает принудительное закрытие всех сокетов и соединений, но более элегантный метод — использоватьclosesocket()Чтобы закрыть указанный сокет, вам нужно только передать дескриптор сокета в этот API

// намного больше, чем здесь определено
// Подробности смотрите в заголовочном файле winsock2.h

#define SOCK_STREAM      1
#define SOCK_DGRAM      2
#define SOCK_RAW      3

#define AF_INET      2 

#define IPPROTO_TCP      6

SOCKET PASCAL socket(int,int,int);
int PASCAL closesocket(SOCKET);

При создании сокета вам нужно передать семейство адресов, тип сокета и тип протокола. Если вы не выполняете какую-либо специальную работу, обычно используйтеAF_INETПередается в качестве семейства адресов по умолчанию, этот параметр определяет, как интерпретировать адрес

Фактически, существует не только один тип сокетов, три наиболее распространенных типа включают необработанные сокеты (Raw Sockets), потоковые сокеты (Stream Sockets) и дейтаграммы Socket (дейтаграммы Sockets). В этой статье используется потоковый сокет, потому что мы имеем дело с протоколом TCP, мы указываемSOCK_STREAMВ видеsocket()Второй параметр

socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)

Подключиться к удаленному хосту — как клиент

Далее попробуйте простую программу, которая может подключаться к удаленному компьютеру.

Нам нужно заполнить информацию об удаленном хосте, а затем передать указатель этой структуры на магическую функциюconnect()Структура и API следующие. нотаsin_zeroПараметр не нужен, поэтому оставьте его пустым

struct sockaddr_in 
{
      short      sin_family;
      u_short      sin_port;
      struct      in_addr sin_addr;
      char      sin_zero[8];
};

int PASCAL connect(SOCKET,const struct sockaddr*,int);

Настоятельно рекомендуется вводить все примеры вручную

// Подключение к удаленному хосту (клиентское приложение)
// Включить необходимые заголовочные файлы
// Не забудьте связать libws2_32.a

#include <winsock.h>
/// Если вы не добавите эту новую версию против, вы получите ошибку
#pragma warning(disable : 4996)
/// Ссылка это, вам не нужно найти файл libws2_32.a
#pragma comment(lib, "Ws2_32.lib")
///#pragma comment(lib, "libws2_32.a")
using namespace std;

SOCKET s; // Сокет дескриптор

// CONNECTTOHOST подключиться к удаленному хосту
bool ConnectToHost(int PortNo,const char* IPAddress)
{
    // Включаем Winsock ...
    WSADATA wsadata;
    int error = WSAStartup(0x0202, &wsadata);

    // Есть ли исключения?
    if (error)
        return false;

    // Получена ли правильная версия Winsock
    if (wsadata.wVersion!=0x0202)
    {
        WSACleanup(); // Очистить Winsock
        return false;
    }

    // Заполняем информацию, необходимую для инициализации Socket
    SOCKADDR_IN target; // Информация об адресе сокета

    target.sin_family = AF_INET;// Адрес семьи
    target.sin_port = htons(PortNo);// Номер подключенного порта
    target.sin_addr.s_addr=inet_addr(IPAddress);//Адрес назначения

    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// Создать сокет
    if (s==INVALID_SOCKET)
    {
        return false;// Невозможно создать сокет
    }

    // Попробуйте подключиться ...
    if (connect(s,(SOCKADDR*)&target,sizeof(target))==SOCKET_ERROR)
    {
        return false;//невозможно подключиться
    }
    else
    {
        return true;// соединение успешно
    }
}

// CLOSECONNECTION-закрыть сокет и отключить все его соединения
void CloseConnection()
{
    // Если сокет существует, закройте
    if (s)
        closesocket(s);
    WSACleanup();
}

Получите соединение как сервер

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

int PASCAL bind(SOCKET,const struct sockaddr*,int); // Привязать сокет
int PASCAL listen(SOCKET,int); // Прослушивание запросов на соединение

// Принимаем запрос на соединение
SOCKET PASCAL accept(SOCKET,struct sockaddr*,int*);

Как сервер, пока вы слушаете этот порт, вы можете получать все запросы на этот порт. Например, если удаленный компьютер хочет пообщаться с вашим компьютером, он сначала спросит, может ли ваш сервер установить соединение. Чтобы установить соединение, ваш сервер долженaccept()Запрос на подключение.

хотяlisten()Эта функция — самый простой способ прослушивания порта и работы в качестве сервера, но она не самая идеальная. Мы обнаружим, что при выполнении программа будет зависать до тех пор, пока не будет установлено соединение. потому чтоlisten()Является ли «блокирующей» функцией (одновременно может быть выполнена только одна задача, и она не вернется, пока не будет приостановлено соединение)

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

Но нет необходимости быть таким хлопотным, потому что вы можете заменить его «асинхронной» функцией Socket.listen()

Прежде чем вы планируете прослушивать порт, вы должны:

  1. Инициализация Winsock (обсуждалось ранее)
  2. Включите Socket, убедитесь, что вы возвращаете ненулевое значение (представляющее успех), это возвращаемое значение является дескриптором Socket
  3. вSOCKADDR_INЗапишите необходимые данные в структуру, включая семейство адресов, порт и IP-адрес
  4. использованиеbind()Функция привязывает сокет к IP (еслиsin_addrПараметрSOCKADDR_INИспользуйте inet_addr («0.0.0.0») илиhtonl(INADD_ANY)Можно привязать любой IP-адрес)

В это время, если все идет по плану, вы можете позвонитьlisten()Следите за тем, что вы хотите

listen()Первый параметр должен быть инициализированным дескриптором сокета. Конечно, порт, к которому подключен этот сокет, является портом, который мы намереваемся слушать. Используйте функции next и final, чтобы указать максимальное количество удаленных компьютеров, взаимодействующих с сервером. В общем, если вы не хотите использовать только несколько конкретных соединений, нам нужно толькоSOMAXCONN(SOcket MAX CONNection) передается в качестве параметраlisten(), Если Socket запущен и работает нормально, все в порядке. При получении запроса на подключениеlisten()Вернусь. Если вы согласны установить соединение, позвонитеaccept()

#include <Windows.h>
#include <winsock.h>
#pragma comment(lib, "Ws2_32.lib")

SOCKET s;
WSADATA w;

// LISTENONPORT-прослушивание определенного порта, ожидание соединения или данных
bool ListenOnPort(int portno)
{
    int error = WSAStartup(0x0202, &w);// Заполняем информацию WSA
    if (error)
    {
        return false;// Невозможно открыть Winsock по какой-то причине
    }
    if (w.wVersion!=0x0202)// Неправильная версия Winsock?
    {
        WSACleanup();
        return false;
    }

    SOCKADDR_IN addr; // Адресная структура сокета TCP

    addr.sin_family = AF_INET; // Адрес семьи
    addr.sin_port = htons(portno); // Указать порт для сокета

    // Если используется INADDR_ANY, принимать все запросы с IP-адресов
    // Вы также можете передать inet_addr ('0.0.0.0'), чтобы выполнить то же самое
    // Если вы хотите прослушивать только определенный IP-адрес, укажите его
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Создать сокет

    if (s==INVALID_SOCKET)
    {
        return false; // Если вы не можете создать сокет, вы не можете продолжить
    }

    if (bind(s, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR)
    {
        // Невозможно связать (это произойдет, когда мы попытаемся связать сокет несколько раз)
        return false;
    }

    // Теперь мы начинаем прослушивание (использование параметра SOMAXCONN позволяет установить как можно больше соединений)
    // Вы также можете указать любое целое число при необходимости
    // Эта функция не вернется, пока не будет установлено соединение
    listen(s, SOMAXCONN);

    // Не забудьте использовать CloseConnection
}

Если вы скомпилируете и разрешите этот код, как упоминалось ранее, ваша программа будет зависать до тех пор, пока не будет отправлен запрос на подключение. Вы можете инициировать этот запрос на подключение, попробовав «telnent» -соединение. Конечно, соединение неизбежно прервется, потому что наше соединение не было принято (), а потому чтоlisten()Вернулся, значит, программа воскресла. Вы можете ввести в терминаленомер порта telnet 127.0.0.0Отправить запрос

Асинхронный сокет

использованиеlisten()Такая функция блокировки настолько глупа, давайте посмотрим, как работает «асинхронный сокет»

У C ++ есть преимущество, которого нет у других языков высокого уровня: прежде чем мы используем асинхронный Socekt, нам больше не нужно находить «потомок» родительского окна. C ++ сделал это для нас, все, что нам нужно сделать, это отправить код обработки обработчику сообщений. Вы увидите, что асинхронный сокет отправит ваше программное сообщение, когда вы сделаете запрос на соединение и получите данные. Следовательно, он может спокойно ждать в фоновом режиме, не нарушая работу родительской программы и не влияя на производительность, поскольку он обменивается данными только при необходимости. Заплаченная цена действительно мала, потому что на самом деле нет необходимости добавлять код. Чтобы понять, как это работает, может потребоваться некоторое время, но я очень рад, что вы готовы потратить время на то, чтобы разобраться в асинхронных сокетах, что сэкономит вам много времени в долгосрочной перспективе.

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

  • FD_ACCEPT: Если ваша программа является клиентом (используйтеconnnet()Сторона, подключенная к удаленному хосту), вы получите это сообщение при отправке запроса на подключение. При отправке запроса на подключение будут отправлены следующие сообщения
  • FD_CONNECT: Указывает на то, что соединение успешно установлено
  • FD_READ: Мы получаем входящие данные с удаленного компьютера. Позже я научусь с этим бороться
  • FD_CLOSE: Удаленный хост отключен, поэтому соединение потеряно

Эти значения будут в обработчике сообщенийlParamПараметры. Я скажу вам конкретное местоположение через минуту, но сначала нам нужно понять параметры API для установки Socket в асинхронный режим:

// Преобразование сокета в неблокирующий асинхронный сокет
int PASCAL WSAAsyncSelect(SOCKET,HWND,u_int,long);

Очевидно, что первый параметр — это наш дескриптор Socket, а второй параметр — наше родительское окно. Это необходимое условие, иначе сообщение не может быть отправлено в правильное окно! Мы видим, что третий параметр — это целое число, то есть уникальный номер уведомления, который вы хотите указать. Когда сообщение отправляется обработчику программы, этот номер также отправляется. Поэтому вы можете указать тип уведомления для отправки на основе номера в обработчике сообщений. Я знаю, что вы немного сбиты с толку, так что посмотрите на исходный код ниже, вы должны быть в состоянии понять некоторые из них:

#define MY_MESSAGE_NOTIFICATION      1048 // Пользовательское уведомление

// Это процесс нашего обработчика сообщений / окна
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) //Обрабатывать информацию
    {
    case MY_MESSAGE_NOTIFICATION: // Вы хотите отправить сообщение?
        {
            switch (lParam) // Если да, какой из них отправить?
            {
            case FD_ACCEPT:
                // Запрос на соединение отправлен
                break;

            case FD_CONNECT:
                // Соединение было успешно установлено
                break;

            case FD_READ:
                // Готов принять данные
                break;

            case FD_CLOSE:
                //Потерянное соединение
                break;
            }
        }
        break;

        // Другая информация о окне ...

    default: // Эта новость не имеет к нам никакого отношения
        return DefWindowProc(hwnd, message, wParam, lParam);
    }
    break;
}

Разве это не особенно сложно? Теперь все готово, мы должны быть вListenOnPort()В функцииlisten()После добавления строки кода:

// Сокет создан

// IP-адрес привязан

// функция listen () только что была вызвана ... 

// Установить сокет в неблокирующий асинхронный режим
// hwnd - допустимый дескриптор родительского окна
// Убедитесь, что вы используете или символы для соединения между знаками
WSAAsyncSelect (s, hwnd, MY_MESSAGE_NOTIFICATION, (FD_ACCEPT | FD_CONNECT |
     FD_READ | FD_CLOSE);

//Во-первых…
PS C:Users73157> netstat -an

 Активное соединение

     Протокол Локальный адрес Внешний адрес Статус
  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING
  TCP    0.0.0.0:1234           0.0.0.0:0              LISTENING
  TCP    0.0.0.0:1536           0.0.0.0:0              LISTENING
  TCP    0.0.0.0:1537           0.0.0.0:0              LISTENING
  TCP    10.200.59.149:139      0.0.0.0:0              LISTENING
  TCP    10.200.59.149:14339    14.17.41.210:80        CLOSE_WAIT
  TCP    10.200.59.149:16179    183.61.49.150:8080     ESTABLISHED
  TCP    10.200.59.149:17408    157.55.170.113:5671    ESTABLISHED
  TCP    127.0.0.1:58288        0.0.0.0:0              LISTENING
  TCP    127.0.0.1:65000        0.0.0.0:0              LISTENING
  TCP    [::]:135               [::]:0                 LISTENING
  TCP    [::]:445               [::]:0                 LISTENING
  UDP    0.0.0.0:5050           *:*
  UDP    0.0.0.0:62341          *:*
  UDP    10.200.59.149:137      *:*
  UDP    10.200.59.149:138      *:*
  UDP    127.0.0.1:64961        *:*
  UDP    127.0.0.1:65000        *:*
  UDP    [::]:500               *:*
  UDP    [::]:3702              *:*

Если ваш сервер работает должным образом, вы должны увидеть «0.0.0.0: Порт №» в «Локальный адрес». Порт № — это номер порта, который вы прослушиваете, и в настоящее время он прослушивает. PS: если вы забыли использоватьhtons()Преобразовав номер порта, вы можете обнаружить, что новый порт открыт, но он полностью отличается от того, что вы ожидали.

Не волнуйтесь, каждый должен попробовать несколько раз, чтобы добиться успеха. Вы можете попробовать больше раз. (Конечно, если вы не пытались в течение нескольких недель, запишите этот урок и забудьте, кто его написал!)

Отправить данные и получить данные

На этом этапе все ваши серверы глухонемые, что, безусловно, не тот результат, которого вы хотите. Итак, посмотрите, как эффективно общаться с другими компьютерами. Как всегда, эти вызовы API решают проблему:

// Передача текстовых данных на удаленный компьютер
int PASCAL send(SOCKET,const char*,int,int);

// Получаем данные с удаленного компьютера
int PASCAL recv(SOCKET,char*,int,int); 

// Расширенная функция: позволяет общаться с одним компьютером, когда несколько компьютеров подключены к одному серверу.
int PASCAL sendto(SOCKET,const char*,int,int,const struct   sockaddr*,int);
int PASCAL recvfrom(SOCKET,char*,int,int,struct sockaddr*,int*);

Если вы не используете асинхронный сервер сокетов, вы должны поставитьrecv()Функция помещается в функцию таймера — постоянно просматривает входящие данные, что является как минимум решением. Конечно, если вы правильно настроили асинхронный сервер Socekt, вам просто нужноrecv()Код вставлен в обработчик сообщенийFD_READВсе нормально. Когда данные поступят, вы будете уведомлены. Есть ли что-нибудь проще, чем это?

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

case FD_READ:
    {
        char buffer[80];
        memset(buffer, 0, sizeof(buffer)); // Очистить буфер

        // Помещаем входящий текст в буфер
        recv (s, buffer, sizeof(buffer)-1, 0); 

        // Делаем что-нибудь умное с текстом в буфере
        // Вы можете отобразить его в текстовом поле или использовать:

        //MessageBox(hwnd, Buffer, "Captured Text…", MB_OK);
    }
    break;

Теперь вы можете получать текст с удаленного компьютера или сервера, но сервер не ответил или возможность «отправлять» данные на удаленный компьютер. Это, вероятно, самый простой шаг в программировании Winsock, но если вы так же глупы, как я, каждый шаг должен быть обучен и использован правильноsend()Способ заключается в следующем.

char *szpText;

// Выделим память для текста при редактировании текста и извлекаем текст
// (подробности см. В исходном коде) Затем передайте указатель на него ...

send(s, szpText, len_of_text, 0);

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

Расширенные инструкции: иногда простоsend()сreceive()Функция не может удовлетворить ваши потребности. Разные компьютеры имеют несколько подключений одновременно (мы называемlisten()Когда мы прошлиSOMAXCONNЭтот параметр ограничивает максимальное количество подключений), и вам необходимо отправлять данные на указанный компьютер, а не на все компьютеры. Если вы умный призрак, вы можете найтиsend()сreceive()Два других API:sendto()сreceivefrom()(Если вы найдете его, наградите маленький красный цветок ~).

Эти два API-интерфейса позволяют вам общаться с любым удаленным компьютером независимо от других подключений. Эти расширенные функции имеют дополнительный параметр для полученияsockaddr_inУказатель типа структуры, вы можете использовать этот параметр, чтобы указать IP-адрес удаленного компьютера для связи. Если вы хотите создать полноценную программу чата или что-то подобное, это важный навык. Однако, помимо того, что я помогу вам понять основные понятия этих функций, я позволю вам решить эти проблемы самостоятельно. (Не думаете ли вы, что это раздражает, если вы говорите это из уст автора? Обычно у нас нет никаких следов… но если вы действительно решите это сделать, это не будет стоить вам слишком много времени.)

постскриптум

К настоящему времени вы должны хорошо понимать Windows Socket (или ненависть), но в любом случае, если вы хотите найти лучшее объяснение, посмотрите на исходный код, приведенный в этой статье. Практика позволяет узнать больше, чем теория

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

Я надеюсь, что вы что-то получили. Оставьте комментарий, чтобы сообщить мне, что вы думаете об этой статье

соглашение

Эта статья и сопровождающий ее исходный код и файлы следуют Атрибуция-поделился таким же образом 2.5 общийлицензия

Книги: [Классика] [Базы данных] [Internet/WWW] [Сети] [Программирование] [UNIX] [Windows] [Безопасность] [Графика] [Software Engineering] [ERP-системы] [Hardware]

Самоучитель игры на WINSOCK

(статья была опубликована в журнале «Программист»)

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

Программирование сокетов несложно само по себе, но довольно поверхностно описано в доступной литературе, а Windows Sockets SDK содержит многоженство ошибок как в технической документации, так и в прилагаемых к ней демонстрационных примерах. К тому же имеются значительные отличия реализаций сокетов в UNIX и в Windows, что создает очевидные проблемы.

Автор постарался дать максимально целостное и связанное описание, затрагивающее не только основные моменты, но и некоторые тонкости не известные рядовым программистам. Ограниченный объем журнальной статьи не позволяет рассказать обо всем, поэтому, пришлось сосредоточиться только на одной реализации сокетов — библиотеке Winsock 2, одном языке программирования — Си/Си ++ (хотя сказанное большей частью приемлемо к Delphi, Perl и т. д.) и одном виде сокетов — блокируемых синхронных сокетах.

ALMA MATER

Основное подспорье в изучении сокетов — Windows Sockets 2 SDK. SDK — это документация, набор заголовочных файлов и инструментарий разработчика. Документация не то, чтобы очень хороша — но все же написана достаточна грамотно и позволяет, пускай, не без труда, освоить сокеты даже без помощи какой-либо другой литературы. Причем, большинство книг, имеющиеся на рынке, явно уступают Microsoft в полноте и продуманности описания. Единственный недостаток SDK — он полностью на английском (для некоторых это очень существенно).

Из инструментария, входящего в SDK, в первую очередь хотелось бы выделить утилиту sockeye.exe — это настоящий «тестовый стенд» разработчика. Она позволяет в интерактивном режиме вызывать различные сокет-функции и манипулировать ими по своему усмотрению.

Демонстрационные программы, к сожалению, не лишены ошибок, причем порой довольно грубых и наводящих на мысли — а тестировались ли эти примеры вообще? (Например, в исходном тексте программы simples.c в вызове функций send и sendto вместо strlen стоит sizeof) В то же время, все примеры содержат множество подробных комментариев и раскрывают довольно любопытные приемы нетрадиционного программирования, поэтому ознакомится с ними все-таки стоит.

Из WEB-ресуросов, посвященных программированию сокетов, и всему, что с ними связано, в первую очередь хотелось бы отметить следующие три: sockaddr.com; www.winsock.com и www.sockets.com.

Обзор сокетов

Библиотека Winsock поддерживает два вида сокетов — синхронные (блокируемые) и асинхронные (неблокируемые). Синхронные сокеты задерживают управление на время выполнения операции, а асинхронные возвращают его немедленно, продолжая выполнение в фоновом режиме, и, закончив работу, уведомляют об этом вызывающий код.

ОС Windows 3.x поддерживает только асинхронные сокеты, поскольку, в среде с корпоративной многозадачностью захват управления одной задачей «подвешивает» все остальные, включая и саму систему. ОС Windows 9xNT поддерживают оба вида сокетов, однако, в силу того, что синхронные сокеты программируются более просто, чем асинхронные, последние не получили большого распространения. Эта статья посвящена исключительно синхронным сокетам (асинхронные — тема отдельного разговора).

Сокеты позволяют работать со множеством протоколов и являются удобным средством межпроцессорного взаимодействия, но в данной статье речь будет идти только о сокетах семейства протоколов TCP/IP, использующихся для обмена данными между узлами сети Интернет. Все остальные протоколы, такие как IPX/SPX, NetBIOS по причине ограниченности объема журнальной статьи рассматриваться не будут.

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

Выбор того или иного типа сокетов определяется транспортным протоколом на котором работает сервер, — клиент не может по своему желанию установить с дейтаграммным сервером потоковое соединение.

Замечание: дейтаграммные сокеты опираются на протокол UDP, а потоковые на TCP.

Первый шаг, второй, третий

Для работы с библиотекой Winsock 2.х в исходный тест программы необходимо включить директиву «#include <winsock2.h>«, а в командной строке линкера указать «ws2_32.lib». В среде разработки Microsoft Visual Studio для этого достаточно нажать <Alt-F7>, перейти к закладке «Link» и к списку библиотек, перечисленных в строке «Object/Library modules», добавить «ws2_32.lib», отделив ее от остальных символом пробела.

Перед началом использования функций библиотеки Winsock ее необходимо подготовить к работе вызовом функции «int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData)» передав в старшем байта слова wVersionRequested номер требуемой версии, а в младшем — номер подверсии.

Аргумент lpWSAData должен указывать на структуру WSADATA, в которую при успешной инициализации будет занесена информация о производителе библиотеки. Никакого особенного интереса она не представляет и прикладное приложение может ее игнорировать. Если инициализация проваливается, функция возвращает ненулевое значение.

Второй шаг – создание объекта «сокет». Это осуществляется функцией «SOCKET socket (int af, int type, int protocol)«. Первый слева аргумент указывает на семейство используемых протоколов. Для Интернет — приложений он должен иметь значение AF_INET.

Следующий аргумент задает тип создаваемого сокета — потоковый (SOCK_STREAM) или дейтаграммный (SOCK_DGRAM) (еще существуют и сырые сокеты, но они не поддерживаются Windows — см раздел «Сырые сокеты»).

Последний аргумент уточняет какой транспортный протокол следует использовать. Нулевое значение соответствует выбору по умолчанию: TCP — для потоковых сокетов и UDP для дейтаграммных. В большинстве случаев нет никакого смысла задавать протокол вручную и обычно полагаются на автоматический выбор по умолчанию.

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

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

Клиент: шаг третий для установки соединения с удаленным узлом потоковый сокет должен вызвать функцию «int connect (SOCKET s, const struct sockaddr FAR* name, int namelen)». Датаграмные сокеты работают без установки соединения, поэтому, обычно не обращаются к функции connect.

Примечание: за словом «обычно» кроется один хитрый примем программирования — вызов connect позволяет дейтаграмному сокету обмениваться данными с узлом не только функциями sendto, recvfrom, но и более удобными и компактными send и recv. Эта тонкость описана в Winsocket SDK и широко используется как самой Microsoft, так и сторонними разработчикам. Поэтому, ее использование вполне безопасно.

Первый слева аргумент — дескриптор сокета, возращенный функцией socket; второй — указатель на структуру «sockaddr«, содержащую в себе адрес и порт удаленного узла с которым устанавливается соединение. Структура sockaddr используется множеством функций, поэтому ее описание вынесено в отдельный раздел «Адрес раз, адрес два». Последний аргумент сообщает функции размер структуры sockaddr.

После вызова connect система предпринимает попытку установить соединение с указанным узлом. Если по каким-то причинам это сделать не удастся (адрес задан неправильно, узел не существует или «висит», компьютер находится не в сети), функция возвратит ненулевое значение.

Сервер: шаг третий – прежде, чем сервер сможет использовать сокет, он должен связать его с локальным адресом. Локальный, как, впрочем, и любой другой адрес Интернета, состоит из IP-адреса узла и номера порта. Если сервер имеет несколько IP адресов, то сокет может быть связан как со вмести ними сразу (для этого вместо IP-адреса следует указать константу INADDR_ANY равную нулю), так и с каким-то конкретным одним.

Связывание осуществляется вызовом функции «int bind (SOCKET s, const struct sockaddr FAR* name, int namelen)». Первым слева аргументом передается дескриптор сокета, возращенный функций socket, за ним следуют указатель на структуру sockaddr и ее длина (см. раздел «Адрес раз, адрес два«).

Строго говоря, клиент также должен связывать сокет с локальным адресом перед его использованием, однако, за него это делает функция connect, ассоциируя сокет с одним из портов, наугад выбранных из диапазона 1024-5000. Сервер же должен «садиться» на заранее определенный порт, например, 21 для FTP, 23 для telnet, 25 для SMTP, 80 для WEB, 110 для POP3 и т.д. Поэтому ему приходится осуществлять связывание «вручную».

При успешном выполнении функция возвращает нулевое значение и ненулевое в противном случае.

Сервер: шаг четвертыйвыполнив связывание, потоковый сервер переходит в режим ожидания подключений, вызывая функцию «int listen (SOCKET s, int backlog )«, где – дескриптор сокета, а backlog – максимально допустимый размер очереди сообщений.

Размер очереди ограничивает количество одновременно обрабатываемых соединений, поэтому, к его выбору следует подходить «с умом». Если очередь полностью заполнена, очередной клиент при попытке установить соединение получит отказ (TCP пакет с установленным флагом RST). В то же время максимально разумное количество подключений определяются производительностью сервера, объемом оперативной памяти и т.д.

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

Сервер: шаг пятый – извлечение запросов на соединение из очереди осуществляется функцией «SOCKET accept (SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen)«, которая автоматически создает новый сокет, выполняет связывание и возвращает его дескриптор, а в структуру sockaddr заносит сведения о подключившемся клиенте (IP-адрес и порт). Если в момент вызова accept очередь пуста, функция не возвращает управление до тех пор, пока с сервером не будет установлено хотя бы одно соединение. В случае возникновения ошибки функция возвращает отрицательное значение.

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

все вместе после того как соединение установлено, потоковые сокеты могут обмениваться с удаленным узлом данными, вызывая функции «int send (SOCKET s, const char FAR * buf, int len,int flags)» и «int recv (SOCKET s, char FAR* buf, int len, int flags)» для посылки и приема данных соответственно.

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

Функция же recv возвращает управление только после того, как получит хотя бы один байт. Точнее говоря, она ожидает прихода целой дейтаграммы. Дейтаграмма — это совокупность одного или нескольких IP пакетов, посланных вызовом send. Упрощенно говоря, каждый вызов recv за один раз получает столько байтов, сколько их было послано функцией send. При этом подразумевается, что функции recv предоставлен буфер достаточных размеров, — в противном случае ее придется вызвать несколько раз. Однако, при всех последующих обращениях данные будет браться из локального буфера, а не приниматься из сети, т.к. TCP-провайдер не может получить «кусочек» дейтаграммы, а только ею всю целиком.

Работой обоих функций можно управлять с помощью флагов, передаваемых в одной переменной типа int третьим слева аргументом. Эта переменная может принимать одно из двух значений: MSG_PEEK и MSG_OOB.

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

Флаг MSG_OOB предназначен для передачи и приема срочных (Out Of Band) данных. Срочные данные не имеют преимущества перед другими при пересылке по сети, а всего лишь позволяют оторвать клиента от нормальной обработки потока обычных данных и сообщить ему «срочную» информацию. Если данные передавались функцией send с установленным флагом MSG_OOB, для их чтения флаг MSG_OOB функции recv так же должен быть установлен.

Замечание: настоятельно рекомендуется воздержаться от использования срочных данных в своих приложениях. Во-первых, они совершенно необязательны — гораздо проще, надежнее и элегантнее вместо них создать отдельное TCP-соединение. Во-вторых, по поводу их реализации нет единого мнения и интерпретации различных производителей очень сильно отличаются друг от друга. Так, разработчики до сих пор не пришли к окончательному соглашению по поводу того, куда должен указывать указатель срочности: или на последний байт срочных данных, или на байт, следующий за последним байтом срочных данных. В результате, отправитель никогда не имеет уверенности, что получатель сможет правильно интерпретировать его запрос.

Еще существует флаг MSG_DONTROUTE, предписывающий передавать данные без маршрутизации, но он не поддерживаться Winsock и, поэтому, здесь не рассматривается.

Дейтаграммный сокет так же может пользоваться функциями send и recv, если предварительно вызовет connect (см. «Клиент: шаг третий«), но у него есть и свои, «персональные», функции: «int sendto (SOCKET s, const char FAR * buf, int len,int flags, const struct sockaddr FAR * to, int tolen)» и «int recvfrom (SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen )«.

Они очень похожи на send и recv, — разница лишь в том, что sendto и recvfrom требуют явного указания адреса узла принимаемого или передаваемого данные. Вызов recvfrom не требует предварительного задания адреса передающего узла — функция принимает все пакеты, приходящие на заданный UDP-порт со всех IP адресов и портов. Напротив, отвечать отправителю следует на тот же самый порт откуда пришло сообщение. Поскольку, функция recvfrom заносит IP-адрес и номер порта клиента после получения от него сообщения, программисту фактически ничего не нужно делать — только передать sendto тот же самый указатель на структуру sockaddr, который был ранее передан функции recvfrem, получившей сообщение от клиента.

Еще одна деталь – транспортный протокол UDP, на который опираются дейтаграммные сокеты, не гарантирует успешной доставки сообщений и эта задача ложиться на плечи самого разработчика. Решить ее можно, например, посылкой клиентом подтверждения об успешности получения данных. Правда, клиент тоже не может быть уверен, что подтверждение дойдет до сервера, а не потеряется где-нибудь в дороге. Подтверждать же получение подтверждения — бессмысленно, т. к. это рекурсивно неразрешимо. Лучше вообще не использовать дейтаграммные сокеты на ненадежных каналах.

Во всем остальном обе пары функций полностью идентичны и работают с теми самыми флагами — MSG_PEEK и MSG_OOB.

Все четыре функции при возникновении ошибки возвращают значение SOCKET_ERROR (== -1).

Примечание: в UNIX с сокетами можно обращаться точно так, как с обычными файлами, в частности писать и читать в них функциями write и read. ОС Windows 3.1 не поддерживала такой возможности, поэтому, при переносе приложений их UNIX в Windows все вызовы write и read должны были быть заменены на send и recv соответственно. В Windows 95 с установленным Windows 2.x это упущение исправлено, — теперь дескрипторы сокетов можно передавать функциям ReadFil, WriteFile, DuplicateHandle и др.

Шаг шестой, последний – для закрытия соединения и уничтожения сокета предназначена функция «int closesocket (SOCKET s)«, которая в случае удачного завершения операции возвращает нулевое значение.

Перед выходом из программы, необходимо вызвать функцию «int WSACleanup (void)» для деинициализации библиотеки WINSOCK и освобождения используемых этим приложением ресурсов. Внимание: завершение процесса функцией ExitProcess автоматически не освобождает ресурсы сокетов!

Примечание: более сложные приемы закрытия соединения — протокол TCP позволяет выборочно закрывать соединение любой из сторон, оставляя другую сторону активной. Например, клиент может сообщить серверу, что не будет больше передавать ему никаких данных и закрывает соединение «клиент ( сервер», однако, готов продолжать принимать от него данные, до тех пор, пока сервер будет их посылать, т.е. хочет оставить соединение «клиент ( сервер» открытым.

Для этого необходимо вызвать функцию «int shutdown (SOCKET s ,int how )», передав в аргументе how одно из следующих значений: SD_RECEIVE для закрытия канала «сервер ( клиент», SD_SEND для закрытия канала «клиент ( сервер», и, наконец, SD_BOTH для закрытия обоих каналов.

Последний вариант выгодно отличается от closesocket «мягким» закрытием соединения — удаленному узлу будет послано уведомление о желании разорвать связь, но это желание не будет воплощено в действительность, пока тот узел не возвратит свое подтверждение. Таким образом, можно не волноваться, что соединение будет закрыто в самый неподходящий момент.

Внимание: вызов shutdown не освобождает от необходимости закрытия сокета функцией closesocket!

Дерево вызовов

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

        WSAStartup
            |
          Socket
        /      
      клиент       сервер
         /        
       TCP  UDP      bind
           /           /     
         connect    |-sendto   TCP     UDP
            |      |-recvfrom  |    |
            |-send          listen  |
            |-recvfrom        |    |
                                       /
                                  accept
                                 /       
                               TCP      UDP
                                |-send   |-sendto
                                |-recv   |-recvform
              ||
          /
         closesocket
          |
          WSAClenup

Листинг 29 Последовательность вызова функций сокетов при различных операциях

Адрес раз, адрес два

С адресами как раз и наблюдается наибольшая путаница, в которую не помешает внести немного ясности. Прежде всего структура sockaddr определенная так:

struct sockaddr

{

u_short sa_family; // семейство протоколов

// (как правило AF_INET)

char sa_data[14]; // IP-адрес узла и порт

};

Листинг 30 Устаревшее определение структуры sockaddr

теперь уже считается устаревшей, и в Winsock 2.x на смену ей пришла структура sockaddr_in, определенная следующим образом:

  struct sockaddr_in
  {
    short  sin_family;      // семейство протоколов
              // (как правило AF_INET)
    u_short  sin_port;    // порт 
    struct  in_addr sin_addr;  // IP – адрес
    char  sin_zero[8];    // хвост
  };

Листинг 31 Современное определение структуры sockaddr_in

В общем-то ничего не изменилось (и стоило огород городить?), замена безнакового короткого целого на знаковое короткое целое для представления семейства протоколов ничего не дает. Зато теперь адрес узла представлен в виде трех полей — sin_port (номера порта), sin_addr (IP-адреса узла) и «хвоста» из восьми нулевых байт, который остался от четырнадцати символьного массива sa_data. Для чего он нужен? Дело в том, что структура sockaddr не привязана именно к Интернету и может работать и с другими сетями. Адреса же некоторых сетей требуют для своего представления гораздо больше четырех байт, — вот и приходится брать с запасом!

Структура in_addr определяется следующим в образом:

  struct in_addr {
    union {
      struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
                  // IP-адрес

      struct { u_short s_w1,s_w2; } S_un_w;
                  // IP-адрес

      u_long S_addr;        // IP-алрес
    } S_un;
  }

Листинг 32 Определение структуры in_addr

Как видно, она состоит из одного IP-адреса, записанного в трех «ипостасях» — четырехбайтовой последовательности (S_un_b), пары двухбайтовых слов (S_un_W) и одного длинного целого (S_addr) — выбирай на вкус Но не все так просто! Во многих программах, технических руководствах и даже демонстрационных примерах, прилагающихся к Winsock SDK, встречается обращение к «таинственному» члену структуры s_addr, который явно не описан в SDK! Например, вот строка из файла «Simples.h»: «local.sin_addr.s_addr = (!interface)?INADDR_ANY:inet_addr(interface);»

Это что такое?! Заглянув в файл «winsock2.h» можно обнаружить следующее: «#define s_addr S_un.S_addr». Ага, да ведь это эквивалент s_addr, т.е. IP-адресу, записанному в виде длинного целого!

На практике можно с одинаковым успехом пользоваться как «устаревшей» sockaddr, так и «новомодной» sockaddr_in. Однако, поскольку, прототипы остальных функций не изменились, при использовании sockaddr_in придется постоянно выполнять явные преобразования, например так: «sockaddr_in dest_addr; connect (mysocket, (struct sockaddr*) &dest_addr, sizeof(dest_addr)».

Для преобразования IP-адреса, записанного в виде символьной последовательности наподобие «127.0.0.1» в четырехбайтовую числовую последовательность предназначена функция «unsigned long inet_addr (const char FAR * cp )«. Она принимает указатель на символьную строку и в случае успешной операции преобразует ее в четырехбайтовый IP адрес или –1 если это невозможно. Возвращенный функцией результат можно присвоить элементу структуры sockaddr_in следующим образом: «struct sockaddr_in dest_addr; dest_addr.sin_addr.S_addr=inet_addr(«195.161.42.222»);«. При использовании структуры sockaddr это будет выглядеть так: «struc sockaddr dest_addr; ((unsigned int *)(&dest_addr.sa_data[0]+2))[0] = inet_addr(«195.161.42.222»);»

Попытка передать inet_addr доменное имя узла приводит к провалу. Узнать IP-адрес такого-то домена можно с помощью функции «struct hostent FAR * gethostbyname (const char FAR * name);«. Функция обращается к DNS и возвращает свой ответ в структуре hostent или нуль если DNS сервер не смог определить IP-адрес данного домена.

Структура hostent выглядит следующим образом:

  struct hostent
  {
    char FAR *  h_name;    // официальное имя узла
    char FAR * FAR* h_aliases;  // альтернативные имена
              // узла (массив строк)

    short    h_addrtype;    // тип адреса 
    short    h_length;    // длина адреса
              // (как правило AF_INET)

    char FAR * FAR * h_addr_list;  // список указателей
              //на IP-адреса
              // ноль – конец списка
  };

Листинг 33 Определение структуры hostent

Как и в случае с in_addr, во множестве программ и прилагаемых к Winsock SDK примерах активно используется недокументированное поле структуры h_addr. Например, вот строка из файла «simplec.c» «memcpy(&(server.sin_addr),hp->h_addr,hp->h_length);» Заглянув в «winsock2.h» можно найти, что оно обозначает: «#define h_addr h_addr_list[0]«.

А вот это уже интересно! Дело в том, что с некоторыми доменными именами связано сразу несколько IP-адресов. В случае неработоспособности одного узла, клиент может попробовать подключится к другому или просто выбрать узел с наибольшей скоростью обмена. Но в приведенном примере клиент использует только первый IP-адрес в списке и игнорирует все остальные! Конечно, это не смертельно, но все же будет лучше, если в своих программах вы будете учитывать возможность подключения к остальным IP-адресам, при невозможности установить соединение с первым.

Функция gethostbyname ожидает на входе только доменные имена, но не цифровые IP-адреса. Между тем, правила «хорошего тона» требуют предоставления клиенту возможности как задания доменных имен, так и цифровых IP-адресов.

Решение заключается в следующем — необходимо проанализировать переданную клиентом строку — если это IP адрес, то передать его функции inet_addr в противном случае — gethostbyaddr, полагая, что это доменное имя. Для отличия IP-адресов от доменных имен многие программисты используют нехитрый трюк: если первый символ строки — цифра, это IP-адрес, иначе — имя домена. Однако, такой трюк не совсем честен — доменные имя могут начинаться с цифры, например, «666.ru», могут они и заканчиваться цифрой, например, к узлу «666.ru» члены cубдомена «666» могут так и обращаться — «666». Самое смешное, что (теоретически) могут существовать имена доменов, синтаксически неотличимые от IP-адресов! Поэтому, на взгляд автора данной статьи, лучше всего действовать так: передаем введенную пользователем строку функции inet_addr, если она возвращает ошибку, то вызываем gethostbyaddr.

Для решения обратной задачи – определении доменного имени по IP адресу предусмотрена функция «struct HOSTENT FAR * gethostbyaddr (const char FAR * addr, int len, int type)«, которая во всем аналогична gethostbyname, за тем исключением, что ее аргументом является не указатель на строку, содержащую имя, а указатель на четырехбайтовый IP-адрес. Еще два аргумента задают его длину и тип (соответственно, 4 и AF_INET).

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

Для преобразования IP-адреса, записанного в сетевом формате в символьную строку, предусмотрена функция «char FAR * inet_ntoa (struct in_addr)«, которая принимает на вход структуру in_addr, а возвращает указатель на строку, если преобразование выполнено успешно и ноль в противном случае.

Сетевой порядок байт

Среди производителей процессоров нет единого мнения на счет порядка следования младших и старших байт. Так например, у микропроцессоров Intel младшие байты располагаются по меньшим адресам, а у микропроцессоров Motorola 68000 — наоборот. Естественно, это вызывает проблемы при межсетевом взаимодействии, поэтому, был введен специальный сетевой порядок байт, предписывающий старший байт передавать первым (все не так, как у Intel).

Для преобразований чисел из сетевого формата в формат локального хоста и наоборот предусмотрено четыре функции — первые две манипулируют короткими целыми (16-битными словами), а две последние — длинными (32-битными двойными словами): u_short ntohs (u_short netshort); u_short htons (u_short hostshort ); u_long ntohl (u_long netlong ); u_long htonl (u_long hostlong);

Чтобы в них не запутаться, достаточно запомнить, что за буквой «n» скрывается сокращение «network», за «h» — «host» (подразумевается локальный), «s» и «l» соответственно короткое (short) и длинное (long) беззнаковые целые, а «to» обозначает преобразование. Например, «htons» расшифровывается так: «Host ( Network (short )» т.е. преобразовать короткое целое из формата локального хоста в сетевой формат.

Внимание: все значения, возвращенные socket-функциями уже находятся в сетевом формате и «вручную» их преобразовывать нельзя! Т.к. это преобразование исказит результат и приведен к неработоспособности.

Чаще всего к вызовам этих функций прибегают для преобразования номера порта согласно сетевому порядку. Например: dest_addr.sin_port = htons(110).

Дополнительные возможности

Для «тонкой» настойки сокетов предусмотрена функция «int setsockopt (SOCKET s, int level, int optname, const char FAR * optval, int optlen)«. Первый слева аргумент — дескриптор сокета, который собираются настраивать, level уровень настойки. С каждым уровнем связан свой набор опций. Всего определено два уровня — SOL_SOCKET и IPPROTO_TCP. В ограниченном объеме журнальной статьи перечислить все опции невозможно, поэтому, ниже будет рассказано только о самых интересных из них, а сведения обо всех остальных можно почерпнуть из Winsock SDK.

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

Уровень SOL_SOCKET:

  1. SO_RCVBUF (int) — задает размер входного буфера для приема данных. К TCP-окну никакого отношения не имеет, поэтому, может безболезненно варьироваться в широких пределах.
  2. SO_SNDBUF (int) — задает размер входного буфера для передачи данных. Увеличение размера буферов на медленных каналах приводит к задержкам и снижает производительность.

Уровень IPPROTO_TCP

  1. TCP_NODELAY (BOOL) — выключает Алгоритм Нагла. Алгоритм Нагла был разработан специально для прозрачного кэширования крохотных пакетов (тиниграмм). Когда один узел посылает другому несколько байт, к ним дописываются заголовки TCP и IP, которые в совокупности обычно занимают более 50 байт. Таким образом, при побайтовом обмене между узлами свыше 98% передаваемой по сети информации будет приходиться на служебные данные! Алгоритм Нагла состоит в следующем: отправляем первый пакет и, до тех пор, пока получатель не возвратит TCP-уведомление успешности доставки, не передаем в сеть никаких пакетов, а накапливаем их на локальном узле, собирая в один большой пакет. Такая техника совершенно прозрачна для прикладных приложений, и в то же время позволяет значительно оптимизировать трафик, но в некоторых (достаточно экзотических) случаях, когда требуется действительно побайтовый обмен, Алгоритм Нагла приходится отключать (по умолчанию он включен).

Для получения текущих значений опций сокета предусмотрена функция «int getsockopt (SOCKET s, int level, int optname, char FAR* optval, int FAR* optlen)» которая полностью аналогична предыдущей за исключением того, что не устанавливает опции, а возвращает их значения.

Сырые сокеты

Помимо потоковых и дейтаграммных сокетов существуют, так называемые, сырые (RAW) сокеты. Они предоставляют возможность «ручного» формирования TCPIP-пакетов, равно как и полного доступа к содержимому заголовков полученных TCPIP-пакетов, что необходимо многим сетевым сканерам, FireWall-ам, брандмаузерам и, разумеется, атакующим программам, например, устанавливающим в поле «адрес отправителя» адрес самого получателя.

Спецификация Winsock 1.x категорически не поддерживала сырых сокетов. В Winsock 2.x положение как будто было исправлено: по крайней мере формально такая поддержка появилась, и в SDK даже входил пример, демонстрирующий использование сырых сокетов для реализации утилиты ping. Однако, попытки использования сырых сокетов для всех остальных целей с треском проваливались — система упрямо игнорировала сформированный «вручную» IP (или TCP) пакет и создавала его самостоятельно.

Документация объясняла, что для самостоятельной обработки заголовков пакетов, опция IP_HDRINCL должна быть установлена. Весь фокус в том, что вызов «setsockopt(my_sock,IPPROTO_IP, IP_HDRINCL, &oki, sizeof(oki))» возвращал ошибку!

Таким образом, на прикладном уровне получить непосредственный доступ к заголовкам TCP/IP невозможно. Это препятствует переносу многих приложений из UNIX в Windows, более того, определенным образом ущемляет возможности самой Windows, не позволяя ей решать целый ряд задач, требующих поддержки сырых сокетов.

Законченные реализации

Ниже приведено четыре подробно комментированных исходных текста, реализующих простых TCP и UDP эхо-сервера и TCP и UDP клиентов. (Эхо сервер просто возвращает клиенту, полученные от него данные).

Для их компиляции с помощью Microsoft Visual C++ достаточно отдать команду: «cl.exe имя_файла.cpp ws2_32.lib».

Проверка работоспособности TCP-сервера: запустите TCP-сервер и наберите в командной строке Windows «telnet.exe 127.0.0.1 666», где 127.0.0.1 обозначает локальный адрес вашего узла (это специально зарезервированный для этой цели адрес и он выглядит одинаково для всех узлов), а 666 — номер порта на который «сел» сервер. Если все работает успешно, то telnet установит соединение и на экране появится приветствие «Hello, Sailor!». Теперь можно набирать на клавиатуре некоторый текст и получать его назад от сервера.

Проверка работоспособности TCP-клиента: запустите TCP-сервер и затем одну или несколько копий клиента. В каждом из них можно набирать некоторый текст на клавиатуре и после нажатия на Enter получать его обратно от сервера.

Проверка работоспособности UDP сервера и клиента: запустите UDP-сервер и одну или несколько копий клиента — в каждой из них можно набирать на клавиатуре некоторые данные и получать их обратно от сервера.

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

Будьте очень внимательны, а еще лучше, не входите в Интернет, пока не будете полностью уверенны, что сервера отлажены и не содержат ошибок!

Пример реализации TCP эхо-сервера

  // Пример простого TCP – эхо сервера

  #include <stdio.h>
  #include <winsock2.h>  // Wincosk2.h должен быть 
          // подключен раньше windows.h!
  #include <windows.h>

  #define MY_PORT    666
           // Порт, который слушает сервер

  // макрос для печати количества активных
  // пользователей 
  #define PRINTNUSERS if (nclients)
  printf("%d user on-linen",nclients);
  else printf("No User on linen");

  // прототип функции, обслуживающий
  // подключившихся пользователей
  DWORD WINAPI SexToClient(LPVOID client_socket);

  // глобальная переменная – количество
  // активных пользователей 
  int nclients = 0;

  int main(int argc, char* argv[])
  {
    char buff[1024];    // Буфер для различных нужд

    printf("TCP SERVER DEMOn");

    // Шаг 1 - Инициализация Библиотеки Сокетов
    // Т.к. возвращенная функцией информация
    // не используется ей передается указатель на
    // рабочий буфер, преобразуемый
    // к указателю  на структуру WSADATA.
    // Такой прием позволяет сэкономить одну
    // переменную, однако, буфер должен быть не менее
    // полкилобайта размером (структура WSADATA
    // занимает 400 байт)
    if (WSAStartup(0x0202,(WSADATA *) &buff[0])) 
    {
      // Ошибка!
	      printf("Error WSAStartup %dn",
             WSAGetLastError());
      return -1;
    }

    // Шаг 2 - создание сокета
    SOCKET mysocket;
    // AF_INET     - сокет Интернета
    // SOCK_STREAM  - потоковый сокет (с
    //      установкой соединения)
    // 0      - по умолчанию выбирается TCP протокол
    if ((mysocket=socket(AF_INET,SOCK_STREAM,0))<0)
    {
      // Ошибка!
      printf("Error socket %dn",WSAGetLastError());
      WSACleanup();
        // Деиницилизация библиотеки Winsock
      return -1;
    }

    // Шаг 3 связывание сокета с локальным адресом
    sockaddr_in local_addr;
    local_addr.sin_family=AF_INET;
    local_addr.sin_port=htons(MY_PORT);
             // не забываем о сетевом порядке!!!
    local_addr.sin_addr.s_addr=0;
             // сервер принимает подключения
             // на все IP-адреса

    // вызываем bind для связывания
    if (bind(mysocket,(sockaddr *) &local_addr,
                sizeof(local_addr)))
    {
      // Ошибка
      printf("Error bind %dn",WSAGetLastError());
      closesocket(mysocket;  // закрываем сокет!
      WSACleanup();
      return -1;
    }

    // Шаг 4 ожидание подключений
    // размер очереди – 0x100
    if (listen(mysocket, 0x100))
    {
      // Ошибка
      printf("Error listen %dn",WSAGetLastError());
      closesocket(mysocket);
      WSACleanup();
      return -1;
    }

    printf("Ожидание подключенийn");

    // Шаг 5 извлекаем сообщение из очереди
    SOCKET client_socket;    // сокет для клиента
    sockaddr_in client_addr;    // адрес клиента
              // (заполняется системой)

    // функции accept необходимо передать размер
    // структуры
    int client_addr_size=sizeof(client_addr);

    // цикл извлечения запросов на подключение из
    // очереди
    while((client_socket=accept(mysocket, (sockaddr *)
            &client_addr, &client_addr_size)))
    {
      nclients++;      // увеличиваем счетчик
              // подключившихся клиентов

      // пытаемся получить имя хоста
      HOSTENT *hst;
      hst=gethostbyaddr((char *)
          &client_addr.sin_addr.s_addr,4, AF_INET);

      // вывод сведений о клиенте
      printf("+%s [%s] new connect!n",
      (hst)?hst->h_name:"",
      inet_ntoa(client_addr.sin_addr));
      PRINTNUSERS

      // Вызов нового потока для обслужвания клиента
      // Да, для этого рекомендуется использовать
      // _beginthreadex но, поскольку никаких вызов
      // функций стандартной Си библиотеки поток не
      // делает, можно обойтись и CreateThread
      DWORD thID;
      CreateThread(NULL,NULL,SexToClient,
              &client_socket,NULL,&thID);
    }
    return 0;
  }

  // Эта функция создается в отдельном потоке и
  // обсуживает очередного подключившегося клиента
  // независимо от остальных
  DWORD WINAPI SexToClient(LPVOID client_socket)
  {
    SOCKET my_sock;
    my_sock=((SOCKET *) client_socket)[0];
    char buff[20*1024];
    #define sHELLO "Hello, Sailorrn"

    // отправляем клиенту приветствие 
    send(my_sock,sHELLO,sizeof(sHELLO),0);

    // цикл эхо-сервера: прием строки от клиента и
    // возвращение ее клиенту
    while( (int bytes_recv=
                 recv(my_sock,&buff[0],sizeof(buff),0))
              && bytes_recv !=SOCKET_ERROR)
      send(my_sock,&buff[0],bytes_recv,0);

    // если мы здесь, то произошел выход из цикла по
    // причине возращения функцией recv ошибки –
    // соединение клиентом разорвано
    nclients--; // уменьшаем счетчик активных клиентов
    printf("-disconnectn"); PRINTNUSERS

    // закрываем сокет
    closesocket(my_sock);
    return 0;
  }

Листинг 34 Пример реализации TCP эхо-сервера

Пример реализации TCP-клиента

  // Пример простого TCP клиента
  #include <stdio.h>
  #include <string.h>
  #include <winsock2.h>
  #include <windows.h>


  #define PORT 666
  #define SERVERADDR "127.0.0.1"

  int main(int argc, char* argv[])
  {
    char buff[1024];
    printf("TCP DEMO CLIENTn");

    // Шаг 1 - инициализация библиотеки Winsock
    if (WSAStartup(0x202,(WSADATA *)&buff[0]))
    {
      printf("WSAStart error %dn",WSAGetLastError());
      return -1;
    }

    // Шаг 2 - создание сокета
    SOCKET my_sock;
    my_sock=socket(AF_INET,SOCK_STREAM,0);
    if (my_sock < 0)
    {
      printf("Socket() error %dn",WSAGetLastError());
      return -1;
    }

    // Шаг 3 - установка соединения

    // заполнение структуры sockaddr_in
    // указание адреса и порта сервера
    sockaddr_in dest_addr;
    dest_addr.sin_family=AF_INET;
    dest_addr.sin_port=htons(PORT);
    HOSTENT *hst;

    // преобразование IP адреса из символьного в
    // сетевой формат
    if (inet_addr(SERVERADDR)!=INADDR_NONE)
      dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
    else
      // попытка получить IP адрес по доменному
      // имени сервера
      if (hst=gethostbyname(SERVERADDR))
      // hst->h_addr_list содержит не массив адресов,
      // а массив указателей на адреса
      ((unsigned long *)&dest_addr.sin_addr)[0]=
        ((unsigned long **)hst->h_addr_list)[0][0];
      else 
      {
        printf("Invalid address %sn",SERVERADDR);
        closesocket(my_sock);
        WSACleanup();
        return -1;
      }

    // адрес сервера получен – пытаемся установить
    // соединение 
    if (connect(my_sock,(sockaddr *)&dest_addr,
                sizeof(dest_addr)))
    {
      printf("Connect error %dn",WSAGetLastError());
      return -1;
    }

    printf("Соединение с %s успешно установленоn
    Type quit for quitnn",SERVERADDR);

    // Шаг 4 - чтение и передача сообщений
    int nsize;
    while((nsize=recv(my_sock,&buff[0],
                      sizeof(buff)-1,0))
                  !=SOCKET_ERROR)
    {
      // ставим завершающий ноль в конце строки 
      buff[nsize]=0;

      // выводим на экран 
      printf("S=>C:%s",buff);

      // читаем пользовательский ввод с клавиатуры
      printf("S<=C:"); fgets(&buff[0],sizeof(buff)-1,
             stdin);

      // проверка на "quit"
      if (!strcmp(&buff[0],"quitn"))
      {
        // Корректный выход
        printf("Exit...");
        closesocket(my_sock);
        WSACleanup();
        return 0;
      }

      // передаем строку клиента серверу
      send(my_sock,&buff[0],nsize,0);
    }

    printf("Recv error %dn",WSAGetLastError());
    closesocket(my_sock);
    WSACleanup();
    return -1;
  }

Листинг 35 Пример реализации TCP-клиента

Пример реализации UDP-сервера

  // Пример простого UDP-эхо сервера
  #include <stdio.h>
  #include <winsock2.h>

  #define PORT 666    // порт сервера
  #define sHELLO "Hello, %s [%s] Sailorn"

  int main(int argc, char* argv[])
  {
    char buff[1024];

    printf("UDP DEMO echo-Servern");

    // шаг 1 - подключение библиотеки 
    if (WSAStartup(0x202,(WSADATA *) &buff[0]))
    {
      printf("WSAStartup error: %dn",
             WSAGetLastError());
      return -1;
    }

    // шаг 2 - создание сокета
    SOCKET my_sock;
    my_sock=socket(AF_INET,SOCK_DGRAM,0);
    if (my_sock==INVALID_SOCKET)
    {
      printf("Socket() error: %dn",WSAGetLastError());
      WSACleanup();
      return -1;
    }

    // шаг 3 - связывание сокета с локальным адресом 
    sockaddr_in local_addr;
    local_addr.sin_family=AF_INET;
    local_addr.sin_addr.s_addr=INADDR_ANY;
    local_addr.sin_port=htons(PORT);

    if (bind(my_sock,(sockaddr *) &local_addr,
        sizeof(local_addr)))
    {
      printf("bind error: %dn",WSAGetLastError());
      closesocket(my_sock);
      WSACleanup();
      return -1;
    }

    // шаг 4 обработка пакетов, присланных клиентами
    while(1)
    {
      sockaddr_in client_addr;
      int client_addr_size = sizeof(client_addr);
      int bsize=recvfrom(my_sock,&buff[0],
        sizeof(buff)-1,0,
        (sockaddr *) &client_addr, &client_addr_size);
      if (bsize==SOCKET_ERROR)
      printf("recvfrom() error: %dn",
             WSAGetLastError());

      // Определяем IP-адрес клиента и прочие атрибуты
      HOSTENT *hst;
      hst=gethostbyaddr((char *)
            &client_addr.sin_addr,4,AF_INET);
      printf("+%s [%s:%d] new DATAGRAM!n",
      (hst)?hst->h_name:"Unknown host",
      inet_ntoa(client_addr.sin_addr),
      ntohs(client_addr.sin_port));

      // добавление завершающего нуля
      buff[bsize]=0;

      // Вывод на экран 
      printf("C=>S:%sn",&buff[0]);

      // посылка датаграммы клиенту
      sendto(my_sock,&buff[0],bsize,0,
        (sockaddr *)&client_addr, sizeof(client_addr));
    }
    return 0;
  }

Листинг 36 Пример реализации UDP-сервера

Пример реализации UDP-клиента

  // пример простого UDP-клиента
  #include <stdio.h>
  #include <string.h>
  #include <winsock2.h>
  #include <windows.h>

  #define PORT 666
  #define SERVERADDR "127.0.0.1"

  int main(int argc, char* argv[])
  {
    char buff[10*1014];
    printf("UDP DEMO ClientnType quit to quitn");

    // Шаг 1 - иницилизация библиотеки Winsocks
    if (WSAStartup(0x202,(WSADATA *)&buff[0]))
    {
      printf("WSAStartup error: %dn",
             WSAGetLastError());
      return -1;
    }

    // Шаг 2 - открытие сокета
    SOCKET my_sock=socket(AF_INET, SOCK_DGRAM, 0);
    if (my_sock==INVALID_SOCKET)
    {
      printf("socket() error: %dn",WSAGetLastError());
      WSACleanup();
      return -1;
    }

    // Шаг 3 - обмен сообщений с сервером
    HOSTENT *hst;
    sockaddr_in dest_addr;

    dest_addr.sin_family=AF_INET;
    dest_addr.sin_port=htons(PORT);

    // определение IP-адреса узла
    if (inet_addr(SERVERADDR))
      dest_addr.sin_addr.s_addr=inet_addr(SERVERADDR);
    else
      if (hst=gethostbyname(SERVERADDR))
        dest_addr.sin_addr.s_addr=((unsigned long **)
              hst->h_addr_list)[0][0];
    else
      {
        printf("Unknown host: %dn",WSAGetLastError());
        closesocket(my_sock);
        WSACleanup();
        return -1;
      }

    while(1)
    {
      // чтение сообщения с клавиатуры
      printf("S<=C:");fgets(&buff[0],sizeof(buff)-1,
             stdin);
      if (!strcmp(&buff[0],"quitn")) break;

      // Передача сообщений на сервер
      sendto(my_sock,&buff[0],strlen(&buff[0]),0,
        (sockaddr *) &dest_addr,sizeof(dest_addr));

      // Прием сообщения с сервера
      sockaddr_in server_addr;
      int server_addr_size=sizeof(server_addr);

      int n=recvfrom(my_sock,&buff[0],sizeof(buff)-1,0,
        (sockaddr *) &server_addr, &server_addr_size);

      if (n==SOCKET_ERROR)
      {
        printf("recvfrom() error:"
          "%dn",WSAGetLastError());
        closesocket(my_sock);
        WSACleanup();
        return -1;
      }

      buff[n]=0;

      // Вывод принятого с сервера сообщения на экран
      printf("S=>C:%s",&buff[0]);
    }

    // шаг последний - выход
    closesocket(my_sock);
    WSACleanup();

    return 0;
  }

Листинг 37 Пример реализации UDP-клиента

Аннотация
Статьи из книги

[Заказать книгу в магазине «Мистраль»]

Новости мира IT:

  • 02.02 — Власти РФ согласовали сделку по продаже «Билайна» российскому топ-менеджменту — акции компании выросли
  • 02.02 — Сергей Брин снова начал работать программистом в Google
  • 02.02 — МТС начнёт выпускать в Твери автомобильные мультимедийные системы с навигацией
  • 02.02 — В России возник дефицит чипов для документов — проблема затронула загранпаспорта нового образца
  • 01.02 — Microsoft прекратила прямые продажи Windows 10
  • 01.02 — «Яндекс» интегрирует в поиск и другие сервисы свой аналог ChatGPT до конца года
  • 01.02 — Солнце и ветер дали Европе больше электроэнергии, чем любой другой источник в 2022 году
  • 01.02 — Google тестирует на своих сотрудниках потенциальных конкурентов ChatGPT, включая бот Apprentice Bard
  • 01.02 — Создатель ChatGPT разработал инструмент для выявления текстов, написанных ИИ
  • 30.01 — Роскомнадзор получил более 44 тыс. жалоб о неправомерной обработке персональных данных в 2022 году
  • 30.01 — На Apple подали в суд из-за сбора данных пользователей
  • 30.01 — «Ростелеком», возможно, интересуется покупкой «Мегафона»
  • 30.01 — В идеале Apple стремится создать очки дополненной реальности, которые можно носить весь день
  • 30.01 — Российские мобильные операторы перешли на отечественную техподдержку
  • 30.01 — Продажа «Билайна» российскому топ-менеджменту затягивается
  • 27.01 — «Яндекс» попал в десятку самых посещаемых сайтов в мире
  • 27.01 — В списке кредиторов криптобиржи FTX оказались Apple, Google, Amazon, Microsoft, а также авиакомпании, СМИ и университеты
  • 27.01 — IBM и SAP показали хорошие квартальные результаты, но всё равно сокращают рабочие места
  • 27.01 — От антимонопольного иска против Google выиграет Apple и другие компании
  • 26.01 — Власти США подали иск, чтобы заблокировать сделку между Microsoft и Activision Blizzard

Архив новостей

  • 4VPS.SU: виртуальные серверы в 17 странах мира на любой бюджет

Socket programming with winsock

This is a quick guide/tutorial to learning socket programming in C language on Windows. «Windows» because the code snippets shown over here will work only on Windows. The windows api to socket programming is called winsock.

Sockets are the fundamental «things» behind any kind of network communications done by your computer. For example when you type www.google.com in your web browser, it opens a socket and connects to google.com to fetch the page and show it to you.

Same with any chat client like gtalk or skype. Any network communication goes through a socket.

Before you begin

This tutorial assumes that you have basic knowledge of C and pointers. Also download Visual C++ 2010 Express Edition.

Initialising Winsock

Winsock first needs to be initialiased like this :

/*
	Initialise Winsock
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	
	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.");

	return 0;
}

winsock2.h is the header file to be included for winsock functions. ws2_32.lib is the library file to be linked with the program to be able to use winsock functions.

The WSAStartup function is used to start or initialise winsock library. It takes 2 parameters ; the first one is the version we want to load and second one is a WSADATA structure which will hold additional information after winsock has been loaded.

If any error occurs then the WSAStartup function would return a non zero value and WSAGetLastError can be used to get more information about what error happened.

OK , so next step is to create a socket.

Creating a socket

The socket() function is used to create a socket.
Here is a code sample :

/*
	Create a TCP socket
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");

	return 0;
}

Function socket() creates a socket and returns a socket descriptor which can be used in other network commands. The above code will create a socket of :

Address Family : AF_INET (this is IP version 4)
Type : SOCK_STREAM (this means connection oriented TCP protocol)
Protocol : 0 [ or IPPROTO_TCP , IPPROTO_UDP ]

It would be a good idea to read some documentation here

Ok , so you have created a socket successfully. But what next ? Next we shall try to connect to some server using this socket. We can connect to www.google.com

Note

Apart from SOCK_STREAM type of sockets there is another type called SOCK_DGRAM which indicates the UDP protocol. This type of socket is non-connection socket. In this tutorial we shall stick to SOCK_STREAM or TCP sockets.

Connect to a Server

We connect to a remote server on a certain port number. So we need 2 things , IP address and port number to connect to.

To connect to a remote server we need to do a couple of things. First is create a sockaddr_in structure with proper values filled in. Lets create one for ourselves :

struct sockaddr_in server;

Have a look at the structures

// IPv4 AF_INET sockets:
struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};


typedef 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;
} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;


struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

The sockaddr_in has a member called sin_addr of type in_addr which has a s_addr which is nothing but a long. It contains the IP address in long format.

Function inet_addr is a very handy function to convert an IP address to a long format. This is how you do it :

server.sin_addr.s_addr = inet_addr("74.125.235.20");

So you need to know the IP address of the remote server you are connecting to. Here we used the ip address of google.com as a sample. A little later on we shall see how to find out the ip address of a given domain name.

The last thing needed is the connect function. It needs a socket and a sockaddr structure to connect to. Here is a code sample.

/*
	Create a TCP socket
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;
	struct sockaddr_in server;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	
	server.sin_addr.s_addr = inet_addr("74.125.235.20");
	server.sin_family = AF_INET;
	server.sin_port = htons( 80 );

	//Connect to remote server
	if (connect(s , (struct sockaddr *)&server , sizeof(server)) < 0)
	{
		puts("connect error");
		return 1;
	}
	
	puts("Connected");

	return 0;
}

It cannot be any simpler. It creates a socket and then connects. If you run the program it should show Connected.
Try connecting to a port different from port 80 and you should not be able to connect which indicates that the port is not open for connection.

OK , so we are now connected. Lets do the next thing , sending some data to the remote server.

Quick Note

The concept of «connections» apply to SOCK_STREAM/TCP type of sockets. Connection means a reliable «stream» of data such that there can be multiple such streams each having communication of its own. Think of this as a pipe which is not interfered by other data.

Other sockets like UDP , ICMP , ARP dont have a concept of «connection». These are non-connection based communication. Which means you keep sending or receiving packets from anybody and everybody.

Sending Data

Function send will simply send data. It needs the socket descriptor , the data to send and its size.
Here is a very simple example of sending some data to google.com ip :

/*
	Create a TCP socket
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;
	struct sockaddr_in server;
	char *message;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	
	server.sin_addr.s_addr = inet_addr("74.125.235.20");
	server.sin_family = AF_INET;
	server.sin_port = htons( 80 );

	//Connect to remote server
	if (connect(s , (struct sockaddr *)&server , sizeof(server)) < 0)
	{
		puts("connect error");
		return 1;
	}
	
	puts("Connected");
	
	//Send some data
	message = "GET / HTTP/1.1rnrn";
	if( send(s , message , strlen(message) , 0) < 0)
	{
		puts("Send failed");
		return 1;
	}
	puts("Data Sendn");

	return 0;
}

In the above example , we first connect to an ip address and then send the string message «GET / HTTP/1.1rnrn» to it.
The message is actually a http command to fetch the mainpage of a website.

Now that we have send some data , its time to receive a reply from the server. So lets do it.

Receiving Data

Function recv is used to receive data on a socket. In the following example we shall send the same message as the last example and receive a reply from the server.

/*
	Create a TCP socket
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;
	struct sockaddr_in server;
	char *message , server_reply[2000];
	int recv_size;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	
	server.sin_addr.s_addr = inet_addr("74.125.235.20");
	server.sin_family = AF_INET;
	server.sin_port = htons( 80 );

	//Connect to remote server
	if (connect(s , (struct sockaddr *)&server , sizeof(server)) < 0)
	{
		puts("connect error");
		return 1;
	}
	
	puts("Connected");
	
	//Send some data
	message = "GET / HTTP/1.1rnrn";
	if( send(s , message , strlen(message) , 0) < 0)
	{
		puts("Send failed");
		return 1;
	}
	puts("Data Sendn");
	
	//Receive a reply from the server
	if((recv_size = recv(s , server_reply , 2000 , 0)) == SOCKET_ERROR)
	{
		puts("recv failed");
	}
	
	puts("Reply receivedn");

	//Add a NULL terminating character to make it a proper string before printing
	server_reply[recv_size] = '
/*
	Create a TCP socket
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;
	struct sockaddr_in server;
	char *message , server_reply[2000];
	int recv_size;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	
	server.sin_addr.s_addr = inet_addr("74.125.235.20");
	server.sin_family = AF_INET;
	server.sin_port = htons( 80 );

	//Connect to remote server
	if (connect(s , (struct sockaddr *)&server , sizeof(server)) < 0)
	{
		puts("connect error");
		return 1;
	}
	
	puts("Connected");
	
	//Send some data
	message = "GET / HTTP/1.1rnrn";
	if( send(s , message , strlen(message) , 0) < 0)
	{
		puts("Send failed");
		return 1;
	}
	puts("Data Sendn");
	
	//Receive a reply from the server
	if((recv_size = recv(s , server_reply , 2000 , 0)) == SOCKET_ERROR)
	{
		puts("recv failed");
	}
	
	puts("Reply receivedn");

	//Add a NULL terminating character to make it a proper string before printing
	server_reply[recv_size] = '';
	puts(server_reply);

	return 0;
}

';
puts(server_reply);

return 0;
}

Here is the output of the above code :

Initialising Winsock...Initialised.
Socket created.
Connected
Data Send
Reply received
HTTP/1.1 302 Found
Location: http://www.google.co.in/
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: PREF=ID=7da819edfd7af808:FF=0:TM=1324882923:LM=1324882923:S=PdlMu0TE
E3QKrmdB; expires=Wed, 25-Dec-2013 07:02:03 GMT; path=/; domain=.google.com
Date: Mon, 26 Dec 2011 07:02:03 GMT
Server: gws
Content-Length: 221
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.in/">here</A>.
</BODY></HTML>
Press any key to continue

We can see what reply was send by the server. It looks something like Html, well IT IS html. Google.com replied with the content of the page we requested. Quite simple!

Now that we have received our reply, its time to close the socket.

Close socket

Function closesocket is used to close the socket. Also WSACleanup must be called to unload the winsock library (ws2_32.dll).

closesocket(s);
WSACleanup();

Thats it.

Lets Revise

So in the above example we learned how to :
1. Create a socket
2. Connect to remote server
3. Send some data
4. Receive a reply

Its useful to know that your web browser also does the same thing when you open www.google.com
This kind of socket activity represents a CLIENT. A client is a system that connects to a remote system to fetch or retrieve data.

The other kind of socket activity is called a SERVER. A server is a system that uses sockets to receive incoming connections and provide them with data. It is just the opposite of Client. So www.google.com is a server and your web browser is a client. Or more technically www.google.com is a HTTP Server and your web browser is an HTTP client.

Now its time to do some server tasks using sockets. But before we move ahead there are a few side topics that should be covered just incase you need them.

Get IP address of a hostname/domain

When connecting to a remote host , it is necessary to have its IP address. Function gethostbyname is used for this purpose. It takes the domain name as the parameter and returns a structure of type hostent. This structure has the ip information. It is present in netdb.h. Lets have a look at this structure

/* Description of data base entry for a single host.  */
struct hostent
{
  char *h_name;			/* Official name of host.  */
  char **h_aliases;		/* Alias list.  */
  int h_addrtype;		/* Host address type.  */
  int h_length;			/* Length of address.  */
  char **h_addr_list;		/* List of addresses from name server.  */
};

The h_addr_list has the IP addresses. So now lets have some code to use them.

/*
	Get IP address from domain name
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	char *hostname = "www.google.com";
	char ip[100];
	struct hostent *he;
	struct in_addr **addr_list;
	int i;
	
	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
		
	if ( (he = gethostbyname( hostname ) ) == NULL) 
	{
		//gethostbyname failed
		printf("gethostbyname failed : %d" , WSAGetLastError());
		return 1;
	}
	
	//Cast the h_addr_list to in_addr , since h_addr_list also has the ip address in long format only
	addr_list = (struct in_addr **) he->h_addr_list;
	
	for(i = 0; addr_list[i] != NULL; i++) 
	{
		//Return the first one;
		strcpy(ip , inet_ntoa(*addr_list[i]) );
	}
	
	printf("%s resolved to : %sn" , hostname , ip);
	return 0;
	return 0;
}

Output of the code would look like :

www.google.com resolved to : 74.125.235.20

So the above code can be used to find the ip address of any domain name. Then the ip address can be used to make a connection using a socket.

Function inet_ntoa will convert an IP address in long format to dotted format. This is just the opposite of inet_addr.

So far we have see some important structures that are used. Lets revise them :

1. sockaddr_in — Connection information. Used by connect , send , recv etc.
2. in_addr — Ip address in long format
3. sockaddr
4. hostent — The ip addresses of a hostname. Used by gethostbyname

Server Concepts

OK now onto server things. Servers basically do the following :

1. Open a socket
2. Bind to a address(and port).
3. Listen for incoming connections.
4. Accept connections
5. Read/Send

We have already learnt how to open a socket. So the next thing would be to bind it.

Bind a socket

Function bind can be used to bind a socket to a particular address and port. It needs a sockaddr_in structure similar to connect function.

Lets see a code example :

/*
	Bind socket to port 8888 on localhost
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s;
	struct sockaddr_in server;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	//Prepare the sockaddr_in structure
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons( 8888 );
	
	//Bind
	if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
	{
		printf("Bind failed with error code : %d" , WSAGetLastError());
	}
	
	puts("Bind done");
	
	closesocket(s);

	return 0;
}

Now that bind is done, its time to make the socket listen to connections. We bind a socket to a particular IP address and a certain port number. By doing this we ensure that all incoming data which is directed towards this port number is received by this application.

This makes it obvious that you cannot have 2 sockets bound to the same port.

Listen for connections

After binding a socket to a port the next thing we need to do is listen for connections. For this we need to put the socket in listening mode. Function listen is used to put the socket in listening mode. Just add the following line after bind.

//Listen
listen(s , 3);

Thats all. Now comes the main part of accepting new connections.

Accept connection

Function accept is used for this. Here is the code

/*
	Bind socket to port 8888 on localhost
*/

#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s , new_socket;
	struct sockaddr_in server , client;
	int c;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	//Prepare the sockaddr_in structure
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons( 8888 );
	
	//Bind
	if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
	{
		printf("Bind failed with error code : %d" , WSAGetLastError());
	}
	
	puts("Bind done");
	

	//Listen to incoming connections
	listen(s , 3);
	
	//Accept and incoming connection
	puts("Waiting for incoming connections...");
	
	c = sizeof(struct sockaddr_in);
	new_socket = accept(s , (struct sockaddr *)&client, &c);
	if (new_socket == INVALID_SOCKET)
	{
		printf("accept failed with error code : %d" , WSAGetLastError());
	}
	
	puts("Connection accepted");

	closesocket(s);
	WSACleanup();

	return 0;
}

Output

Run the program. It should show

Initialising Winsock...Initialised.
Socket created.
Bind done
Waiting for incoming connections...

So now this program is waiting for incoming connections on port 8888. Dont close this program , keep it running.
Now a client can connect to it on this port. We shall use the telnet client for testing this. Open a terminal and type

telnet localhost 8888

And the server output will show

Initialising Winsock...Initialised.
Socket created.
Bind done
Waiting for incoming connections...
Connection accepted
Press any key to continue

So we can see that the client connected to the server. Try the above process till you get it perfect.

Note

You can get the ip address of client and the port of connection by using the sockaddr_in structure passed to accept function. It is very simple :

char *client_ip = inet_ntoa(client.sin_addr);
int client_port = ntohs(client.sin_port);

We accepted an incoming connection but closed it immediately. This was not very productive. There are lots of things that can be done after an incoming connection is established. Afterall the connection was established for the purpose of communication. So lets reply to the client.

Here is an example :

/*
	Bind socket to port 8888 on localhost
*/
#include<io.h>
#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s , new_socket;
	struct sockaddr_in server , client;
	int c;
	char *message;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	//Prepare the sockaddr_in structure
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons( 8888 );
	
	//Bind
	if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
	{
		printf("Bind failed with error code : %d" , WSAGetLastError());
	}
	
	puts("Bind done");

	//Listen to incoming connections
	listen(s , 3);
	
	//Accept and incoming connection
	puts("Waiting for incoming connections...");
	
	c = sizeof(struct sockaddr_in);
	new_socket = accept(s , (struct sockaddr *)&client, &c);
	if (new_socket == INVALID_SOCKET)
	{
		printf("accept failed with error code : %d" , WSAGetLastError());
	}
	
	puts("Connection accepted");

	//Reply to client
	message = "Hello Client , I have received your connection. But I have to go now, byen";
	send(new_socket , message , strlen(message) , 0);
	
	getchar();

	closesocket(s);
	WSACleanup();
	
	return 0;
}

Run the above code in 1 terminal. And connect to this server using telnet from another terminal and you should see this :

Hello Client , I have received your connection. But I have to go now, bye

So the client(telnet) received a reply from server. We had to use a getchar because otherwise the output would scroll out of the client terminal without waiting

We can see that the connection is closed immediately after that simply because the server program ends after accepting and sending reply. A server like www.google.com is always up to accept incoming connections.

It means that a server is supposed to be running all the time. Afterall its a server meant to serve. So we need to keep our server RUNNING non-stop. The simplest way to do this is to put the accept in a loop so that it can receive incoming connections all the time.

Live Server

So a live server will be alive for all time. Lets code this up :

/*
	Live Server on port 8888
*/
#include<io.h>
#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib,"ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
	WSADATA wsa;
	SOCKET s , new_socket;
	struct sockaddr_in server , client;
	int c;
	char *message;

	printf("nInitialising Winsock...");
	if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
	{
		printf("Failed. Error Code : %d",WSAGetLastError());
		return 1;
	}
	
	printf("Initialised.n");
	
	//Create a socket
	if((s = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
	{
		printf("Could not create socket : %d" , WSAGetLastError());
	}

	printf("Socket created.n");
	
	//Prepare the sockaddr_in structure
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons( 8888 );
	
	//Bind
	if( bind(s ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
	{
		printf("Bind failed with error code : %d" , WSAGetLastError());
		exit(EXIT_FAILURE);
	}
	
	puts("Bind done");

	//Listen to incoming connections
	listen(s , 3);
	
	//Accept and incoming connection
	puts("Waiting for incoming connections...");
	
	c = sizeof(struct sockaddr_in);
	
	while( (new_socket = accept(s , (struct sockaddr *)&client, &c)) != INVALID_SOCKET )
	{
		puts("Connection accepted");
		
		//Reply to the client
		message = "Hello Client , I have received your connection. But I have to go now, byen";
		send(new_socket , message , strlen(message) , 0);
	}
	
	if (new_socket == INVALID_SOCKET)
	{
		printf("accept failed with error code : %d" , WSAGetLastError());
		return 1;
	}

	closesocket(s);
	WSACleanup();
	
	return 0;
}

We havent done a lot there. Just the accept was put in a loop.

Now run the program in 1 terminal , and open 3 other terminals. From each of the 3 terminal do a telnet to the server port.

Run telnet like this. It will launch the interactive prompt.

C:>telnet

At the telnet shell, run the command «open localhost 8888». This command will try to connect to localhost on port number 8888.

Welcome to Microsoft Telnet Client
Escape Character is 'CTRL+]'
Microsoft Telnet> open localhost 8888

Next you should see the following message at the telnet prompt. This message is received from the socket server running on port 8888.

Hello Client , I have received your connection. But I have to go now, bye

On the other hand, the server terminal would show the following messages, indicating that a client connected to it.

Initialising Winsock...Initialised.
Socket created.
Bind done
Waiting for incoming connections...
Connection accepted
Connection accepted

So now the server is running nonstop and the telnet terminals are also connected nonstop. Now close the server program.

All telnet terminals would show «Connection to host lost.»
Good so far. But still there is not effective communication between the server and the client.

The server program accepts connections in a loop and just send them a reply, after that it does nothing with them. Also it is not able to handle more than 1 connection at a time. So now its time to handle the connections , and handle multiple connections together.

Handling Connections

To handle every connection we need a separate handling code to run along with the main server accepting connections.
One way to achieve this is using threads. The main server program accepts a connection and creates a new thread to handle communication for the connection, and then the server goes back to accept more connections.

We shall now use threads to create handlers for each connection the server accepts. Lets do it pal.


Run the above server and open 3 terminals like before. Now the server will create a thread for each client connecting to it.

The telnet terminals would show :


This one looks good , but the communication handler is also quite dumb. After the greeting it terminates. It should stay alive and keep communicating with the client.

One way to do this is by making the connection handler wait for some message from a client as long as the client is connected. If the client disconnects , the connection handler ends.

So the connection handler can be rewritten like this :


The above connection handler takes some input from the client and replies back with the same. Simple! Here is how the telnet output might look


So now we have a server thats communicative. Thats useful now.

Conclusion

The winsock api is quite similar to Linux sockets in terms of function name and structures. Few differences exist like :

1. Winsock needs to be initialised with the WSAStartup function. No such thing in linux.

2. Header file names are different. Winsock needs winsock2.h , whereas Linux needs socket.h , apra/inet.h , unistd.h and many others.

3. Winsock function to close a socket is closesocket , whereas on Linux it is close.
On Winsock WSACleanup must also be called to unload the winsock dll.

4. On winsock the error number is fetched by the function WSAGetLastError(). On Linux the errno variable from errno.h file is filled with the error number.

And there are many more differences as we go deep.

By now you must have learned the basics of socket programming in C. You can try out some experiments like writing a chat client or something similar.

If you think that the tutorial needs some addons or improvements or any of the code snippets above dont work then feel free to make a comment below so that it gets fixed.

Понравилась статья? Поделить с друзьями:
  • Windows socket error code 10060 openvpn
  • Windows socket error 10048 on api bind
  • Windows smm security mitigations table что это
  • Windows smartscreen не дает установить программу
  • Windows smartscreen как отключить windows server