TCP klient v Linuxu - Builder.cz - Informacni server o programovani

Odběr fotomagazínu

Fotografický magazín "iZIN IDIF" každý týden ve Vašem e-mailu.
Co nového ve světě fotografie!

 

Zadejte Vaši e-mailovou adresu:

Kamarád fotí rád?

Přihlas ho k odběru fotomagazínu!

 

Zadejte e-mailovou adresu kamaráda:



C/C++

TCP klient v Linuxu

cpp net

20. prosince 2002, 00.00 | Dnes se naučíme vytvářet jednoduchého TCP klienta v operačním systému Linux. 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, close.

{TCP klient v Linuxu}

Dnes se naučíme vytvářet jednoduchého klienta v operačním systému Linux. 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 ú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 celé číslo int. K vytvoření soketu slouží funkce socket. Funkce je deklarována v hlavičkovém souboru sys/socket.h. Jako parametry budeme funkci předávat makra, která jsou v hlavičkovém souboru sys/types.h. Hlavička funkce:

  • int socket(int domain, int type, int protocol); - prvním parametrem je doména. Určuje způsob komunikace. 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. Existují i jiné možnosti (soket je velice obecný nástroj), ale těmi se nebudeme v seriálu zabývat. 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. Více informací je v manuálových stránkách. 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í -1.
Struktura sockaddr_in

Než začneme navazovat spojení, seznámíme se se strukturou sockaddr_in. 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:

  • sa_family_t 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 Linux. Struktura 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_addrjsme se setkali při překladu doménových jmen.
  • unsigned short int 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. 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(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); - 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 -1. A nyní můžeme začít posílat data nebo data přijímat.

Jedna z možností jak poslat a přijmout data je použít funkce send a recv, které si popíšeme. Jinak ale můžeme použít identifikátor soketu téměř všude, kde je očekáván identifikátor souboru (file descriptor). Můžeme tedy používat funkce read, write. Také je možné například pomocí fdopen vytvořit strukturu FILE a se soketem pracovat pomocí fprintf. Taková možnost v MS Windows® není. Alespoň tedy mě se to nepovedlo.

Odeslání dat

K odeslání dat slouží funkce send. Funkce send je deklarována v hlavičkovém souboru sys/socket.h. Její hlavička je:

  • int send(int s, const void *msg, size_t len, int flags); - funkce odešle data. Prvním parametrem je identifikátor soketu, druhým parametrem je ukazatel na blok dat (zpráva - message) 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 -1 v případě chyby. Na manuálových stránkách funkce send naleznete i jiné varianty. Jsou to funkce sendto a sendmsg.
Příjem dat

K příjmu dat slouží funkce recv. Funkce recv je deklarována v hlavičkovém souboru sys/socket.h. Její hlavička je:

  • int recv(int s, void *buf, size_t len, int flags); - funkce přijme data. 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 -1 v případě chyby. Na manuálových stránkách funkce send naleznete i jiné varianty. Jsou to funkce recvfrom a recvmsg. Jestliže druhá strana (server) uzavře spojení, funkce recv vrací -1 (Rozdíl oproti WinSocket).

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ů).

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 stejně jako soubor. Použijeme funkci close.

[-more-]{Ukázkový příklad} Ukázkový příklad - TCP klient v Linuxu

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 <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFSIZE 1000

using namespace std;

int main(int argc, char *argv[])
{
    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;
    }
    port = atoi(argv[2]);
    // Zjistíme info o vzdáleném počítači
    if ((host = gethostbyname(argv[1])) == NULL)
    {
        cerr << "Špatná adresa" << endl;
        return -1;
    }
    // Vytvoříme soket
    if ((mySocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
    {
        cerr << "Nelze vytvořit soket" << endl;
        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;
        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;
        return -1;
    }
    cout << "Odesláno " << size << endl;
    // Příjem dat
    text = "";
    while ((size = recv(mySocket, buf, BUFSIZE, 0)) != -1) 
    {        
        cout << "Přijato " << size << endl;
        text += buf;
    }
    // Uzavřu spojení
    close(mySocket);
    cout <<  endl << text << endl;
    return 0;
}

Tento příklad je možné si stáhnout jako ukázku. Ke stažení je toho tentokrát ale více. Druhý příklad je program, který vlastně dělá stejnou činnost jako tento, ale nepoužívá funkce send a recv, ale místo nich používá fscanf a fprintf. I pomocí těchto funkcí lze posílat a přijímat data. Tím máme hotového Linuxového 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 třetí 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 klienta pod MS Windows®. Opět zdrojový text bude nápadně podobný tomuto. Nebude ale úplně stejný. A v článku, který bude následovat poté, začneme vytvářet jednoduchý TCP server.

Obsah seriálu (více o seriálu):

Tématické zařazení:

 » Rubriky  » C/C++  

 

 

 

Nejčtenější články
Nejlépe hodnocené články

 

Přihlášení k mému účtu

Uživatelské jméno:

Heslo: