Johnnie Winsock Výučba
Ak ste úmyselne prišli na môj návod Winsock, s najväčšou pravdepodobnosťou ste našli myšlienku, že vaše vlastné aplikácie komunikujú cez Internet ako fascinujúcu vyhliadku ako mám. Alebo snáď niekto iný našiel túto perspektívu rovnako zaujímavý a ste boli poverení uvedením tejto vízie do reality. V oboch prípadoch vám sieťová služba Winsock a tento tutoriál pomôže dosiahnuť vaše ciele komerčného podnikania, jednoducho preskúmať oblasť programovania siete pre osobné použitie alebo niečo medzi tým.
Tu je to, čo budeme pokrývať:
- Vytvorenie počúvacej zásuvky: Vzhľadom na malú armádu sieťových funkcií, môžeme vytvoriť program, ktorý trpezlivo čaká na prichádzajúce pripojenia? (Áno, môžme.)
- Vytvorenie vlastných pripojení: Vzhľadom na niekoľko ďalších funkcií, môžeme vytvoriť program, ktorý úspešne spája server počúvania? (Áno, môžme.)
- Odosielanie a prijímanie: Akonáhle dosiahneme aktívne spojenie, ako to používame na výmenu dát medzi dvoma programami? (Hádali ste to—send() a recv().)
- Neblokovacie a asynchrónne zásuvky: Ako môžeme zvýšiť účinnosť nášho kódu zavedením inej schémy vytvárania sietí? (My sme sa trochu ošípali s oknom.)
- Ďalšie konzultácie a odkazy: Aké sú zdroje nad týmto tutoriálom? Zdôrazňujem 3, ktoré by vás mali na chvíľu zaneprázdňovať (samozrejme po tom, ako ste si strávili tutoriál :-)).
- Komentáre a kritiky: Tu je vaša príležitosť hodnotiť tutoriál, klásť otázky alebo uverejniť komentáre.
Hoci môžete byť túžbou dosiahnuť ten úchvatný bod, v ktorom vaša aplikácia úspešne robí svoje prvé spojenie, uvedomte si koncepty, ktoré sa skrývajú za kódom. Snažte sa vyhnúť sa jednoduchej manipulácii s daným kódom tak, aby vyhovovala vašim okamžitým potrebám, a namiesto toho určiť požiadavky vašej aplikácie a až potom vykonať to, čo sa zdá byť najlepším riešením. To je dosť mojich Zen poradenstva pre vývoj softvéru zatiaľ; Urobme nejaké programovanie v sieti…
Neváhajte a stiahnite si celý výpis kódu tutoriálu. Nezabudnite, že akýkoľvek kód uvedený v tomto návode by mal byť prepojený s knižnicou Winsock, zvyčajne wsock32.lib alebo podobným názvom. Tiež, ak používate kód presne tak, ako je to uvedené v tutoriáli vo vašom vlastnom IDE (Dev-C++, Microsoft VC++, C++ Builder atď.), vyberte vybudovať projekt systému Windows s WinMain(), aby ste sa vyhli chybám.
Vytvorenie zdieľacej zásuvky
Aplikácie obsluhujúce externé počítače sa nazývajú servery. Serverové aplikácie počúvajú klientov inicializáciou jednej alebo viacerých počúvacích pätiek. Keď sa klient pripojí k jednej z týchto počúvacích pätiek, server dostane upozornenie od spoločnosti Winsock, prijíma pripojenie a začne odosielať a zachycovať správy od nového klienta. Snáď najjednoduchší spôsob, ktorým servery spracovávajú viacerých klientov, je vytvoriť novú niť pre každé pripojenie klienta. Tento serverový model najčastejšie využíva blokovacie zásuvky, ktoré dočasne pozastavujú čakanie na prichádzajúce dáta, nové pripojenie a iné sieťové udalosti. Po prvé, identifikujeme niektoré štruktúry, ktoré budeme potrebovať na inicializáciu blokovacieho soketu:
WSADATA: Táto štruktúra sa používa na vyhľadávanie operačného systému pre verziu Winsock, ktorú vyžaduje náš kód. Aplikácia zavolá WSAStartup() inicializovať správne Winsock DLL.
SOCKET: Objekt (v skutočnosti je definovaný ako u_int, nepodpísané celé číslo, v winsock.h— dobré vedieť pre smalltalk na večierkoch), ktoré používajú aplikácie na uloženie zväzku soketu.
SOCKADDR_IN: Aplikácia využíva túto štruktúru na určenie toho, ako by mala pracovať soket. SOCKADDR_IN obsahuje polia pre adresu IP a číslo portu:
struct sockaddr_in { short sin_family; // Typ protokolu u_short sin_port; // Číslo portu zásuvky struct in_addr sin_addr; // IP adresa char sin_zero[8]; // Nepoužívaný };
Prvé pole je typ protokolu, ktorý je zvyčajne AF_INET (TCP/IP). Keďže zásuvka pre počúvanie sa netýka sieťovej adresy počítača, na ktorom je zariadenie umiestnené, Winsock automaticky priradí IP adresu a číslo portu počúvaným zásuvkám po vytvorení.
Budeme budovať náš prvý server na počúvanie s vyššie uvedenými štruktúrami a malou armádou sieťových funkcií:
#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); // We'd like Winsock version 1.1 // Začíname inicializáciou Winsock WSAStartup(sockVersion, &wsaData); // Ďalej vytvorte zásuvku počúvania SOCKET listeningSocket; listeningSocket = socket(AF_INET, // Prejdite cez protokol TCP / IP SOCK_STREAM, // Toto je zásuvka orientovaná na prúd IPPROTO_TCP); // Použite skôr TCP než UDP if (listeningSocket == INVALID_SOCKET) { nret = WSAGetLastError(); // Získajte podrobnejšiu chybu ReportError(nret, "socket()"); // Nahláste chybu pomocou vlastnej funkcie WSACleanup(); // Vypnutie Winsock return NETWORK_ERROR; // Vráť hodnotu chyby } // Použite štruktúru SOCKADDR_IN na vyplnenie informácií o adrese SOCKADDR_IN serverInfo; serverInfo.sin_family = AF_INET; serverInfo.sin_addr.s_addr = INADDR_ANY; // Keďže táto zásuvka počúva pripojenia, // akákoľvek miestna adresa bude robiť serverInfo.sin_port = htons(8888); // Preveďte celé číslo 8888 na poradie sieťových bajtov // a vložte do poľa portu // Pripojte zásuvku na našu miestnu adresu servera nret = bind(listeningSocket, (LPSOCKADDR)&serverInfo, sizeof(struct sockaddr)); if (nret == SOCKET_ERROR) { nret = WSAGetLastError(); ReportError(nret, "bind()"); WSACleanup(); return NETWORK_ERROR; } // Make socket počúvať nret = listen(listeningSocket, 10); // Až 10 pripojení môže čakať na akékoľvek // Jeden čas na prijatie()'ed if (nret == SOCKET_ERROR) { nret = WSAGetLastError(); ReportError(nret, "listen()"); WSACleanup(); return NETWORK_ERROR; } // Počkajte na klienta SOCKET theClient; theClient = accept(listeningSocket, NULL, // Prípadne adresa štruktúry SOCKADDR_IN NULL); // Prípadne adresa premennej obsahujúcej // Sizeof (struct SOCKADDR_IN) if (theClient == INVALID_SOCKET) { nret = WSAGetLastError(); ReportError(nret, "accept()"); WSACleanup(); return NETWORK_ERROR; } // Odošlite a prijímajte od klienta a nakoniec, closesocket(theClient); closesocket(listeningSocket); // Vypnutie Winsock WSACleanup(); return NETWORK_OK; } void ReportError(int errorCode, const char *whichFunc) { char errorMsg[92]; // Vyhlásenie vyrovnávacej pamäte // Generované chybové hlásenie ZeroMemory(errorMsg, 92); // Automaticky ukončiť reťazec NULL // Nasledujúci riadok kopíruje frázu, ktorýFunc reťazec, a integer errorCode do vyrovnávacej pamäte sprintf(errorMsg, "Volanie na% s vráti chybu% d!", (char *)whichFunc, errorCode); MessageBox(NULL, errorMsg, "socketIndication", MB_OK); }
Jedna vec, ktorú môžete ihneď upozorniť na kód, je množstvo úsilia vložené do kontroly chýb. Kedykoľvek nastane chyba, kód získa špecifický kód chyby s WSAGetLastError() a uloží výsledok do nret. Kód chyby sa potom odošle spolu s reťazcom označujúcim názov funkcie zlyhania vlastnej funkcie s názvom ReportError(). Tam je chybové hlásenie konštruované a zobrazené používateľovi s volaním MessageBox(), ktorý je súčasťou štandardného WinAPI. Napríklad, ak sa listen() nepodarilo s chybovým kódom 10093 (definovaným ako WSANOTINITIALISED), hotový chybový reťazec by bol “Volať na listen() vrátil chybu 10093!”. Vy, obozretný vývojár, by ste potom vyhľadali kód a zistili, že k chybe došlo, pretože ešte nebol úspešný hovor k WSAStartup().
Aleksandar Pavlov rozšíril tento ReportError() tak, aby zahŕňal popisy asi tuctu spoločných socketových chýb. Pomocou inovovanej verzie už nebudete musieť vyhľadávať, čo znamená kód, a váš program sa stáva oveľa užívateľsky príjemnejším a s veľmi malým úsilím z vašej strany.
Zahrnuté sú aj definície pre siete NETWORK_ERROR a NETWORK_OK. Tieto môžu byť užitočné pri kontrole návratovej hodnoty vašich vlastných sieťových funkcií. Ak vaše funkcie vrátia jednu z týchto hodnôt, funkcia volajúceho by mohla vykonať jednoduchý test rovnosti, aby odhalil akékoľvek chyby: if (myNetworkingFunction() == NETWORK_ERROR) {…}. Funkcia volajúceho by potom mohla získať špecifický kód s WSAGetLastError() a zodpovedajúcim spôsobom spracovať chybu. Nakoniec zavedenie dobrej schémy na spracovanie chýb vám teraz ušetrí veľa dní alebo týždňov vývojového času, pretože okamžite budete vedieť, prečo váš program zlyhal.
Okrem prijímania nového klientskyho pripojenia accept() umožňuje serveru získavať informácie o klientovi skôr než pomocou metód, ktoré vyžadujú ďalšie volania funkcie alebo čas (čo sa môže stať problémom na herných serveroch, kde je rýchlosť prijateľnej slučky hlavne kritická). Ak chcete využiť túto funkciu, zadajte adresu sockaddr_in struct cast na ukazovateľ sockaddr, t. J. (LPSOCKADDR) a aSockaddrInStructure. Tiež deklarovať celočíselnú premennú, nastaviť hodnotu int na veľkosť štruktúry sockaddr a prejsť adresu celého čísla ako tretí parameter. Ak sa majú informácie o adrese vrátiť po volaní funkcie, musí byť prítomný parameter dĺžky.
Jdarnold nás varuje, aby neveril dokumentáciu MSDN týkajúcu sa tohto tretieho parametra: “Dokumenty MSDN naznačujú, že nemusíte prechádzať v addrlen, že ide len o voliteľný výstupný parameter, ale oni sa mýlia. V vyrovnávacej pamäti sockaddr a odchádzajúca [Winsock] vyplní, koľko [Winsock] používa. Ak prejdete nula ako len, [Winsock] sa nedotkne vyrovnávacej pamäte.”
Nie je to veľa servera, pretože čaká na pripojenie jediného používateľa a okamžite sa odpojí, ale to je najzákladnejší dizajn. Jednoducho vyriešte veci, volanie WSAStartup() obsahuje slovo WORD, ktoré špecifikuje verziu, ktorú chcete načítať (v tomto prípade je to 1,1) a adresu štruktúry WSADATA. Ďalej sa budeme zaoberať, ako sa spojiť s inými počítačmi.
Vytváranie Vlastných Pripojení
Vytvorenie soketu na pripojenie k niekomu inému používa väčšinu rovnakých funkcií, s výnimkou štruktúry HOSTENT:
HOSTENT: Štruktúra slúžiaca na rozpoznanie zásuvky, do ktorej sa má pripojiť počítač a port. Tieto štruktúry sa obyčajne vyskytujú ako premenné LPHOSTENT, ktoré sú iba ukazovateľmi štruktúr HOSTENT. Pri kódovaní systému Windows zvyčajne zistíte, že akýkoľvek typ údajov, ktorému predchádza LP, označuje, že typ je v skutočnosti ukazovateľom typu “base” (napríklad LPCSTR je ukazovateľ na reťazec C, tiež známy ako char * ).
Takže sa dostaneme priamo do kódu:
#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); // Inicializujte Winsock ako predtým WSAStartup(sockVersion, &wsaData); // Ukladať informácie o serveri LPHOSTENT hostEntry; hostEntry = gethostbyname("www.yahoo.com"); // Zadanie servera podľa jeho mena; // inou možnosťou: gethostbyaddr() if (!hostEntry) { nret = WSAGetLastError(); ReportError(nret, "gethostbyname()"); // Nahláste chybu ako predtým WSACleanup(); return NETWORK_ERROR; } // Vytvorte zásuvku SOCKET theSocket; theSocket = socket(AF_INET, // Prejdite cez protokol TCP/IP SOCK_STREAM, // Toto je zásuvka orientovaná na prúd IPPROTO_TCP); // Použite skôr TCP než UDP if (theSocket == INVALID_SOCKET) { nret = WSAGetLastError(); ReportError(nret, "socket()"); WSACleanup(); return NETWORK_ERROR; } // Vyplňte štruktúru SOCKADDR_IN s informáciami o adrese SOCKADDR_IN serverInfo; serverInfo.sin_family = AF_INET; // V tomto momente sme úspešne získali dôležité informácie o serveri, // Vrátane názvu hostiteľa, aliasov a adries IP. Počkať; Ako by mohol byť jediný // Počítač má viac adries a presne to, čo robí nasledujúci riadok? // Pozrite si vysvetlenie nižšie. serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list); serverInfo.sin_port = htons(80); // Zmeňte poradie sieťových bajtov a // Vložte do poľa port // Pripojte sa k serveru nret = connect(theSocket, (LPSOCKADDR)&serverInfo, sizeof(struct sockaddr)); if (nret == SOCKET_ERROR) { nret = WSAGetLastError(); ReportError(nret, "connect()"); WSACleanup(); return NETWORK_ERROR; } // Úspešne pripojený! // Odoslať/prijať, potom vyčistiť: closesocket(theSocket); WSACleanup(); } void ReportError(int errorCode, const char *whichFunc) { char errorMsg[92]; // Vyhlásenie vyrovnávacej pamäte // Generované chybové hlásenie ZeroMemory(errorMsg, 92); // Automaticky ukončiť reťazec NULL // Nasledujúci riadok kopíruje frázu, whichFunc reťazec, a integer errorCode do vyrovnávacej pamäte sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode); MessageBox(NULL, errorMsg, "socketIndication", MB_OK); }
Najkomplikovanejšia položka v zozname je nasledujúca:
serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);
Pretože vykonáva niekoľko operácií — jeden z nich je pomerne skrytý — naraz. Rozoberme to krok za krokom:
Člen h_addr_list štruktúry HOSTENT je v podstate definovaný ako char **h_addr_list, čo je pole reťazcov, alebo char *’s. gethostbyname() identifikoval a skopíroval všetky známe adresy servera do tohto zoznamu. Koncept viacnásobných adries má však zásadný význam? Vlastne to robí. Váš počítač má v skutočnosti rad všeobecných sieťových adries. Vaša internetová adresa môže byť 205.182.67.96, vaša adresa LAN môže byť 10.0.0.2 a všetky počítače, na ktorých je Windows nainštalovaný prirodzene, majú “loopback” adresu 127.0.0.1, ktorú používa počítač na to, aby sa sám o sebe dozvedel v lokálnej sieti , Rovnaký koncept platí aj v oblasti relácií internetových adries alebo adries IP, a preto je potrebný skôr zoznam než úložný priestor pre jednu adresu. Všimnite si, že preferovaná adresa, teda najdostupnejšia adresa, sa vždy skopíruje do prvého prvku zoznamu, za ním nasleduje druhá preferovaná alebo iná adresa.
Čo je * hostEntry-> h_addr_list robiť? Možno sa domnievate, že operátor oddelenia (*) sa používa na prístup k jedinej adrese v zozname. Ak však neposkytne konkrétny index, dereferenčná operácia automaticky odhalí prvú preferovanú adresu. Táto konkrétna časť je ekvivalentná * hostEntry-> h_addr_list [0], čo je zaručené, pretože server musí mať aspoň jednu adresu.
Ďalej sa char * vrátený operáciou dereferencing prevedie do in_addr * alebo LPIN_ADDR. Nakoniec sa vykoná iná deferenčná operácia na vrátenie štruktúry in_addr, na ktorú odkazuje ukazovateľ, ktorý môže mať iba jednu adresu. Výsledný in_addr struct je potom priradený serveru serverInfo.sin_addr. Následné pripojenie () zaberie jednu adresu ako parameter pri vytváraní spojenia so serverom.
Ak je známa adresa IP servera, je možné získať platný HOSTENT pomocou použitia gethostbyaddr() (na rozdiel od gethostbyname() použitého v predchádzajúcom výpisu):
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) { // Zachovajte podľa toho }
V tomto prípade sa inet_addr() použije na kopírovanie reťazca označujúceho IP adresu priamo do štruktúry in_addr. Potom adresa štruktúry je obsadená do const char * podľa požiadaviek gethostbyaddr(). Obidve metódy sa označujú ako riešenie adresy servera, pretože Winsock vracia záznamy úplných adries z čiastkových informácií.
Niekoľko ďalších poznámok: port 80 bol použitý jednoducho preto, že cez tento port dochádza k prenosu internetových stránok. Ak by ste poslali reťazec na webový server požadujúci konkrétny súbor a pokúsili by sa vám niečo vrátiť, mali by ste mať veľmi jednoduchý webový prehliadač. Samozrejme, tento reťazec musí obsahovať úplný príkaz HTTP. Je skvelé, že môžeme počúvať a pripájať sa k iným počítačom, ale komunikácia zahŕňa aj odosielanie a prijímanie.
Odosielanie a prijímanie
Odosielanie je spracované dostatočne pohodlne funkciou send():
int send( SOCKET s, const char * FAR buf, int len, int flags );
V podstate by ste skopírovali všetko, čo ste chceli, do vyrovnávacej pamäte a použite funkciu send() na pripojenom konektore, aby sa údaje dostali na druhý koniec:
char buffer[256]; // Vyhlásenie vyrovnávacej pamäte na zásobníku char *buffer = new char[256]; // alebo na halde ZeroMemory(buffer, 256); strcpy(buffer, "Pretend this is important data."); nret = send(theSocket, buffer, strlen(buffer), // Všimnite si, že toto špecifikuje dĺžku reťazca; nie // veľkosť celého vyrovnávacej pamäte 0); // Najčastejšie je nula, ale ďalšie možnosti nájdete v programe MSDN delete [] buffer; // Ak a len vtedy, ak sa použilo deklarácia haldy if (nret == SOCKET_ERROR) { // Získajte špecifický kód // Zachovajte podľa toho return NETWORK_ERROR; } else { // nret obsahuje počet poslaných bajtov }
Príjem je rovnaký proces, späť:
char buffer[256]; // Na stohu char *buffer = new char[256]; // alebo na halde nret = recv(theSocket, buffer, 256, // Úplná veľkosť buffera 0); delete [] buffer; // Manipulujte vyrovnávaciu pamäť a potom odstráňte, či a len ak // vyrovnávacia pamäť bola pridelená na halde if (nret == SOCKET_ERROR) { // Získajte špecifický kód // Zachovajte podľa toho return NETWORK_ERROR; } else { // nret obsahuje počet prijatých bajtov }
Čo je zaujímavé, že na paneli s nástrojmi v programe Microsoft Outlook je označené tlačidlo “Odoslať/Rec.” Je “Prijatie” skrátené na “Recv” jednoducho, aby sa zaistilo, že tlačidlo vyzerá správne, alebo je to zvyk programátora z písania recv() toľkokrát? Vytvorte si svoje vlastné konšpiračné teórie (opäť dobré pre malé strany na večierkoch).
Tu som narazil na malý problém pri písaní vlastných programov Winsock. Použitie rekv() je skvelé, keď presne viete, koľko dát budete dostávať (napríklad v hre, kde prvý byte môže byť príkazom a ďalší bajt je parameter atď.), Ale keď to nie je Neviete, čo robíte? Ak prijímané dáta sú ukončené znakom nového riadku (bežný problém s klientmi Java komunikujúcimi s servermi C), môžete zapísať funkciu readLine(), aby ste získali všetko, čo k tomuto znaku. Tu je to, čo som použil:
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); } } }
Vektor sa používa namiesto poľa, pretože jeho úložný priestor sa môže automaticky zvýšiť tak, aby vyhovoval dĺžke čiary. Ak recv() vráti chybu (označenú bytesReceived, ktorá je menšia ako nula), vráti sa NULL. Keďže je to možné, volajúce funkcie by mali zabezpečiť, že reťazec vrátený z readLine () je platný pred použitím. Vo vnútri slučky sa od zásuvky prijíma jeden znak, a ak nie znak nového riadku, pridaný k vektoru. Ak je to znak nového riadku, obsah vektora sa skopíruje do reťazca C a vráti sa. Reťazec je deklarovaný ako jeden znak väčší ako vektor a memset() na nulu, takže vrátený riadok bude automaticky ukončený NULL. Ukončenie reťazcov s hodnotou NULL zabraňuje nezvyčajným chybám a je vo všeobecnosti dobrá prax v programovaní.
Táto chytrne vylepšená verzia nie je prezentovaná s podporou pre spätné priestory a možnosť ľahko meniť nový znak:
// Kód pôvodne napísaný Nor. Zmenené mierne na // Podporovať MessageBox() API, robiť logiku čitateľnejšie, // zarovnať medzery a pridať komentáre. Publikované so súhlasom. #define backKey '\b' // Ak chcete vypnúť 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: // Zachyťte späť if (theVector.size() > 0) theVector.pop_back(); break; case endStr: // Ak sa dosiahne koniec znakového reťazca, case newLine: // alebo ak bol dosiahnutý koniec znaku, 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: // Každý pravidelný char theVector.push_back(buffer); break; } } }
Neblokovacie a asynchrónne zásuvky
Až do tohto bodu sme hovorili o blokovaní zásuviek, kde volanie funkcie, akou je accept(), nečakane čaká na to, aby sa používateľ pripojil. Neblokujúca zásuvka sa vracia okamžite vždy, keď je vyzvané, aby urobila niečo s úspešným výsledkom, chybou alebo ničím (čo naznačuje, že niečo bude neskôr prijaté). Nevýhodou používania tohto typu je, že budete musieť manuálne dotazovať soketu, aby ste zistili, či sa na každú funkciu, ktorú voláte, dostal výsledok. Môžete prejsť sadu zásuviek na funkciu select() a zistiť, ktoré z nich sú pripravené na čítanie, písanie alebo vrátenie chýb.
Funkcie používajúce asynchrónne zásuvky sa tiež okamžite vrátia, ale môžete určiť správu, ktorá sa pošle do okna, keď nastane určitá udalosť. Môžete napríklad mať soket poslať správu SOCKET_GOTMSG vždy, keď niečo dostane. Zvyčajne je rozumné skontrolovať chyby (ťažkopádne, ale nevyhnutné), keď dostanete soketovú správu, aby ste neskôr mohli spôsobiť zbytočné problémy. Najprv definujeme niektoré funkcie, ktoré použijeme na vytvorenie asynchrónnej soketu:
- int WSAAsyncSelect (SOCKET s, HWND hwnd, bez znamienka int wMsg, long lEvent)
Táto funkcia slúži na identifikáciu soketu ako asynchrónne a priradenie správy k nej. S je socket, s ktorým pracujete. Hwnd je popisovač okna, ktorá prijme správu, keď socket generuje udalosť. WMsg je správa, ktorú chcete odoslať do vášho okna (príkladom je správa SOCKET_GOTMSG zhora). Parameter lEvent má jednu alebo viac príznakov, ktoré určujú, v ktorej zásuvke sa odosiela vaša správa. Niektoré z týchto príznakov sú:
FD_READ: Socket je pripravený prijímať dáta
FD_WRITE: Socket je pripravený na odosielanie údajov
FD_ACCEPT: Používa sa na serveroch, táto správa indikuje pripojenie používateľa
FD_CONNECT: Používa sa v klientských aplikáciách, táto správa vám povie, že je zapojená zásuvka
FD_CLOSE: Súprava bola práve zatvorená
- WSAGETSELECTERROR (LPARAM lparam)
Určuje, či socket vrátil chybu. Z technického hľadiska to nie je funkcia, ale makro (môžete skutočne vytvoriť smalltalk na stranách s týmto malým faktom).
- WSAGETSELECTEVENT (LPARAM lparam)
Ďalšie užitočné makro definované v winsock2.h je WSAGETSELECTEVENT(), ktorý sa používa na presné zobrazenie toho, čo urobila zásuvka.
Takže si vytvoríme asynchrónnu zásuvku:
// Začneme tým, že vytvoríme príznak, pomocou ktorého nás bude systém Windows kontaktovať, keď sa niečo stane #define THERE_WAS_A_SOCKET_EVENT WM_USER + 100 // WM_USER je základňou pre vlastné správy
// Niekde v našom inicializačnom kóde po CreateWindow () voláme WSAAsyncSelect () WSAAsyncSelect ( theSocket, hwnd, THERE_WAS_A_SOCKET_EVENT, FD_READ | FD_WRITE | FD_CONNECT | ... ); // Toto prekladá: Windows, kontaktujte ma prosím pomocou príznaku THERE_WAS_A_SOCKET_EVENT, ktorý som // predtým definované vždy, keď sa nachádzajú údaje na čítanie (FD_READ), alebo keď môžem slobodne odosielať dáta // (FD_WRITE), alebo keď som úspešne pripojený k niekomu inému (FD_CONNECT) alebo kedy... atď.
// V našej procedúre okna (funkcia, ktorá spracováva všetky správy, ktoré systém Windows odosiela do vašej aplikácie) LRESULT WINAPI TheWindowProcedure ( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam ) { switch ( msg ) { case THERE_WAS_A_SOCKET_EVENT: if ( WSAGETSELECTERROR ( lParam ) ) { // Ak sa vyskytla chyba, closesocket ( theSocket ); WSACleanup (); // Vypnutie Winsock return NETWORK_ERROR; } switch ( WSAGETSELECTEVENT ( lParam ) ) { // Čo sa stalo, presne? case FD_READ: // Dostávajte údaje break; case FD_WRITE: // Napíšte údaje break; case FD_CONNECT: // Práve pripojený k serveru break; case ... // Rovnaké nastavenie pre iné príznaky break; } break; // iné príkazy s logikou, ktorá spracúva iné správy systému Windows } }
Upozorňujeme, že pre každú udalosť nemôžete definovať jednu správu, ako napríklad SOCKET_GOTMSG pre FD_READ a potom SOCKET_CONNECTED pre FD_CONNECT. Je to preto, že opakované volania WSAAsyncSelect() na nastavenie každej vlajky zrušia účinky posledného hovoru na WSAAsyncSelect().
Ďalšie Návody a Odkazy
Tento tutoriál som napísal v decembri 2000 a od tej doby sedem takých rokov zaznamenalo neustály tok návštevníkov a vylepšení. Dúfam, že ste si užili čítanie rovnako, ako som si užila písanie: ďakujem vám, že ste použili Johnnieho Winsock Tutorial. Vyššie uvedené je len stručný prehľad možností, ktoré môžete dosiahnuť prostredníctvom spoločnosti Winsock, a iní urobili omnoho lepšiu prácu ako ja pri skúmaní špecifikácií tejto témy:
(Ak chcete získať viac informácií, presuňte kurzor myši na obal knihy.)
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
- Winsock Programátor FAQ
- Sprievodca MadWizard Winsock Networking v C++ (je k dispozícii aj verzia Assembly)
- 7 Programových častí Sériových Programov prezentovaných flipcode
Súhlasím s Thomasom Bleekerom (MadWizard), že “programovanie sietí je jednoduchšie ako je.” Nemôžem na vás ukázať dôležitosť praktického využitia týchto funkcií spolu s ladiaci nástrojom, aby ste mohli vidieť, čo sa deje. V konečnom dôsledku budete mať oveľa lepšie pochopiť, ako fungujú veci, ak sa vám to zle, preskúmať, prečo ste to zle a potom skúsenosti s potešením, ako to spraviť. Chyby, inými slovami, je to, ako sa učíme.
Ako sa Môžem Zlepšiť?
Je tu niečo, čo je potrebné objasniť? Sprievodca nepokrýva témy súvisiace so Winsockom, o ktorých sa chcete dozvedieť? Splnil tutoriál vaše potreby ako vývojár softvéru? Je to zábavné? chytro napísané? prehnane zjednodušujúce? Alebo len tak?
Original in English: http://johnnie.jerrata.com/winsocktutorial/