Підручник Джонні по Winsock

Якщо ви навмисно прибули на мій урок по Winsock, ви, швидше за все, знайшли уявлення про ваші власні програми, що взаємодіють через Інтернет, як захоплюючу перспективу, саме як і я. Або, може бути, хтось знайшов перспективу однаково цікавою, і на вас було покладено місію залучення цього бачення до реальності. У кожному разі, служба мережі Winsock і цей підручник допоможе вам у досягненні цілей вашого комерційного підприємства просто шляхом дослідження сфери мережевого програмування для особистого використання, або щось посередині.

Ось що ми будемо охоплювати:

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

Хоча ви можете бути готові досягти тієї вражаючої точки, в якій ваша програма успішно робить свою перше з'єднання, будьте в курсі концепцій коду. Намагайтеся уникати маніпулювання даними кода даремно, щоб задовольнити ваші нагальні потреби, а замість цього, визначайте вимоги вашого застосування і тільки потім здійснюйте те, що здається найкращим рішенням. Вистачить моєї дзен-поради з розробки софта поки що, давайте займемося мережевим програмуванням ...

Не соромтеся скачати весь список уроків за кодом. Пам'ятайте, що будь-який код, представлений в цьому підручнику, повинен бути пов'язаний з бібліотекою 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;


        }






        // Надсилайте та отримуйте від клієнта, і, нарешті,


        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)


{


        // Отримайте спеціальний код


        // звертайтеся відповідно


        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 містить к-ть відправлених байтів


}

 

Що цікаво відзначити, що є кнопка на панелі інструментів у 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 ) )


                       {       // якщо виявилася помилка, закрийте сокет (the Socket);

                               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

     

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

Як я можу поліпшити?

Є щось, що потребує уточнення? Провалився Чи підручник в охопленні теми Winsock, пов'язаної з тим, що ви хотіли дізнатися? Був підручник корисний вашим потребам як розробнику програмного забезпечення? Це забавно? енергійно написано? надмірно спрощено? або просто не так?

- Дивись на http://johnnie.jerrata.com/winsocktutorial/#sthash.bKpVO7OC.dpuf