Учебник Джонни по Winsock

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

Вот что мы будем охватывать:

  • Создание сокета: Учитывая небольшую армию сетевых функций, мы можем построить программу, которая терпеливо ждет входящих соединений? (Да, можем.)
  • Создание собственных соединений: Учитывая еще несколько функций, мы можем создать программу, которая успешно соединяет с сервером для прослушивания? (Да.)
  • Отправка и прием: После того, как мы достигли активного подключения, как мы используем ее для обмена данными между двумя программами? (Угадали - send() and recv().)
  • Больше учебников и ссылок: Какие ресурсы есть сверх этого урока? подчеркните 3 которые займут вас ненадолго (после того как вы переварите мой учебник, конечно :-).

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

Не стесняйтесь скачать весь список уроков по коду. Помните, что любой код, представленный в этом учебнике, должен быть связан с библиотекой Winsock, обычно wsock32.lib или что-то подобное. Кроме того, при использовании кода в точности, как представлено в учебнике в вашем IDE (Dev-C++, Microsoft VC++, C++ Builder, итд.), выберите создать проект Windows с WinMain() во избежание ошибок.

 

Создание Прослушивающего сокета (Listening Socket)

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

  • WSADATA: Эта структура используется для запроса операционной системы для версии Winsock, которую требует наш код. Приложение вызывает WSAStartup () для инициализации правильного Winsock DLL.
  • SOCKET: Объект (фактически, он определяется как u_int, целое число без знака, в winsock.h--- хорошо знать для болтовни на вечеринках) используется приложениями для хранения дескриптора сокета.
  • SOCKADDR_IN: Приложение использует эту структуру, чтобы указать, как должен работать сокет. SOCKADDR_IN содержит поля для IP-адреса и номера порта:
struct sockaddr_in

{

  short sin_family;         // тип протокола

  u_short sin_port;         // порт-номер сокета

  struct in_addr sin_addr;  // IP-адрес

  char sin_zero[8];         // не используется

};

 

Первое поле типа протокола, как правило, AF_INET (TCP/IP). Поскольку прослушивающий сокет не связан с сетевым адресом машины, на которой он находится, Winsock автоматически назначает IP-адрес и номер порта для прослушивающих сокетов во время создания.

Мы будем строить наш первый прослушивающий сервер с вышеуказанными структурами и небольшой армией сетевых функций:

#include <windows.h>

#include <winsock.h>

#include <stdio.h>


#define NETWORK_ERROR -1

#define NETWORK_OK     0


void ReportError(int, const char *);




int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)

{

        WORD sockVersion;

        WSADATA wsaData;

        int nret;


        sockVersion = MAKEWORD(1, 1);                 // нам бы Winsock версии 1.1



        // Мы начинаем с инициализации Winsock

        WSAStartup(sockVersion, &wsaData);



        // Затем создаем прослушивающий сокет

        SOCKET listeningSocket;



        listeningSocket = socket(AF_INET,             // идем через TCP/IP

                                SOCK_STREAM,          // Это сокет, ориентированный на поток

                                IPPROTO_TCP);         // Используйте TCP вместо UDP


        if (listeningSocket == INVALID_SOCKET)

        {

               nret = WSAGetLastError();             // Получите более подробную ошибку

               ReportError(nret, "socket()");        // Сообщите об ошибке с помощью нашей пользовательской функции


               WSACleanup();                         // Закрываем Winsock

               return NETWORK_ERROR;                 // Верните значение ошибки

        }



        // используйте SOCKADDR_IN структуру для заполнения адресной информации

        SOCKADDR_IN serverInfo;


        serverInfo.sin_family = AF_INET;

        serverInfo.sin_addr.s_addr = INADDR_ANY;      // Так как этот сокет прослушивает подключения,

                                                     // любой локальный адрес будет делать

        serverInfo.sin_port = htons(8888);            // Преобразование целого 8888 на порядок network-byte

                                                     // и вставляете в поле порта



        // Привяжите сокет к нашему адресу локального сервера

        nret = bind(listeningSocket, (LPSOCKADDR)&serverInfo, sizeof(struct sockaddr));


        if (nret == SOCKET_ERROR)

        {

               nret = WSAGetLastError();

               ReportError(nret, "bind()");


               WSACleanup();

               return NETWORK_ERROR;

        }




        // Make the socket listen

        nret = listen(listeningSocket, 10);           // Up to 10 connections may wait at any

                                                     // one time to be accept()'ed


        if (nret == SOCKET_ERROR)

        {

               nret = WSAGetLastError();

               ReportError(nret, "listen()");


               WSACleanup();

               return NETWORK_ERROR;

        }




        // ждите клиента

        SOCKET theClient;



        theClient = accept(listeningSocket,

                          NULL,                       // При желании, адрес SOCKADDR_IN структурируется

                          NULL);                      // При желании, адрес переменной содержит

                                                     // sizeof ( struct SOCKADDR_IN )


        if (theClient == INVALID_SOCKET)

        {


               nret = WSAGetLastError();

               ReportError(nret, "accept()");


               WSACleanup();

               return NETWORK_ERROR;

        }



        // Отправляйтe и получайте от клиента, и, наконец,

        closesocket(theClient);

        closesocket(listeningSocket);



        // закрывайте Winsock

        WSACleanup();

        return NETWORK_OK;

}




void ReportError(int errorCode, const char *whichFunc)

{

   char errorMsg[92];                                // Декларируйте буфер для удержания

                                                     // сообщение об ошибке генерируется


   ZeroMemory(errorMsg, 92);                         // автоматически завершится и обнулится строка


   // Следующая строка копирует фразу, whichFunc строку, и интегрирует код ошибки в буфер

   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);


   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);

}

 

Единственное что вы можете сразу же заметите в коде это количество усилий положеных на проверку ошибок. Всякий раз, когда возникает ошибка, код получает соответствующий код ошибки с помощью WSAGetLastError () и сохраняет результат в сети. Код ошибки будет отправлен вместе со строкой, указывающей имя отказавшей функции в пользовательской функции с именем ReportError (). Там, сообщение об ошибке строится и показывается пользователю с помощью вызова MessageBox (), который является частью стандартной WinAPI. Например, провалился listen () бы с кодом ошибки 10093 (определяемой как WSANOTINITIALISED), законченная строка ошибки была бы "Call to listen () возвращенная ошибка 10093!". Вы, благоразумный разработчик, взглянете на код и обнаружите, что произошла ошибка, потому что успешный вызов WSAStartup () еще не был сделан.

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

 

Также определены NETWORK_ERROR и NETWORK_OK. Это может быть полезно при проверке возвращаемого значения ваших собственных сетевых функций. Если ваши функции возвращают одно из этих значений, то функция вызова может выполнить простой тест равенства, чтобы выявить какие-либо ошибки: if (myNetworkingFunction() == NETWORK_ERROR) {...}. Вызывающая функция может затем получить специальный код с WSAGetLastError () и обработать ошибку, соответственно. В конечном счете, внедрение хорошей схемы обработки ошибок теперь сохранит вам много дней или недель времени разработки поскольку Вы будете мгновенно знать, почему ваша программа поламалась.

В дополнение к возврату нового соединения с клиентом, accept () позволяет серверу извлекать информацию о клиенте, а не с помощью методов, требующих дополнительных вызовов функций или времени (которая может стать проблемой в игровых серверах, где скорость принимающего цикла особенно критична). Чтобы воспользоваться этой функциональностью, пройдите в адрес в sockaddr_in структуре литых к SOCKADDR указателю, т.е. (LPSOCKADDR)&aSockaddrInStructure. Кроме того, объявите целочисленную переменную, установите значение int к sizeof в SOCKADDR структуре, и передайте адрес целого числа в качестве третьего параметра. Если информация об адресе должна быть возвращена после вызова функции, параметр длины должен присутствовать.

jdarnold предупреждает нас не верить документации MSDN по этому параметру третьему: "Документы MSDN предполагают, что вы не должны пройти в addrlen, что это всего лишь дополнительный выходной параметр, но они не правы. Inbound говорит, сколько байт в буфере SOCKADDR, и исходящий [Winsock] заполняет сколько [Winsock] используется. Если вы проходите к нулю, как len, [Winsock] не коснется буфера ".

 

Это не так многозависит от сервера, так как он ждет только одного пользователя для подключения, а затем сразу отключается, но это самый основной дизайн. Чтоб просто очистить вещи, вызов WSAStartup () включает в себя СЛОВО , которое указывает, какую версию вы хотите загрузить (в данном случае это 1.1) и адрес структуры WSADATA. Далее, мы рассмотрим, как подключиться к другим компьютерам.

Создание собственных подключений

Создание сокета для подключения к кому-то еще использует те же самые функции, за исключением части структуры HOSTENT:

  • HOSTENT: Структура используется, чтобы сообщить сокету, к которому компьютер и порт подключаются. Эти структуры обычно появляются в качестве переменных HOSTENT IP, которые являются лишь указателями на HOSTENT структуры. Поскольку Вы кодируете для Windows, вы в приницпе поймете, что любой тип данных предшествовший LP обозначает, что тип на самом деле указывает на тип «базовый» (например, LPCSTR является указателем на строку C, также известную как CHAR *).

Итак, давайте приступим прямо к коду:

#include <windows.h>

#include <winsock.h>

#include <stdio.h>


#define NETWORK_ERROR -1

#define NETWORK_OK     0


void ReportError(int, const char *);




int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)

{

        WORD sockVersion;

        WSADATA wsaData;

        int nret;



        sockVersion = MAKEWORD(1, 1);



        // Инициализация Winsock, как и раньше

        WSAStartup(sockVersion, &wsaData);



        // Хранит информацию о сервере

        LPHOSTENT hostEntry;

        hostEntry = gethostbyname("www.yahoo.com");   // Указание сервера по его имени;

                                                     // другой вариант: gethostbyaddr()

        if (!hostEntry)

        {
               nret = WSAGetLastError();

               ReportError(nret, "gethostbyname()"); // Сообщает об ошибке, как и раньше


               WSACleanup();

               return NETWORK_ERROR;

        }



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

        SOCKET theSocket;


        theSocket = socket(AF_INET,                   // через TCP/IP
                          SOCK_STREAM,                // Это поток, ориентированный на сокет

                          IPPROTO_TCP);               // используйте TCP вместо UDP


        if (theSocket == INVALID_SOCKET)

        {
               nret = WSAGetLastError();

               ReportError(nret, "socket()");


               WSACleanup();

               return NETWORK_ERROR;

        }

 

 

        // заполните SOCKADDR_IN структуру адресной информацией

        SOCKADDR_IN serverInfo;


        serverInfo.sin_family = AF_INET;


        // На данный момент, мы успешно извлекли жизненно важную информацию о сервере,

        // в том числе его имя хоста, псевдонимы и IP-адреса. Подождите; как мог один
        // Компьютер иметь несколько адресов, и что именно следующая строка делает?

        // Читайте объяснение ниже.


        serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);



        serverInfo.sin_port = htons(80);              // Перейдите в сетевой порядок байт и

                                                                             // введите в поле порта

 

        // подсоединитесь к серверу

        nret = connect(theSocket,

                     (LPSOCKADDR)&serverInfo,

                      sizeof(struct sockaddr));


        if (nret == SOCKET_ERROR)

        {

               nret = WSAGetLastError();

               ReportError(nret, "connect()");



               WSACleanup();

               return NETWORK_ERROR;

        }


        // успешно подсоединено!


        // Отправьте / получите, а затем очистите:

        closesocket(theSocket);

        WSACleanup();
}


void ReportError(int errorCode, const char *whichFunc)

{

   char errorMsg[92];                                // Декларируйте буфер для удержания

                                                     // сообщение об ошибке генерируется


   ZeroMemory(errorMsg, 92);                         // Автоматически NULL-завершает строку

 

 

   // Следующая строка копирует фразу, которая Func строку, и интегрирует код ошибки в буфер

   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);



   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);

}

 

Наиболее сложная линия в перечислении следующая:

serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);

 

поскольку она выполняет несколько операций --- одна из них относительно скрыта --- сразу. Давайте разбирать его шаг за шагом:

Член структуры HOSTENT h_addr_list обычно определяется как операнд **h_addr_list, который является массивом строк, или char *'s. gethostbyname() идентифицировавший и скопировавший все известные адреса сервера в этот список. Тем не менее, сама концепция нескольких адресов принципиально имеет ли смысл? На самом деле, имеет. Ваш компьютер, в самом деле, имеет множество общих адресов сетей. Ваш адрес в Интернете может быть 205.182.67.96, ваш адрес локальной сети может быть 10.0.0.2, и все компьютеры, на которых установлена ОС Windows, естественно, "замыкают" адрес 127.0.0.1, используемый компьютером для обозначения себя в локальной сети. Эта же концепция применима в сфере интернет-адресов или IP-адресов, поэтому список необходим, а не место для хранения одного адреса. Обратите внимание, что предпочтительный адрес, то есть, самый доступный адрес, всегда копируется в первый элемент списка, а затем со вторым предпочтительным или другим адресам.

Что делает *hostEntry->h_addr_list? Вы могли бы догадаться, что оператор разыменования (*) используется для доступа к единому адресу в списке. Однако, не в состоянии обеспечить определенный индекс, оператор разыменовывания автоматически показывает первый, предпочтительный адрес. Этот особенный раздел эквивалентен *hostEntry->h_addr_list[0], который гарантированно существует, так как сервер должен иметь по крайней мере один адрес.

Следующее, операнда * возвращенная разыменованием оператора брошена в in_addr * или LPIN_ADDR. Наконец, другая операция отсрочки выполняется для возврата in_addr структуры, на которую ссылается указатель, который может провести только один адрес. В результате структура in_addr затем присваивается serverInfo.in_addr. Последующее подключение () принимает один адрес в качестве параметра при формировании соединения с сервером.

Если IP-адрес сервера известен, действительный HOSTENT может быть получен путем использования gethostbyaddr () (в отличие от gethostbyname (), используемого в предыдущем листинге):

LPHOSTENT hostEntry;

in_addr iaHost;


iaHost.s_addr = inet_addr("204.52.135.52");


hostEntry = gethostbyaddr((const char *)&iaHost, sizeof(struct in_addr), AF_INET);


if (!hostEntry)

{

        // Обращайтесь соответственно

}

 

В этом случае, inet_addr () используется для копирования строки, обозначающей IP-адрес непосредственно в in_addr структуре. Впоследствии, адрес структуры попадет в постоянную операнду * в соответствии с требованиями gethostbyaddr (). Оба метода именуются разрешением адресу сервера, так как Winsock возвращает полные записи адреса из частичной информации.

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

Отправка и получение

Отправка осуществляется, достаточно удобно, по функции send ():

int send(

  SOCKET s,

  const char * FAR buf,

  int len,

  int flags

);

 

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

char buffer[256];              // Декларирование буфера в стеке

char *buffer = new char[256];  // или в куче


ZeroMemory(buffer, 256);

strcpy(buffer, "Pretend this is important data.");


nret = send(theSocket,

            buffer,

            strlen(buffer),    // Заметим, что это указывает длину строки; не

                               // размер всего буфера

            0);                // В большинстве случаев равна нулю, но смотрите MSDN для получения дополнительных параметров


delete [] buffer;              // Тогда и только тогда, когда была использована декларация динамической памяти


if (nret == SOCKET_ERROR)

{

        // Get a specific code

        // Handle accordingly

        return NETWORK_ERROR;

} else {

        // nret содержит кол-во отправленных байт

}


Прием это тот же процесс, наоборот:



char buffer[256];              // На стеке

char *buffer = new char[256];  // или в динамической памяти


nret = recv(theSocket,

            buffer,

            256,               // Полный размер буфера

            0);


delete [] buffer;              // Манипулирование буфером, а затем удалите, если и только если

                               // буфер выделяется на динамической памяти


if (nret == SOCKET_ERROR)

{

        // Получите специальный код

        // Обращайтесь соответственно

        return NETWORK_ERROR;

} else {

        // nret contains the number of bytes received

}

 

Что интересно отметить, что есть кнопка на панели инструментов в Microsoft Outlook с надписью "Отправить / Получить (Recv)". Затем ли "Receive" сокращено до "Recv" просто чтобы убедиться, что кнопка выглядит правильно, или это привычка программиста от ввода RECV () столько раз? Сформируйте свои собственные теории заговора (опять же, хорошо для светской беседы на вечеринках).

Вот где я столкнулся с небольшой проблемой, когда писал свои собственные программы Winsock. Только с помощью recv() чудесно, когда вы точно знаете, сколько данных вы будете получать (например, в игре, где первый байт может быть командой и следующий байт будет параметром, и т.д.), но когда вы не знаете, что вы будете делать? Если данные, которые вы получаете, завершаются символом новой строки (общая проблема с Java клиентами, говорящими с C-серверами), вы можете написать функцию readLine(), чтобы захватить все до этого символа. Вот то, что я использовал:

char * readLine()

{

   vector theVector;

   char buffer;

   int bytesReceived;


   while (true)

   {

      bytesReceived = recv(theSocket, &buffer, 1, 0);

      if (bytesReceived <= 0)

         return NULL;


      if (buffer == '\n')

      {

         char *pChar = new char[theVector.size() + 1];

         memset(pChar, 0, theVector.size() + 1);


         for (int f = 0; f < theVector.size(); f++)

            pChar[f] = theVector[f];


         return pChar;

      } else {

         theVector.push_back(buffer);

      }

   }

}

 

Вектор используют вместо массива, поскольку его место для хранения может быть увеличено автоматически в зависимости от длины линии. Если recv() возвращает ошибку (обозначается полученными байтами меньше нуля), значение NULL возвращается. Поскольку это возможность, вызов функций должен убедиться, что строка, вернувшаяся из readLine() действует до использования. Внутри цикла, один символ принимается от сети, и, если не символ новой строки, добавляется к вектору. Если это символ новой строки, содержимое вектора копируется в строку C и возвращается. Строка объявляется одним символом больше, чем вектор и memset()-ируется и стремится к нулю, так что возвратная линия будет автоматически аннулирована. Окончание строки с значением NULL предотвращает необычные ошибки и, как правило, является хорошей практикой программирования.

Нор представляет эту ловко улучшенную версию с поддержкой для возвратов (backspaces) и возможностью легко изменять символ новой строки:

// Код первоначально написан Нором. Модифицированный немного для

// поддержки MessageBox() API, сделать более читабельной логику,

// выровнять расстояние, и добавлять комментарии. Опубликовано с разрешения.


#define backKey '\b'                                 // Чтобы отключить backspace-ы, #define backKey NULL

#define newLine '\n'

#define endStr  '\0'


char *readLine(SOCKET s)

{

        vector theVector;

        char buffer;

        char *pChar;

        int bytesReceived;


        while (true)

        {

               bytesReceived = recv(s, &buffer, 1, 0);


               if (bytesReceived <= 0)

               {

                       MessageBox(NULL, "recv() returned nothing.", "socketIndication", MB_OK);

                       return NULL;

               }



               switch (buffer)

               {

                       case backKey:                  // регулировка backspace

                               if (theVector.size() > 0)

                                      theVector.pop_back();

                               break;

                       case endStr:                   // Если конец строкового символьного операнда достигнут,

                       case newLine:                  // или если конец линейного символьного операнда достигнут,

                               pChar = new char[theVector.size() + 1];

                               memset(pChar, 0, theVector.size() + 1);


                               for (int f = 0; f < theVector.size(); f++)

                                      pChar[f] = theVector[f];

                               return pChar;

                               break;

                       default:                       // любой обычный операнд

                               theVector.push_back(buffer);

                               break;

               }

        }

}

 

Неблокируемые и асинхронные сокеты

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

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

  • int WSAAsyncSelect ( SOCKET s, HWND hwnd, используя int wMsg, длинный lEvent )
    Эта функция используется, чтобы идентифицировать сокет как асинхронный и связать сообщение с ним. Это сокет, с которым вы работаете. HWND является дескриптор окна, которое будет получать сообщение, когда сокет генерирует событие. wMsg это сообщение, которое вы хотите отправить вашей оконной процедуре (пример является сокет SOCKET_GOTMSG сообщение сверху). Параметр IEvent принимает один или более флагов, которые указывают на сокет и при каких обстоятельствах отправить сообщение. Некоторые из этих флагов:
    • FD_READ: Сокет готов к приему данных
    • FD_WRITE: Сокет готов к отправке данных
    • FD_ACCEPT: Используется в серверах, это сообщение означает, что  пользователь подключен
    • FD_CONNECT: Используется в клиентских приложениях, это сообщение говорит вам что сокет был подключен
    • FD_CLOSE: Сокет только что был закрыт
  • WSAGETSELECTERROR ( LPARAM lparam )
    Определяет, если сокет вернул ошибку. Технически, это не функция, а макрос (вы действительно можете генерировать светскую беседу на вечеринках с этого малого факта).
  • WSAGETSELECTEVENT ( LPARAM lparam )
    Еще один полезный макрос, определенный в winsock2.h является WSAGETSELECTEVENT (), который используется, чтобы увидеть именно то, что сокет сделал.

Итак, давайте настроим асинхронный сокет:

// Мы начнем с создания флага, который будет использовать ОС Windows чтоб обращаться к нам, когда что-то случается

#define THERE_WAS_A_SOCKET_EVENT      WM_USER + 100  // WM_USER это база для пользовательских сообщений

 




// Где-то в нашем коде инициализации после CreateWindow (), вызываем WSAAsyncSelect ()

WSAAsyncSelect ( theSocket, hwnd, THERE_WAS_A_SOCKET_EVENT, FD_READ | FD_WRITE | FD_CONNECT | ... );


// Это означает: Windows, пожалуйста, свяжитесь со мной, используя THERE_WAS_A_SOCKET_EVENT флаг который я

// определил ранее, в момент когда есть данные для чтения (FD_READ), или когда я свободен для передачи данных

// (FD_WRITE), или когда я успешно подключен к кому-то еще (FD_CONNECT), или когда...итд.

 

 

 

// В нашей процедуре окна (функция которая обрабатывает все сообщения, которые посылает Windows для вашего приложения)

LRESULT WINAPI TheWindowProcedure ( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )

{

 

        switch ( msg )

        {


               case THERE_WAS_A_SOCKET_EVENT:

                       if ( WSAGETSELECTERROR ( lParam ) )

                       {       // если обнаружилась ошибка,

                               закройте сокет ( theSocket );

                               WSACleanup ();                        // Закройте Winsock

                               return NETWORK_ERROR;

                       }

                       switch ( WSAGETSELECTEVENT ( lParam ) )

                       {       // что конкретно случилось?

                               case FD_READ:

                                      // примите разрыв

                                      данных;

                               case FD_WRITE:

                                      // пропишите

                                      разрыв данных;

                               case FD_CONNECT:

                                      // просто подсоединитесь к

                                      Серверному разрыву;

                               case ...                              // Те же установки для других флагов

                                      и разрывов;

                       }

                       break;



               // другие заявления случаев с логикой, которая обрабатывает другие сообщения Windows

        }

}

 

Обратите внимание, что вы не можете определить одно сообщение для каждого события, как SOCKET _GOT MSG для каждого события FD_READ, а затем SOCKET_CONNECT для FD_CONNECT. Это потому, что неоднократные призывы к WSAAsyncSelect () для установки каждого флага отменяет действие последнего вызова WSAAsyncSelect ().

Еще учебники и ссылки

Я написал этот учебник в декабре 2000 года, и семь лет или около того с тех пор видели постоянный поток посетителей и улучшений. Я надеюсь, что вам понравилось читать насколько мне понравилось писать: спасибо за использование Учебника Джонни о Winsock. Выше это краткий обзор возможностей, которые вы можете достичь через Winsock, и другие сделали это гораздо лучше, чем мне приходило в голову о специфике этого вопроса:

(Наведите курсор на обложку книги для получения дополнительной информации.)

 

Описание: http://www.assoc-amazon.com/e/ir?t=rose-recommends-20&l=as2&o=1&a=0201615894

Описание: TCP/IP Sockets in C: Practical Guide for ProgrammersОписание: http://www.assoc-amazon.com/e/ir?t=rose-recommends-20&l=as2&o=1&a=1558608265

Описание: Programming Windows, Fifth EditionОписание: http://www.assoc-amazon.com/e/ir?t=rose-recommends-20&l=as2&o=1&a=157231995X

Описание: 1 year subscription to Maxim magazineОписание: http://www.assoc-amazon.com/e/ir?t=rose-recommends-20&l=as2&o=1&a=B00005NIPP

Описание: http://johnnie.jerrata.com/images/effective_tcpip_programming_reflected.jpg

Описание: http://johnnie.jerrata.com/images/tcpip_sockets_in_c_reflected.jpg

Описание: http://johnnie.jerrata.com/images/programming_windows_reflected.jpg

Описание: http://johnnie.jerrata.com/images/maxim_1_yr_reflected.jpg

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

Как я могу улучшить?

Есть что-то, что нуждается в уточнении? Провалился ли учебник в охвате темы Winsock, связанной с тем, что вы хотели узнать? Был учебник полезен вашим потребностям как разработчику программного обеспечения? Это забавно? энергично написано? чрезмерно упрощенно? или просто не так?

- Смотри на http://johnnie.jerrata.com/winsocktutorial/#sthash.bKpVO7OC.dpuf