[Grafika] [WebTip] [Fotografování] [Galerie] [MujMac] [Printing]
  Redakce: info (at) builder.cz   Inzerce: reklama (at) grafika.cz
Diskuzní fóra
.Net (58811)
ASP (1464)
ActiveX (163)
Allegro (126)
Assembler (3699)
C++ Builder (21656)
C/C++ (39174)
Databáze (27980)
Delphi (71030)
DelphiX (1575)
DirectX (1427)
Java (34885)
JavaScript (11036)
Matematické programy (1906)
OOP a UML (579)
OpenGL (6612)
Php (61391)
PowerBuilder (457)
Problémy a algoritmy (8579)
Programování v Linuxu (1837)
Právo a programování (3146)
Python (1076)
Ruby (128)
Visual Basic (11529)
Visual C++ (12469)
Wap (56)
Web (10262)
Web servery (5238)
Win32 (12737)
Windows CE (830)
XML/XSL (1655)
Textová inzerce
Služby Builder.cz
  • Bazar - koupím(0)
  • Bazar - prodám(0)
  • Hledám práci(0)
  • Nabízíme práci(0)
  • Projekty(0)
  • TCP klient v MS Windows
    Dnes se naučíme vytvářet jednoduchého TCP klienta v operačním systému MS Windows. Naučíme se jak vytvářet soket, jak navázat spojení se serverem, jak přijmout a odeslat data. Vysvětlíme si pojem soket a ukážeme si funkce socket, connetc, send, recv, closesocket.
    Sokety a C++
    Předchozí díl: TCP klient v Linuxu

    Následující díl: TCP server v Linuxu
    Autor: Radim Dostál
    Rubrika: C/C++
    Publikováno: 03.01. 2003
     Tisk článku
    Poslat odkaz emailem
     

    Dnes se naučíme vytvářet jednoduchého klienta v operačním systému MS Windows®. Naučíme se jak vytvářet soket, jak navázat spojení se serverem, jak přijmout a odeslat data. Komunikace pomocí TCP/IP probíhá na principu klient - server. Server je program, který čeká na připojení. Klient je program, který spojení navazuje. Klient se mi zdá jednodušší, proto jím začneme.

    V minulém díle jsme si velice zjednodušeně vysvětlili co je to soket. Pro ty, kteří nečetli minulý článek věnovaný Linuxu to zopakuji.

    V úvodním díle jsme si popsali službu TCP. Chceme-li komunikovat se vzdáleným počítačem, je třeba vytvořit soket. V úvodním díle jsem napsal, že soket je obecný nástroj pro komunikaci. Lze jej mimo jiné použít i pro komunikaci po síti pomocí protokolů TCP/IP. Co si ale pod pojmem soket blíže představit? Překlad slova socket je objímka, zásuvka, hrdlo(trubky). Máme za úkol pomocí soketu spojit dva počítače. Představme si (samozřejmě velice obrazně :-), že mezi počítačem u kterého sedíme a vzdáleným počítačem, se kterým chceme komunikovat je natažené potrubí. V každém počítači je jeden konec trubky - hrdlo trubky - anglicky socket. Nejprve musíme vytvořit hrdlo trubky (vytvořit soket - funkce socket). Poté musíme druhý konec nasměrovat správným směrem. Tedy zasunout druhý konec roury do vzdáleného počítače (navázat spojení - funkce connect). Máme-li takto vytvořené potrubí, můžeme do našeho konce potrubí (do soketu) něco vložit (poslat data - funkce send). Ono to na druhém konci potrubí "vypadne". Také může něco vypadnout nám na našem konci potrubí (příjem dat - funkce recv). Až nás přestane práce s potrubím bavit, tak náš konec potrubí zahodíme (uzavřeme spojení - funkce close). Na druhé straně potrubí sice bude k dispozici druhý konec trubky, ale nebude už na nic. Protože z něj už nic nevypadne (my tam nemůžeme nic vložit, když nemáme svůj konec) a pokud do něj něco vhodí druhá strana, tak to nikam nedojde (náš konec už není). Je zřejmé, že když navazujeme spojení (dáváme druhý konec potrubí do vzdáleného počítače), musí být na vzdáleném počítači něco, co potrubí uchopí a připraví svůj konec potrubí pro komunikaci. Tedy vzdálený počítač musí být TCP server. TCP serveru se budeme věnovat příští článek.

    Tolik pro představu pro lidi, kteří o soketech ještě nikdy neslyšeli. Nyní se podrobněji podívejme na jednotlivé funkce.

    Vytvoření soketu

    Soket je reprezentován svým identifikátorem, který je typu SOCKET. První malý rozdíl oproti Linuxu. V Linuxu to bylo celé číslo. Je to ale jen takový formální rozdíl, protože typ SOCKET je definován jako u_int. K vytvoření soketu slouží funkce socket. Hlavička funkce:

    • SOCKET socket(int af, int type, int protocol); - prvním parametrem je rodina protokolů. My budeme sokety používat pro komunikaci po síti pomocí protokolu IP. Proto budeme jako první parametr zadávat vždy hodnotu makra AF_INET. Druhým parametrem je typ soketu. Chceme-li používat protokol TCP (spojová služba, "proud" dat), předáme jako druhý parametr hodnotu makra SOCK_STREAM. Později se v seriálu budeme věnovat také protokolu UDP, potom budeme používat makro SOCK_DGRAM. Makro SOCK_STREAM budeme používat v případě, že chceme vytvořit dvoubodové spojení. Bude se jednat o spojovou službu. Odeslaná data budou potvrzována a určitě přijdou v pořadí, v jakém jsme je poslali. Naopak makro SOCK_DGRAM udává, že se bude jednat o nespojovou datagramovou službu. Tím se budeme zabývat v budoucnu. Existují i další možnosti, kterými se v seriálu pravděpodobně zabývat nebudeme. Posledním parametrem je identifikátor protokolu. Chceme-li používat protokol TCP předáme jako parametr hodnotu makra IPPROTO_TCP. Prozatím budeme tedy vždy vytvářet soket takto: socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); funkce vrací identifikátor soketu. V případě, že došlo k chybě (soket není možné vytvořit), funkce vrací INVALID_SOCKET.
    Struktura sockaddr_in

    Než začneme navazovat spojení, seznámíme se se strukturou sockaddr_in. Opět si můžeme všimnout drobných rozdílů oproti linuxové variantě. Instance této struktury přesně charakterizuje místo, kam chceme naši rouru zasunout. Je v ní jak IP adresa (adresa vzdáleného počítače), tak i číslo portu (adresa aplikace na vzdáleném počítači). Pojmy IP adresa a TCP port jsem vysvětlil v úvodním díle seriálu. Atributy struktury:

    • short sin_family - typ adresy. Vždy bude odpovídat makru AF_INETP. K IP adrese vzdáleného počítače si vytvoříme instanci struktury hostent. To jsme si již ukazovali v článku Překlad doménových jmen v OS Windows. Struktura hostent má atribut int h_addr_type. Atributu sin_family přiřadíme hodnotu, která bude v atributu h_addr_type.
    • struct in_addr sin_addr - adresa vzdáleného počítače. Se strukturou in_addr jsme se setkali při překladu doménových jmen.
    • u_short sin_port - číslo portu na vzdáleném počítači, ke kterému se chceme připojit. Nedoporučuji vám ale přiřazovat tomuto atributu přímo číslo. Musíme si uvědomit, že internet je síť složená z různých platforem. Mohou zde nastat problémy s reprezentací čísla (big endian vs. little endian). Pro vyřešení těchto problémů použijeme funkci htons.

    Další atributy nás nemusí zajímat. Můžeme se setkat s identifikátorem SOCKADDR_IN. Jedná se o další pojmenování (pomocí typedef) struktury sockaddr_in. Strukturu sockaddr_in budeme přetypovávat na strukturu sockaddr. Struktura sockaddr by se nám vyplňovala špatně.

    Navázání spojení

    Máte tedy vytvořen náš konec "hadice" (pomocí funkce socket). Teď by to chtělo vložit druhý konec hadice do nějakého počítače. K tomu slouží funkce conncet. Její hlavička je:

    • int connect(SOCKET s, const struct sockaddr *name, int namelen); - prvním parametrem je identifikátor soketu. Druhým je adresa TCP serveru. Vytvoříme instanci struktury sockaddr_in a ukazatel na ní přetypujeme na ukazatel na strukturu sockaddr. Posledním parametrem je velikost adresy. V případě, že spojení bylo navázáno, funkce vrací 0. Jinak SOCKET_ERROR. Nyní můžeme začít posílat data nebo data přijímat.
    Odeslání dat

    K odeslání dat slouží funkce send. Její hlavička je:

    • int send(SOCKET s, char* buf, int len, int flags); - funkce odešle data. Oproti linuxové verzi se deklarace liší v typech parametrů. Jejich význam ale zůstává. Prvním parametrem je identifikátor soketu, druhým parametrem je ukazatel na blok dat určený k odeslání. Třetím parametrem je velikost bloku dat v bytech. Posledním parametrem jsou flagy. My budeme prozatím používat 0. Funkce vrací počet odeslaných bytů, nebo SOCKET_ERROR v případě chyby. Existuje podobná funkce sendto. Je možné ji také použít.
    Příjem dat

    K příjmu dat slouží funkce recv. Její hlavička je:

    • int recv(SOCKET s, char *buf, int len, int flags); - funkce přijme data. Oproti linuxové verzi se deklarace liší v typech parametrů. Jejich význam ale zůstává. Prvním parametrem je identifikátor soketu, druhým parametrem je ukazatel na souvislý blok paměti, který je určen k zaplnění příchozími daty. Třetím parametrem je maximální počet dat, které je možné do bufferu přijmout. Typicky je to číslo, které udává velikost na kterou je alokován buffer (v bytech). Posledním parametrem jsou flagy. My budeme prozatím používat 0. Funkce vrací počet skutečně přijatých bytů, nebo SOCKET_ERROR v případě chyby. Je-li spojení uzavřeno, funkce vrací 0.

    Když jsme vytvářeli soket, použili jsme makro SOCK_STREAM a rozhodli se komunikovat pomocí TCP. Tím jsme hlavně rozhodli o charakteru spojení. Máme garantováno, že data budou přijímána v pořadí, ve kterém byla odeslána. Data nebudou poškozena. Budeme je přijímat jako proud bytů. Jestliže nám druhá strana pomocí jednoho soketu pošle vícekrát data, nebudeme na naší straně schopni nijak rozpoznat, kde končí jeden odeslaný blok dat a začíná druhý. Musíme jej sami rozpoznat řídicích bytů, nebo mít pevně danou délku jednoho záznamu. V tomto smyslu lze přirovnat práci se soketem typu SOCK_STREAM jako s binárním souborem (posloupnost bytů nebo proud bytů).
    Jestliže druhá strana (server) uzavře spojení, funkce recv vrací jako počet přečtených bytů číslo 0. Toho využiji v jednoduchém příkladu.

    Zatím jen pro úplnost poznamenám, že zde mluvíme o tak zvaném blokovacím módu. Funkce recv čeká na data. Tedy zablokuje se běh programu, dokud nějaká data nepřijdou. O neblokovacím módu, jak jej udělat a o věcech s tím souvisejících si povíme něco v budoucnu. Blokovací mód nám bude prozatím stačit. Nyní několik článků budu mluvit o blokovacím módu a nebudu to zdůrazňovat.

    Uzavření spojení

    Soket uzavřeme funkcí closesocket. Musím jen zdůraznit, že jej neuzavřeme funkcí close, jak jsme zvyklí z Unixových systémů. Této chyby se lehce dopustí někdo, kdo je zvyklý používat sokety v Linuxu a přechází na MS Windows ®. Je to taková zákeřnost, protože close očekává jako parametr int, takže nás překladač neupozorní na chybu. Je-li někdo zvyklý z Unixových systémů na close, může být velice překvapen. Překladač nic nenahlásí, spojení se neuzavře, ale programátor při ladění intuitivně očekává, že close spojení uzavře. Například při komunikaci pomocí protokolů HTTP má uzavření spojení velký význam.

    Ukázkový příklad - TCP klient ve Windows

    Nyní si vytvoříme jednoduchý příklad. Vytvoříme program, který naváže spojení se serverem, odešle mu textový řetězec a přijme jiný textový řetězec. Poté server spojení ukončí. Parametry programu budou adresa vzdáleného počítače (IP adresa nebo doménové jméno) a číslo portu. V příkladu používám šablonu string. Umožňuje v C++ pohodlnější práci s řetězci než pole znaků. Více informací o šabloně je v článku Řetezce v C++.

    #include <iostream>
    #include <string>
    #include <windows.h>
    
    #define BUFSIZE 1000
    
    using namespace std;
    
    int main(int argc, char *argv[])
    {
        WORD wVersionRequested = MAKEWORD(1,1); // Číslo verze
        WSADATA data;                           // Struktura s info. o knihovně
        string text("Ahoj\n");                  // Odesílaný a přijímaný text
        hostent *host;                          // Vzdálený počítač
        sockaddr_in serverSock;                 // Vzdálený "konec potrubí"
        int mySocket;                           // Soket
        int port;                               // Číslo portu
        char buf[BUFSIZE];                      // Přijímací buffer
        int size;                             // Počet přijatých a odeslaných bytů
        if (argc != 3)
        {
            cerr << "Syntaxe:\n\t" << argv[0]
                 << " " << "adresa port" << endl;
            return -1;
        }
        // Připravíme sokety na práci
        if (WSAStartup(wVersionRequested, &data) != 0)
        {
            cout << "Nepodařilo se inicializovat sokety" << endl;
            // Podle všeho, zde se WSACleanup volat nemusí.
            return -1;
        }
        port = atoi(argv[2]);
        // Zjistíme info o vzdáleném počítači
        if ((host = gethostbyname(argv[1])) == NULL)
        {
            cerr << "Špatná adresa" << endl;
            WSACleanup();
            return -1;
        }
        // Vytvoříme soket
        if ((mySocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
        {
            cerr << "Nelze vytvořit soket" << endl;
            WSACleanup();
            return -1;
        }
        // Zaplníme strukturu sockaddr_in
        // 1) Rodina protokolů
        serverSock.sin_family = AF_INET;
        // 2) Číslo portu, ke kterému se připojíme
        serverSock.sin_port = htons(port);
        // 3) Nastavení IP adresy, ke které se připojíme
        memcpy(&(serverSock.sin_addr), host->h_addr, host->h_length);
        // Připojení soketu
        if (connect(mySocket, (sockaddr *)&serverSock, sizeof(serverSock)) == -1)
        {
            cerr << "Nelze navázat spojení" << endl;
            WSACleanup();
            return -1;
        }
        // Odeslání dat
        if ((size = send(mySocket, text.c_str(), text.size() + 1, 0)) == -1)
        {
            cerr << "Problém s odesláním dat" << endl;
            WSACleanup();
            return -1;
        }
        cout << "Odesláno " << size << endl;
        // Příjem dat
        text = "";
        while (((size = recv(mySocket, buf, BUFSIZE, 0)) != 0) && (size != -1))
        {
            cout << "Přijato " << size << endl;
            text += buf;
        }
        if (size == -1)
        {
            cout << "Nelze přijmout data" << endl;
        }
        // Uzavřu spojení
        closesocket(mySocket);
        WSACleanup();
        cout << endl << text << endl;
        return 0;
    }
    

    Tento příklad je možné si stáhnout jako ukázku. Tím máme hotového Windows klienta. Teď by to pro názornou ukázku chtělo mít k dispozici server, ke kterému se klient bude moci připojit. Server si napíšeme v příštích článcích. Věřím, že je mnoho takových, kteří si chtějí vyzkoušet klienta teď hned. Chtělo by to nějaký server na rychlé vyzkoušení. Proč ale server narychlo psát, když na internetu je jich spousta (například WWW servery). Proto druhý příklad ke stažení je příklad, který stáhne WWW stránku (tento článek) a uloží ji na disk. Používám protokol HTTP, což je textový protokol, který běží nad TCP/IP. WWW servery (pokud není uvedeno jinak) očekávají spojení na portu 80. HTTP protokolem se v seriálu zabývat nebudeme.

    Příklady

    V příštím článku vytvoříme TCP server v Linuxu. V článku, který bude následovat poté vytvoříme TCP server v MS Windows®.


    Zpět na začátek stránky

    Autor: Radim Dostál
    Klikni pro další články autora

    Hodnocení článku
    1 | 2 | 3 | 4 | 5
    Aktuální známka: 2.60
    (Počet známek: 3691)

    Komentáře k článku
    nejsemzprahy27.01.17:26Jde se connectnout na loopback?
    Radim27.01.19:41RE: Jde se connectnout na loopback?
    nejsemzprahy03.02.10:43RE: RE: Jde se connectnout na loopback?
    nejsemzprahy03.02.19:23RE: RE: RE: Jde se connectnout na loopback?
    František22.12.11:46Obrázek
    R.O.12.10.9:51Poslani dalsiho dotazu na zaklade prijatych dat
    Jan Netopil09.11.17:36Číslo portu
    goderik21.09.14:37RE: Číslo portu
    El Nino19.10.9:50C++ Builder
    Mato09.08.12:08proxy server
    Dark Echo18.05.19:55C++ Builder a formulaře
    martin23.02.20:56pomoc
    Cita04.12.12:39Nelze na php?
    Cita04.12.12:46Jinak
    Cita04.12.12:47RE: Nelze na php?
    Petr09.04.14:58error 2 příklad
    Petr09.04.14:46error 2 příklad
    Petr09.04.14:46error 2 příklad
    Ubu04.02.1:12Jak je to v C?
    Ubu04.02.13:18RE: Jak je to v C?
    Richard Rajnoch20.12.21:51#include
    Richard Rajnoch20.12.21:57RE: #include
    Container20.07.3:10potize s cyklem while () kde je recv()
    Container20.07.3:12RE: potize s cyklem while () kde je recv()
    buggyb12.01.20:28kompilace
    Radim Dostál22.01.9:47RE: kompilace
    sh3g702.03.19:03RE: RE: kompilace
    JK16.11.17:01Otázka
    Radim Dostál27.11.9:59RE: Otázka
    jirka16.05.22:19Chyba
    Radim Dostál28.05.14:24RE: Chyba
    twomi17.10.17:26RE: Chyba
    Mira23.02.15:19HTTP
    Dostál Radim25.02.14:45RE: HTTP
    Mira27.02.15:12RE: RE: HTTP
    Radim Dostál28.02.13:32RE: RE: RE: HTTP
    Martin15.01.11:37Druhy priklad
    Dostál20.01.21:20RE: Druhy priklad
    Dostál20.01.21:20RE: Druhy priklad
    Libor Nenadál28.01.17:36RE: Druhy priklad
    /'nothing17.02.17:39/'nothing
         





    info@builder.cz
    Vydává Grafika Publishing, s.r.o.
    Copyright (c) 1997-2002 Všechna práva vyhrazena