Beejov Vodnik za Omrežno Programiranje

Uporaba Internetnih Vtičnic

Brian “Beej Jorgensen” Hall
beej@beej.us

Različica 3.0.21
8 Junija 2016

Copyright © 2015 Brian “Beej Jorgensen” Hall

Vsebina

1. Uvod
1.1. Občinstvo
1.2. Platforma in prevajalnik
1.3. Uradna stran in knjige za prodajo
1.4. Opomba za programerje Solaris/SunOS
1.5. Opomba za programerje programa Windows
1.6. Politika E-pošte
1.7. Zrcaljenje
1.8. Opomba za prevajalce
1.9. Avtorske pravice in distribucija

2. Kaj je vtičnica?
2.1. Dve vrsti internetnih vtičnic
2.2. Nizka stopnja nesmisla in teorija omrežja

3. Naslovi IP, struct in obdelava podatkov
3.1. IP naslovi, različice 4 in 6
3.2. Byte Order
3.3. structs
3.4. Naslov IP, del Deux

4. Skakanje iz IPv4 v IPv6

5. Sistemski Klici ali Bust
5.1. getaddrinfo () – Pripravite se na začetek!
5.2. socket () – Pridobite opis datoteke!
5.3. bind () – V kakšnem pristanišču sem?
5.4. connect () – Hej, ti!
5.5. listen () – Ali me bo kdo poklical?
5.6. accept () – “Hvala za klic pristanišča 3490.”
5.7. send () in recv () – Pogovorite se z mano!
5.8. sendto () in recvfrom () – Pogovorite se z menoj, slog DGRAM
5.9. close () in shutdown () – Iztegni moj obraz!
5.10. getpeername () – Kdo ste?
5.11. gethostname () – Kdo sem jaz?

6. Ozadje odjemalca-strežnik
6.1. Preprost tok strežnika
6.2. Preprost odjemalec toka
6.3. Datagramove vtičnice

7. Nekoliko napredne tehnike
7.1. Blokiranje
7.2. select () – Sinhrono V/I multipleksiranje
7.3. Ravnanje z delnim pošiljanjem ()
7.4. Serializacija – Kako pakirati podatke
7.5. Sindrom enkapsulacije podatkov
7.6. Prenosni paketi – Pozdravljeni, Svet!

8. Pogosta vprašanja

9. Man strani
9.1. accept()
9.2. bind()
9.3. connect()
9.4. close()
9.5. getaddrinfo(), freeaddrinfo(), gai_strerror()
9.6. gethostname()
9.7. gethostbyname(), gethostbyaddr()
9.8. getnameinfo()
9.9. getpeername()
9.10. errno
9.11. fcntl()
9.12. htons(), htonl(), ntohs(), ntohl()
9.13. inet_ntoa(), inet_aton(), inet_addr
9.14. inet_ntop(), inet_pton()
9.15. listen()
9.16. perror(), strerror()
9.17. poll()
9.18. recv(), recvfrom()
9.19. select()
9.20. setsockopt(), getsockopt()
9.21. send(), sendto()
9.22. shutdown()
9.23. socket()
9.24. struct sockaddr and pals

1. Uvod

Zdravo! Programiranje vtičnic vas je spustilo? Ali je to nekaj stvari precej težavno, da bi ugotovili, na strani človeka? Želite narediti kul programiranje po internetu, vendar nimate časa, da bi se prebudil s kruhom struk, ki bi poskušali ugotoviti, ali morate klicati bind() pred connect() itd. Itd.

No, ugani kaj! Sem že naredil to grdo posel, in umiraj, da podatke delim z vsemi! Prišli ste na pravo mesto. Ta dokument mora dati povprečnemu kompetentnemu programerju C robu, ki ga potrebuje, da se opne na ta šum v omrežju.

In preverite: končno sem ušel v prihodnost (prav tako v časovnem roku!) In posodobili vodnik za IPv6! Uživajte!

1.1. Občinstvo

Ta dokument je napisan kot vadnica, ne pa popolna referenca. To je verjetno v najboljšem primeru, ko ga preberejo posamezniki, ki šele začenjajo s programiranjem vtičnic in iščejo oporo. Vsekakor ni popolna in popolna navodila za programiranje vtičnic.

Upajmo, da bo dovolj čisto, če bodo tiste človeške strani začele smiselno … :-)

1.2. Platforma in prevajalnik

Koda, vsebovana v tem dokumentu, je bila prevedena na Linux PC z Gnujevim gcc prevajalnikom. Vendar bi se moral graditi na skoraj vsaki platformi, ki uporablja gcc. Seveda to ne velja, če programirate v operacijskem sistemu Windows – glejte poglavje o programiranju programa Windows spodaj.

1.3. Uradna domača stran in knjige za prodajo

Ta uradna lokacija tega dokumenta je http://beej.us/guide/bgnet/. Tam boste našli tudi kodo in prevod vodiča v različne jezike.

Če želite kupiti lepo povezane tiskane kopije (nekateri jih imenujemo “knjige”), obiščite http://beej.us/guide/url/bgbuy. Zahvaljujem se nakupu, ker mi pomaga vzdrževati življenjski stil pisanja dokumentov!

1.4. Opomba za programerje Solaris/SunOS

Pri sestavljanju za Solaris ali SunOS morate določiti nekaj dodatnih stikal ukazne vrstice za povezovanje v ustrezne knjižnice. Če želite to narediti, preprosto dodajte “-lnsl -socket -lresolv” na koncu ukaza za sestavljanje, tako:

$ cc -o server server.c -lnsl -lsocket -lresolv

Če še vedno dobite napake, poskusite dodati še “-lxnet” na koncu ukazne vrstice. Ne vem, kaj to počne točno, toda nekateri ljudje to potrebujejo.

Drug kraj, kjer boste morda našli težave, je v klicu na setsockopt (). Prototip je drugačen od tistega na mojem Linuxu, zato namesto:

int yes=1;

Vnesite to:

char yes='1';

Ker nimam polja Sun, nisem preizkusil nobenega od zgornjih informacij – ravno to so mi ljudje povedali po e-pošti.

1.5. Opomba za programerje programa Windows

Na tej točki v priročniku, zgodovinsko, sem naredil nekaj vreč v sistemu Windows, preprosto zaradi dejstva, da mi tega ni všeč. Ampak res bi rad pošten in vam povem, da Windows ima ogromno namestitveno bazo in je očitno popolnoma fini operacijski sistem.

Pravijo, da odsotnost srce raste, in v tem primeru verjamem, da je to res. (Ali morda je to starost.) Toda, kar lahko rečem, je, da po desetletju neuporabe Microsoftovih OS-jev za moje osebno delo sem veliko srečnejši! Kot taka lahko sedim in varno rečem: “Seveda, prosim uporabite Windows!” … Ok, ja, da bi zamažil zobe, da bi to rekel.

Zato vas še vedno spodbujam, da preizkusite Linux, BSD ali nekaj okusa Unixa.

Toda ljudje všeč, kar jim je všeč, in ti Windows ljudje bodo z veseljem vedeli, da te informacije na splošno veljajo za vas, z nekaj manjšimi spremembami, če sploh.

Ena kul stvar, ki jo lahko storite, je namestiti Cygwin, ki je zbirka orodij Unixa za Windows. Slišal sem na vinski trti, ki omogoča, da vse te programe zbirajo nespremenjene.

Toda nekateri od vas bi morda želeli narediti stvari Pure Windows Way. To je zelo težko od tebe, in to je tisto, kar morate storiti: zmanjkuje in takoj dobite Unix! Ne, ne, jaz se šalim. V teh dneh bi moral biti prijazen do okolja …

To je tisto, kar boste morali storiti (razen če namestite Cygwin!): Najprej prezrite skoraj vse datoteke glave sistema, ki jih tukaj omenjam. Vse, kar morate vključiti, je:

#include <winsock.h>

Počakajte! Prav tako morate poklicati WSAStartup (), preden naredite karkoli drugega s knjižnico vtičnic. Koda za to izgleda nekako takole:

#include <winsock.h>

{
    WSADATA wsaData;   // if this doesn't work
    //WSAData wsaData; // then try this instead

    // MAKEWORD(1,1) for Winsock 1.1, MAKEWORD(2,0) for Winsock 2.0:

    if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) {
        fprintf(stderr, "WSAStartup failed.\n");
        exit(1);
    }

Prav tako morate svojemu prevajalcu povedati, da se poveže v knjižnico Winsock, ki se običajno imenuje wsock32.lib ali winsock32.lib ali ws2_32.lib za Winsock 2.0. Pod VC ++ lahko to storite v meniju Projekt, pod Nastavitvami…. Kliknite jeziček Povezava in poiščite polje z naslovom »Objekt / knjižnice«. Dodajte »wsock32.lib« (ali katerikoli lib je vaša prednost) na ta seznam.

Ali tako slišim.

Nazadnje morate poklicati WSACleanup (), ko ste končali s knjižnico vtičnic. Za podrobnosti si oglejte spletno pomoč.

Ko to storite, bi morali našteti primeri v tej vadnici na splošno uporabiti z nekaj izjemami. Za eno stvar ne morete uporabiti zapreti (), da zaprete vtičnico – namesto tega morate uporabiti zaprto vtičnico (). Izberi tudi () deluje le z deskriptorji sock, ne z deskriptorji datotek (na primer 0 za stdin).

Obstaja tudi razred vtičnice, ki ga lahko uporabite, CSocket. Preverite svoje strani za pomoč pri prevajalnikih za več informacij.

Če želite več informacij o Winsock, preberite odgovore na Winsock FAQ in pojdite od tam.

Na koncu slišim, da Windows nima sistemskega klica fork (), ki se na žalost uporablja v nekaterih mojih primerih. Morda se morate povezati v knjižnici POSIX ali podobno, da jo lahko delate ali pa uporabite CreateProcess (). fork () ne upošteva nobenih argumentov in CreateProcess () traja približno 48 milijard argumentov. Če niste odvisni od tega, je CreateThread () malo preprosteje prebrati… žal diskusija o večnitnem tisku presega obseg tega dokumenta. O takih pogovorih se lahko pogovarjam, veš!

1.6. Politika e-pošte

Na splošno vam lahko pomagam z e-poštnimi vprašanji, zato se lahko prijavite, vendar ne morem odgovoriti. Vodim precej zasedeno življenje in časi, ko ne morem odgovoriti na vprašanje, ki ga imate. Ko je tako, ponavadi samo izbrišem sporočilo. Nič osebnega; Nikoli ne bom imela dovolj časa za natančen odgovor, ki ga potrebujete.

Praviloma je bolj zapleteno vprašanje manj verjetno, da bi se odzval. Če lahko zožite svoje vprašanje, preden ga pošljete in se prepričajte, da vključite vse pomembne informacije (kot so platforma, prevajalnik, sporočila o napakah, ki ste jih dobili, in kaj drugega, za katere menite, da mi bodo morda pomagale odpraviti težave), boste veliko bolj verjetno dobili Odgovor. Za več kazalcev preberite dokument ESR, kako postavljati vprašanja na pametno pot.

Če ne dobite odgovora, poskusite poiskati odgovor in če je še vedno neizvedljiv, potem mi znova napišite informacije, ki ste jih našli, in upam, da bo dovolj, da vam pomagam.

Zdaj, ko sem vam pomanjkanja pisanja in ne pisanja, bi vam rad povedal, da v celoti cenim vse pohvale, ki jih je vodnik prejela v preteklih letih. To je resnično vzpodbujanje morale in me veseli, da slišim, da se uporablja za dobro! :-) Hvala vam!

1.7. Zrcaljenje

To spletno mesto ste bolj kot dobrodošli, javno ali zasebno. Če javno ogledate spletno stran in želite, da se povezujem z njo z glavne strani, mi vrnite črto na beej@beej.us.

1.8. Opomba za prevajalce

Če želite prevedeti vodnik v drug jezik, napišite me na beej@beej.us in na glavno stran bom povezal vaš prevod. V prevod lahko dodate svoje ime in kontaktne podatke.

Upoštevajte omejitve licenc v razdelku Avtorske pravice in razdelek, spodaj.

Če želite, da gosti prevod, samo vprašajte. Prav tako se bom povezal z njim, če ga želite gostiti; v vsakem primeru je v redu.

Beejov vodnik za omrežno programiranje je Copyright © 2015 Brian “Beej Jorgensen”.

Določene izjeme za izvorno kodo in prevode, spodaj, je to delo pod licenco Creative Commons Priznanje avtorstva-Nekomercialno in brez izvedenih del 3.0. Če si želite ogledati kopijo te licence, obiščite http://creativecommons.org/licenses/by-nc-nd/3.0/ ali pošljite pismo Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, ZDA .

Ena posebna izjema od dela brez izvedenih del licence je naslednja: ta priročnik je lahko prosto preveden v katerikoli jezik, pod pogojem, da je prevod točen in da je priročnik ponatisnjen v celoti. Enake licenčne omejitve veljajo za prevod v zvezi s prvotnim vodnikom. Prevod lahko vključuje tudi ime in kontaktne podatke za prevajalca.

Izvorna koda C, predstavljena v tem dokumentu, se odobri v javni lasti in je popolnoma brez kakršnekoli omejitve licence.

Izobraževalcem se svobodno priporoča, da priporočijo ali pošljejo izvode tega priročnika svojim učencem.

Za več informacij se obrnite na beej@beej.us.

2. Kaj je vtičnica?

Ou, ves čas slišite govor o “vtičnicah”, in morda se sprašujete, kaj so točno. No, to so: način govoriti z drugimi programi z uporabo standardnih deskriptorjev datotek Unixa.

Kaj?

Ok – morda ste že slišali kakšno Unix stanje hacker, “Jeez, vse v Unixu je datoteka!” O tem se je morda pogovarjala dejstvo, da ko programi Unixa izvajajo kakršne koli I/O, to storijo z branjem ali pisanjem v deskriptorju datoteke. Descriptor datoteke je preprosto celo število, povezano z odprto datoteko. Toda (in tukaj je ulov), lahko ta datoteka omrežna povezava, FIFO, cev, terminal, resnična datoteka na disku ali skoraj nič drugega. Vse v Unixu je datoteka! Torej, ko želite komunicirati z drugim programom prek interneta, boste to storili prek deskriptorja datoteke, bolje ji verjite.

“Kje dobim opis datoteke za omrežno komunikacijo, gospod Smarty-Pants?” je verjetno zadnje vprašanje v mislih zdaj, vendar bom vseeno odgovoril: Kličete se v sistemski rutini socket (). Vrne opisni list vtičnice in komunicira prek njega s posebnimi klicnimi številkami za send() in recv() (man send, man recv).

“Ampak, hej!” Morda ste pravkar vznemirljivi. “Če gre za deskriptor datotek, zakaj v imenu Neptuna ne morem samo uporabljati običajnih read () in write () klicev za komunikacijo preko vtičnice?” Kratek odgovor je: “Lahko!” Daljši odgovor je: “Lahko, vendar send() in recv() ponuja veliko večji nadzor nad prenosom podatkov.”

Kaj je naslednje? Kaj pa to: obstajajo vse vrste vtičnic. Obstajajo DARPA internetni naslovi (internetne vtičnice), imena poti na lokalnem vozlišču (Unix Sockets), CCITT X.25 naslovi (X.25 vtičnice, ki jih lahko varno prezrete) in verjetno še veliko drugih, odvisno od tega, kateri okus Unixa uporabljate. Ta dokument obravnava samo prvo: internetne vtičnice.

2.1. Dve vrsti internetnih vtičnic

Kaj je to? Obstajata dve vrsti internetnih vtičnic? Ja. No, ne. Lažem. Obstaja več, vendar nisem hotel prestrašiti. Tukaj bom govoril samo o dveh vrstah. Razen tega stavka, kjer vam bom povedal, da so “Raw Sockets” tudi zelo močni in jih morate pogledati.

V redu, že. Kakšna sta dva tipa? Ena je “Stream Sockets”; Druga pa “Datagram Sockets”, ki se lahko v nadaljevanju imenujejo “SOCK_STREAM” in “SOCK_DGRAM”. Datagramove vtičnice se včasih imenujejo “priključne vtičnice”. (Čeprav jih lahko povežete () ‘d, če si res želite. Glejte connect() spodaj.)

Sokovi za pretok so zanesljivi dvosmerni povezani komunikacijski tokovi. Če v vtičnico izberete dva predmeta v vrstnem redu “1, 2″, bodo prišli v vrstnem redu “1, 2″ na nasprotni strani. Prav tako bodo brez napak. Tako sem prepričan, pravzaprav bodo brez napak, da bom samo prstom ušes in ušel la la la la, če kdorkoli poskuša zahtevati drugače.

Kaj uporablja vtičnice za pretok? No, morda ste že slišali za aplikacijo telnet, ja? Uporablja tokovne vtičnice. Vsi znaki, ki jih vnesete, morajo prispeti v isti vrstici, ki jih vnesete, kajne? Spletni brskalniki uporabljajo tudi protokol HTTP, ki za iskanje strani uporablja slikovne vtiče. Dejansko, če telnet pošljete na spletno mesto v pristanišču 80 in vnesete »GET / HTTP/1.0« in dvakrat pritisnete RETURN, bo HTML vrnil na vas!

Kako vtičnice za tokove dosežejo to visoko raven prenosa podatkov? Uporabljajo protokol, imenovan “Protokol za nadzor prenosa”, sicer znan kot “TCP” (glej RFC 793 za zelo podrobne informacije o TCP-ju). TCP poskrbi, da bodo vaši podatki zaporedoma zaprti in brez napak. Morda ste že slišali »TCP« kot boljšo polovico »TCP/IP«, kjer »IP« pomeni »internetni protokol« (glej RFC 791.). IP se ukvarja predvsem z internetnim usmerjanjem in na splošno ni odgovoren za celovitost podatkov.

Kul. Kaj pa vtičnice Datagram? Zakaj se imenujejo brez povezave? Kakšen je dogovor, tukaj? Zakaj so nezanesljivi? No, tukaj je nekaj dejstev: če pošljete datagram, lahko pride. Morda ne bo narobe. Če pride, bodo podatki v paketu brez napak.

Datagramove vtičnice uporabljajo tudi IP za usmerjanje, vendar ne uporabljajo TCP; Uporabljajo “protokol User Datagram” ali “UDP” (glej RFC 768.)

Zakaj so brez povezave? No, v bistvu, to je zato, ker vam ni treba vzdrževati odprto povezavo, kot delate s vtičnicami za pretok. Samo zgradite paket, na njej udarite glavo glave IP-ja s podatki o destinaciji in ga pošljite. Povezava ni potrebna. Običajno se uporabljajo, kadar stack TCP ni na voljo ali ko nekaj padajočih paketov tukaj in tam ne pomeni konca vesolja. Primeri aplikacij: tftp (trivialni protokol prenosa datotek, manjši brat na FTP), dhcpcd (odjemalec DHCP), igre z več igralci, pretakanje zvoka, videokonference itd.

“Počakajte trenutek!” tftp in dhcpcd se uporabljajo za prenos binarnih aplikacij z enega gostitelja na drugega! Podatkov ni mogoče izgubiti, če pričakujete, da bo aplikacija delovala, ko bo prišla! Kakšna je temna magija?”

No, moj človeški prijatelj, tftp in podobni programi imajo svoj protokol na vrhu UDP-ja. Na primer, protokol tftp pravi, da mora prejemnik za vsak paket, ki ga pošlje, pošiljati paket, ki pravi: “Imam ga!” (Paket “ACK”.) Če pošiljatelj prvotnega paketa ne dobi odgovora, recimo, pet sekund, bo paket preneslo, dokler končno ne dobi ACK. Ta potrditveni postopek je zelo pomemben pri izvajanju zanesljivih aplikacij SOCK_DGRAM.

Za nezanesljive aplikacije, kot so igre, avdio ali video, preprosto ignorirate padajoče pakete ali pa jih poskusite pametno nadomestiti. (Quake igralci bodo vedeli, kakšen učinek ima ta učinek s tehničnim izrazom: prekleto lag. Beseda “prekleti” v tem primeru predstavlja kakršno koli izredno napeto izgovor.)

Zakaj bi uporabili nezanesljiv osnovni protokol? Dva razloga: hitrost in hitrost. To je hitreje, da se ogenj in pozabite, kot je slediti temu, kar je prišlo varno in se prepričajte, da je v redu, in vse to. Če pošiljate klepetna sporočila, je TCP odličen; Če pošiljate 40 lokacijskih posodobitev na sekundo igralcev na svetu, morda ni tako pomembno, če se eden ali dva spustijo, UDP pa je dobra izbira.

2.2. Nizka stopnja nesmisel in omrežna teorija

Ker sem ravnokar omenil strjevanje protokolov, je čas, da se pogovorimo o tem, kako omrežja res delujejo, in pokazati nekaj primerov, kako so izdelani paketi SOCK_DGRAM. Praktično verjetno lahko preskočite ta razdelek. Vendar je dobro ozadje.

[Encapsulated Protocols Diagram]
Inkapsulacija podatkov.

Hej, otroci, čas je, da se naučimo o Data Encapsulation! To je zelo pomembno. To je tako pomembno, da se lahko samo naučite o tem, če se obrnite na tečaj omrežja tukaj v Chico State ;-). V bistvu piše: paket je rojen, paket je zavit (“enkapsuliran”) v glavi (in redko noga) s prvim protokolom (recimo, TFTP protokol), potem pa celotna stvar (vključen je TFTP glava) je znova zaokrožena z naslednjim protokolom (recimo, UDP), nato pa z naslednjim (IP), nato pa s končnim protokolom na strojni (fizični) sloj (recimo, Ethernet).

Kadar drug računalnik prejme paket, strojna oprema preusmeri glavo ethernet, jedro prekriva naslove IP in UDP, program TFTP preusmeri glavo TFTP in končno vsebuje podatke.

Sedaj lahko končno govorim o zloglasnem modelu Layered Network (aka “ISO/OSI”). Ta mrežni model opisuje sistem omrežnih funkcij, ki imajo veliko prednosti pred drugimi modeli. Na primer, lahko napišete programe, ki so popolnoma enaki brez skrbi, kako se podatki fizično prenašajo (serijski, tanki Ethernet, AUI, karkoli), ker programi na nižjih ravneh obravnavajo to za vas. Dejanska omrežna strojna oprema in topologija sta pregledna za programerja vtičnic.

Brez kakršnih koli nadaljnjih ado, bom predstavil plasti celotnega modela. Zapomni si to za izpite razreda omrežja:

  • Uporaba
  • Predstavitev
  • Seja
  • Prevoz
  • Omrežje
  • Podatkovna povezava
  • Fizično

Fizični sloj je strojna oprema (serijska, ethernet, itd.). Aplikacijski sloj je skoraj tako daleč od fizičnega sloja, kot si lahko predstavljate – to je kraj, kjer uporabniki interakcijo z omrežjem.

Zdaj je ta model tako splošen, da ga verjetno lahko uporabite kot vodnik za popravilo avtomobilov, če si res želel. Večplasten model, ki je bolj skladen z Unixom, je lahko:

  • Aplikacijski sloj (telnet, ftp, itd.)
  • Transportni sloj gostitelja-gostitelja (TCP, UDP)
  • Internetni sloj (IP in usmerjanje)
  • Omrežni dostopni sloj (Ethernet, wi-fi ali karkoli)

V tem trenutku lahko verjetno vidite, kako te plasti ustrezajo inkapsulaciji prvotnih podatkov.

Oglejte si, koliko dela pri izdelavi preprostega paketa? Jeez! V glave paketov morate vnašati tudi “cat“! Samo šalim se. Vse, kar morate storiti za vtičnice za pretok, je pošiljanje send(). Vse, kar morate storiti za vtičnike datagram, je pakiranje v paketu po metodi, ki jo izberete, in ga sendto(). Jedro gradi prometni sloj in internetni sloj za vas in strojna oprema omogoča dostop do omrežnega dostopa. Ah, sodobna tehnologija.

Torej konča našo kratko prepletanje v teorijo omrežja. O, da, pozabil sem ti povedati vse, kar želim povedati o poti: nič! Tako je, sploh ne bom govoril o tem. Usmerjevalnik usmerja paket v naslov IP, se posvetuje z usmerjevalno tabelo, blah blah blah. Preverite IP RFC, če vam res skrbi. Če se o tem ne boste nikoli naučili, boste živeli.

3. IP-naslovi, structs in obdelava podatkov

Tukaj je del igre, kjer se pogovorimo o kodi za spremembo.

Najprej pa pogovorimo o več ne-kode! Juhu! Najprej bi rad govoril o IP naslovih in pristaniščih le za tad, zato smo to razvrstili. Nato bomo govorili o tem, kako API vtičnic shrani in manipulira z naslovi IP in drugimi podatki.

3.1. IP naslovi, različice 4 in 6

V dobrih starih časih, ko se je Ben Kenobi še vedno imenoval Obi Wan Kenobi, je bil čudovit mrežni sistem, imenovan Internet Protocol Version 4, imenovana tudi IPv4. Imela so naslove, sestavljene iz štirih bajtov (A.K.A., štiri “okteti”) in so bili pogosto zapisani v obliki “pike in številke”, tako: 192.0.2.111.

Verjetno ste jo videli.

Pravzaprav, od tega pisanja praktično vsako spletno mesto na internetu uporablja IPv4.

Vsi, vključno z Obi Wanom, so bili veseli. Stvari so bile odlične, dokler nekateri naysayer z imenom Vint Cerf niso vsi opozorili, da nam bodo zmanjkalo naslovov IPv4!

(Poleg opozoril vsakogar o Prihajajočem IPv4 Apocalypse Of Doom In Gloom, Vint Cerf je tudi znana po tem, da je oče interneta. Torej, resnično nisem sposoben drugič presoditi njegove sodbe.)

Zmanjkuje naslovov? Kako je to mogoče? Mislim, v naslovu IPv4 je 32-bitni IP-naslov. Imamo resnično milijarde računalnikov tam zunaj?

Ja.

Tudi v začetku, ko je bilo le nekaj računalnikov in vsi mislili, da je bil milijarda nemogoče veliko število, so bile nekatere velike organizacije velikodušno dodeljene milijone naslovov IP za lastno uporabo. (Kot so Xerox, MIT, Ford, HP, IBM, GE, AT & T in nekaj majhnih podjetij, imenovanih Apple, da navedemo nekaj.)

Dejansko bi, če ne bi bilo več ukrepov za zaustavitev, že zdavnaj zmanjkalo.

Toda zdaj živimo v dobi, ko govorimo o vsakem človeku, ki ima naslov IP, vsak računalnik, vsak kalkulator, vsak telefon, vsak parkirni meter in (zakaj ne) vsak psiček.

Tako se je rodil IPv6. Ker je Vint Cerf verjetno nesmrten (čeprav bi moral njegova fizična oblika preiti, nebesa prepovedujejo, verjetno že obstaja kot nekakšen hiper-inteligentni program ELIZA v globinah interneta2), nihče ne želi, da bi ga slišal ponovno spet “sem ti to rekel”, če v naslednji različici internetnega protokola nimamo dovolj naslovov.

Kaj vam to predlaga?

Da potrebujemo veliko več naslovov. Da ne potrebujemo le dvakrat toliko naslovov, ne milijarde krat več, ne tisoč milijard večkrat, vendar 79 milijonov milijard dolarjev toliko možnih naslovov! To jim bo pokazalo!

Pravite: “Beej, ali je to res? Imam vse razloge, zakaj ne verjamem velikih številk.” No, razlika med 32 bitov in 128 bitov morda ne zveni kot veliko; To je samo 96 več bitov, kajne? Vendar ne pozabite, da govorimo o pooblastilih tukaj: 32 bitov predstavlja približno 4 milijarde številk (232), medtem ko 128 bitov predstavlja okoli 340 milijard trilijonov trilijonovih številk (za realno, 2128). To je kot milijon IPv4 Internets za vsako posamezno zvezdo v vesolju.

Pozabite tudi na to piko in številke lookup IPv4, Zdaj imamo heksadecimalno predstavitev, pri čemer je vsak dvobajtni del, ki ga ločuje debelo črevo, takole: 2001: 0db8: c9d2: aee5: 73e3: 934a: a5ae: 9551.

To ni vse! Veliko krat, boste imeli IP naslov z veliko ničle v njem, in jih lahko stisnete med dvema kolonama. In lahko zapustite vodilne ničle za vsak par bajtov. Na primer, vsak od teh parov naslovov je enakovreden:

2001:0db8:c9d2:0012:0000:0000:0000:0051
2001:db8:c9d2:12::51

2001:0db8:ab00:0000:0000:0000:0000:0000
2001:db8:ab00::

0000:0000:0000:0000:0000:0000:0000:0001
::1

Naslov :: 1 je povratni naslov. Vedno pomeni “ta stroj, na katerem trenutno deluje”. V IPv4 je naslov zanke 127.0.0.1.

Nazadnje, obstaja tudi način IPv4-združljivosti za naslove IPv6, na katere se morda naletite. Če želite na primer predstaviti naslov IPv4 192.0.2.33 kot naslov IPv6, uporabite naslednji zapis: “::ffff: 192.0.2.33″.

Govorimo resno.

Pravzaprav je tako resno zabavno, da so ustvarjalci IPv6 precej razbremenili trilijone in trilionov naslovov za rezervirano uporabo, vendar imamo toliko, odkrito, kdo celo šteje več? Za vsakega moškega, ženske, otroka, mladička in števca parkirnih mest je veliko na vsem planetu v galaksiji. In verjemi mi, vsak planet v galaksiji ima parkirne števce. Veš, da je res.

3.1.1. Podmnožice

Iz organizacijskih razlogov je včasih primerno razglasiti, da je “ta prvi del tega naslova IP prek tega bita omrežni del IP-naslova, preostanek pa je gostiteljski del.

Na primer, z IPv4 boste morda imeli 192.0.2.12 in lahko bi rekli, da so prvi trije bajti omrežje, zadnji gostitelj pa je bil gostitelj. Ali pa postavite drug način, govorimo o gostitelju 12 v omrežju 192.0.2.0 (poglejte, kako izklopimo bajt, ki je bil gostitelj.)

In zdaj za zastarele informacije! Pripravljen? V Ancient Timesih so obstajali “razredi” podomrežij, kjer je bil prvi, dva ali trije bajt naslova omrežni del. Če ste imeli dovolj sreče, da imate en bajt za omrežje in tri za gostitelja, lahko imate v vašem omrežju 24 bitnih gostiteljev (16 milijonov). To je bila mreža “razreda A”. Na nasprotni strani je bil “razred C”, s tremi bajti omrežja in en bajt gostitelja (256 gostiteljev, minus par, ki je bil rezerviran.)

Tako kot lahko vidite, je bilo le nekaj razreda As, ogromen kup razreda C in nekaj razredov Bs na sredini.

Omrežni del IP-naslova opisuje nekaj, kar imenujemo omrežna maska, ki vam je bitno-IN z naslovom IP, da iz njega pridobite omrežno številko. Netmaskat običajno izgleda nekaj podobnega kot 255.255.255.0. (Npr. S to masko, če je vaš IP 192.0.2.12, potem je vaše omrežje 192.0.2.12 in 255.255.255.0, ki daje 192.0.2.0.)

Na žalost se je izkazalo, da to ni bilo dovolj natančno za morebitne potrebe interneta; Zelo hitro smo zmanjkalo omrežij razreda C, in zagotovo smo bili iz razreda As, zato se ne trudite vprašati. Če želite to odpraviti, so pooblastila, ki omogočajo, da je omrežna maska ​​poljubno število bitov, ne le 8, 16 ali 24. Torej, morda imate mrežno masko, recimo 255.255.255.252, kar je 30 bitov omrežja in 2 bita gostitelja, ki omogočata štiri gostitelje v omrežju. (Upoštevajte, da je omrežna maska ​​VSAKO, 1-bita, ki ji sledi kup 0-bitov.)

Ampak to je malo grobo, da uporabite velik niz številk, kot so 255.192.0.0 kot mrežna maska. Prvič, ljudje nimajo intuitivne ideje o tem, koliko bitov je, in drugič, res ni kompakten. Tako je prišel novi slog in je veliko lepše. Po naslovu IP ste postavili poševnico, nato pa sledite številu omrežnih bitov v decimalni skupini. Všeč mi je: 192.0.2.12/30.

Ali, za IPv6, nekaj takega: 2001:db8::/32 ali 2001:db8:5413:4028::9db9/64.

3.1.2. Pristaniške številke

Če se boste vljudno spomnili, sem vas prej predstavil z modelom Layered Network, ki je imel internetni sloj (IP) ločen od transportnega sloja med gostiteljem in gostiteljem (TCP in UDP). Pojdi na hitrost na to pred naslednjim odstavkom.

Izkazalo se je, da poleg IP-naslova, ki ga uporablja IP-plast, obstaja še en naslov, ki ga uporabljajo TCP (vtičnice za pretok) in, po naključju, UDP (datagramove). To je številka vrat. To je 16-bitna številka, ki je kot lokalni naslov povezave.

Pomislite na naslov IP kot ulico naslova hotela in številko vrat kot številko sobe. To je primerna analogija; morda kasneje bom prišel do tistega, ki vključuje avtomobilsko industrijo.

Recimo, da želite imeti računalnik, ki obravnava dohodno pošto in spletne storitve – kako razlikujete med obema na računalniku z enim samim naslovom IP?

No, različne storitve na internetu imajo različne znane številke vrat. Vse jih lahko vidite na seznamu velikih vrat IANA ali, če ste v polju Unix, v datoteki /etc/services. HTTP (spletna) je pristanišče 80, telnet je pristanišče 23, SMTP je pristanišče 25, igra DOOM uporabljena vrata 666 itd. In tako naprej. Pristanišča pod 1024 se pogosto štejejo za posebna in običajno zahtevajo posebne privilegije OS za uporabo.

In to je približno to!

3.2. Byte Order

Z redom kraljestva! Dva bajtna naročila, v nadaljevanju imenovana Lame in Magnificent!

Šalim se, vendar je eden res boljši od drugega. :-)

V resnici ni preprostega načina za to, da bi to povedal, zato ga bom samo utripal: računalnik je morda shranil bajt v obratnem vrstnem redu za hrbet. Vem! Nihče te ni hotel povedati.

Stvar je v tem, da so vsi v svetovnem spletu splošno strinjali, da če želite predstaviti dvobajtno šestnajstiško številko, recimo b34f, jo shranite v dva zaporedna bajta b3, ki ji sledi 4f. Ima smisel in, kot vam je povedal Wilford Brimley, je to prava stvar. Ta številka, shranjena s prvim velikim koncem, se imenuje Big-Endian.

Na žalost je nekaj računalnikov, razpršenih tu in tam po vsem svetu, in sicer z vsemi procesorji, združljivimi s procesorjem Intel ali Intel, shranjene obrnjene bajtove, zato b34f shranite v pomnilnik kot zaporedni bajt 4f, ki mu sledi b3. Ta način shranjevanja se imenuje Little-Endian.

Ampak počakaj, še nisem končal z terminologijo še! Bolj pameten Big-Endian se imenuje tudi Network Byte Order, ker je to vrsta vrst omrežja, kot so.

Računalnik shrani številke v Host Byte Order. Če gre za Intel 80×86, je Host Byte Order. Če je Motorola 68k, je Host Byte Order Big-Endian. Če je PowerPC, je Host Byte Order… torej, odvisno od tega!

Veliko krat, ko izdelujete pakete ali napolnite podatkovne strukture, boste morali zagotoviti, da sta vaši dve- in štirideset-številčni številki v omrežni vrstni red. Toda kako lahko to storite, če ne poznate domačega Byte Reda?

Dobre novice! Prepričajte se, da je ukaz gostiteljskega bajta neveljaven, vedno pa jo uporabite za funkcijo, da jo nastavite na vrstni red omrežnega bajta. Funkcija bo naredila čarobno pretvorbo, če bo to storila, in tako je vaša koda prenosljiva na stroje različnih konec.

Vse pravice. Obstajata dve vrsti številk, ki jih lahko pretvorite: kratki (dva bajta) in dolga (štirje bajti). Te funkcije delujejo tudi za nepodpisane različice. Recimo, da želite kratek pretvoriti iz naslova Host Byte v omrežni vrstni red. Začnite s »h« za »gostitelj«, sledite ji z »do«, potem »n« za »omrežje« in »s« za »kratko«: h-to-ns ali htons () (preberite: »Host do Network Short “).

Skoraj preprosto je…

Lahko uporabite vsako kombinacijo “n”, “h”, “s” in “l”, ki jo želite, ne računate pa res neumnih. Npr. Na stolpcu ni stolp () (“Kratka do dolga gostiteljica”) – ne na tej strani. Vendar obstajajo:

 

htons() host to network short
htonl() host to network long
ntohs() network to host short
ntohl() network to host long

 

V bistvu želite pretvoriti številke v omrežni vrstni red, preden gredo ven na žico, in jih pretvorite v Host Byte Order, ko pridejo v žico.

Ne vem za 64-bitno različico, oprosti. Če želite narediti plavajočo točko, si oglejte razdelek Serialization, daleč pod njim.

Predpostavimo, da so številke v tem dokumentu v naročilu Host Byte, če ne rečem drugače.

3.3. Strukture

No, končno smo tukaj. Čas je, da govorimo o programiranju. V tem razdelku bom zajemal različne vrste podatkov, ki jih uporablja vmesnik vtičnic, saj so nekateri med njimi resnično medvedi, ki jih je treba razumeti.

Najprej enostavno: deskriptor vtičnice. Deskriptor socket je naslednji tip:

int

Samo redni int.

Stvari se čudno od tukaj, zato samo preberite in nosite z mano.

My First StructTM – struct addrinfo. Ta struktura je najnovejši izum in se uporablja za pripravo kontaktnih struktur za nadaljnjo uporabo. Uporablja se tudi v lookups ime gostitelja in lookups ime storitve. To bo bolj smiselno kasneje, ko bomo prišli do dejanske uporabe, vendar samo za zdaj vedite, da je to ena od prvih stvari, ki jih boste poklicali pri vzpostavljanju povezave.

struct addrinfo {
    int              ai_flags;     // AI_PASSIVE, AI_CANONNAME, etc.
    int              ai_family;    // AF_INET, AF_INET6, AF_UNSPEC
    int              ai_socktype;  // SOCK_STREAM, SOCK_DGRAM
    int              ai_protocol;  // use 0 for "any"
    size_t           ai_addrlen;   // size of ai_addr in bytes
    struct sockaddr *ai_addr;      // struct sockaddr_in or _in6
    char            *ai_canonname; // full canonical hostname

    struct addrinfo *ai_next;      // linked list, next node
};

To strukturo boste naložili malo in nato pokličite getaddrinfo(). Vrnil bo kazalec na nov povezani seznam teh struktur, ki se izpolnijo z vsemi potrebami, ki jih potrebujete.

Lahko ga prisilite, da uporabite IPv4 ali IPv6 v polju ai_family ali pa pustite, da je AF_UNSPEC uporabil karkoli. To je kul, ker je vaša koda lahko različica agnostika IP.

Upoštevajte, da je to povezani seznam: ai_next točke na naslednjem elementu – tam bi lahko imeli več rezultatov. Uporabil bi prvi rezultat, ki je deloval, vendar imate morda različne poslovne potrebe; Ne vem vse, človek!

Videli boste, da je polje ai_addr v struct addrinfo kazalec na struct sockaddr. To je, če začnemo pridobivati podrobnosti o tem, kaj je v strukturi IP naslovov.

Morda vam ponavadi ni treba pisati v te strukture; pogosto, klic do getaddrinfo(), da izpolnite vaš struct addrinfo za vas, je vse, kar potrebujete. Vendar boste morali v teh strukturah pregledati, da bi se vrednosti izločile, zato jih predstavljam tukaj.

(Prav tako je bila vsa koda, napisana pred izumljanjem strukture addrinfo, vse te stvari pakirali z roko, zato boste v naravi videli veliko kode IPv4, ki to počne ravno. V starih različicah tega vodnika in tako naprej.)

Nekateri strukturi so IPv4, nekateri so IPv6, nekateri pa sta oba. Zapisal bom, katere so kaj.

Kakorkoli že, struct sockaddr ima informacije o naslovih vtičnic za mnoge vrste vtičnic.

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
}; 

sa_family je lahko več stvari, vendar bo za vse, kar počnemo v tem dokumentu, AF_INET (IPv4) ali AF_INET6 (IPv6). sa_data vsebuje ciljni naslov in številko vrat za vtičnico. To je precej grozljivo, saj ne želite, da bi potegnili naslov v sa_data z roko.

Za obravnavo struct sockaddr so programerji ustvarili vzporedno strukturo: struct sockaddr_in (“v” za “internet”), ki se uporablja z IPv4.

In to je pomemben bit: kazalec na struct sockaddr_in se lahko prenese v kazalec na struct sockaddr in obratno. Torej, čeprav connect () hoče strukturo sockaddr *, lahko še vedno uporabite struct sockaddr_in in jo oddate v zadnji minuti!

// (IPv4 only--see struct sockaddr_in6 for IPv6)

struct sockaddr_in {
    short int          sin_family;  // Address family, AF_INET
    unsigned short int sin_port;    // Port number
    struct in_addr     sin_addr;    // Internet address
    unsigned char      sin_zero[8]; // Same size as struct sockaddr
};

Ta struktura olajša referenco elementov naslova vtičnice. Upoštevajte, da je treba sin_zero (ki je vključen, da se struktura podloži na dolžino struct sockaddr) nastaviti na vse ničle s funkcijo memset(). Prav tako opazite, da sin_family ustreza sa_family v struct sockaddr in mora biti nastavljen na “AF_INET”. Končno, sin_port mora biti v omrežnem vrstnem redu (z uporabo htons()!)

Krenimo še globlje! Vidiš, da je polje sin_addr struct in_addr. Kaj je to? No, ne biti preveč dramatičen, ampak to je eden od najstrašjih sindikatov vseh časov:

// (IPv4 only--see struct in6_addr for IPv6)

// Internet address (struktura iz zgodovinskih razlogov)
struct in_addr {
    uint32_t s_addr; // that's a 32-bit int (4 bytes)
};

Vau! No, nekoč je bila sindikat, zdaj pa se zdi, da ti dnevi niso več. Dobro osvoboditev. Torej, če ste prijavili ina, da je tipa struct sockaddr_in, se ina.sin_addr.s_addr sklicuje na 4-bajtni IP-naslov (v omrežnem vrstnem redu). Upoštevajte, da četudi vaš sistem še vedno uporablja Bog-grozno zvezo za struct in_addr, lahko še vedno sklicujete na 4-bajtni IP-naslov na popolnoma enak način, kot sem storil zgoraj (to zaradi #defines.)

Kaj pa IPv6? Za to obstajajo podobne struct:

// (IPv6 only--see struct sockaddr_in and struct in_addr for IPv4)

struct sockaddr_in6 {
    u_int16_t       sin6_family;   // naslov družine, AF_INET6
    u_int16_t       sin6_port;     // številka vrat, Network Byte Order
    u_int32_t       sin6_flowinfo; // Podatki o toku IPv6
    struct in6_addr sin6_addr;     // Naslov IPv6
    u_int32_t       sin6_scope_id; // Področje ID
};

struct in6_addr {
    unsigned char   s6_addr[16];   // Naslov IPv6
};

Upoštevajte, da ima IPv6 naslov IPv6 in številko vrat, tako kot IPv4 ima naslov IPv4 in številko vrat.

Upoštevajte tudi, da ne bom govoril o pretočnih podatkih IPv6 ali Field Field ID za trenutek… to je le začetni vodnik. :-)

Nenazadnje, tu je še ena preprosta struktura, struct sockaddr_storage, ki je zasnovana tako, da je dovolj velika, da drži strukture IPv4 in IPv6. (Glej, za nekatere klice včasih ne veste vnaprej, če bo izpolnil vaš struct sockaddr z naslovom IPv4 ali IPv6. Torej, preideš v to vzporedno strukturo, zelo podobna strukturi sockaddr, razen večje, do vrste, ki jo potrebujete:

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // vse to je oblazinjenje, specifično izvajanje, ga ignoriraj:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};

Pomembno je, da si lahko v polju ss_family ogledate družino naslovov, da preverite, ali je to AF_INET ali AF_INET6 (za IPv4 ali IPv6). Potem ga lahko oddate v struct sockaddr_in ali struct sockaddr_in6, če želite.

3.4. Naslov IP, del Deux

Na srečo imate na voljo številne funkcije, ki vam omogočajo, da manipulirate z IP-adresami. Ni potrebe, da jih ugotovite z roko in jih dolgo dolgo z << operaterjem.

Najprej recimo, da imate struct sockaddr_in ina in imate naslov IP “10.12.110.57” ali “2001: db8: 63b3: 1 :: 3490″, ki ga želite shraniti vanj. Funkcija, ki jo želite uporabiti, inet_pton () pretvori IP naslov v zapisu številk in pik v strukturo in_addr ali strukturo in6_addr, odvisno od tega, ali določite AF_INET ali AF_INET6. (“Pton” pomeni “predstavitev v omrežje” – lahko ga imenujemo “tiskanje v omrežje”, če je to lažje zapomniti.) Pretvorbo lahko naredite na naslednji način:

struct sockaddr_in sa; // IPv4
struct sockaddr_in6 sa6; // IPv6

inet_pton(AF_INET, "10.12.110.57", &(sa.sin_addr)); // IPv4
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); // IPv6

(Hitro opomba: stari način izvajanja stvari je uporabil funkcijo, imenovano inet_addr () ali drugo funkcijo, imenovano inet_aton (), ki so zdaj zastarela in ne delujejo z IPv6.)

Zdaj zgornji del kode ni zelo robusten, ker ni preverjanja napak. Glej, inet_pton() vrne -1 ob napaki ali 0, če je naslov zmeden. Zato preverite, ali je rezultat večji od 0 pred uporabo!

V redu, zdaj lahko pretvorite niza IP naslovov v njihove binarne predstavitve. Kaj pa obratno? Kaj, če imate strukturo in_addr in jo želite natisniti v zapisu številk in pik? (Ali pa strukturo in6_addr, ki jo želite v zapisu »heks in kolone«.) V tem primeru boste želeli uporabiti funkcijo inet_ntop() (“ntop” pomeni “omrežje za predstavitev” – lahko ga poimenujte “omrežje za tiskanje”, če je to lažje zapomniti), na primer:

// IPv4:

char ip4[INET_ADDRSTRLEN];  // prostor, da držite niz IPv4
struct sockaddr_in sa;      // se pretvarjam, da je to naloženo z nekaj

inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);

printf("The IPv4 address is: %s\n", ip4);


// IPv6:

char ip6[INET6_ADDRSTRLEN]; // prostor, da držite niz IPv6
struct sockaddr_in6 sa6;    // se pretvarjam, da je to naloženo z nekaj

inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN);

printf("The address is: %s\n", ip6);

Ko jo pokličete, boste posredovali vrsto naslova (IPv4 ali IPv6), naslov, kazalec na niz, ki naj ima rezultat, in največjo dolžino tega niza. (Dva makra prikladno držita velikost niza, ki jo boste morali imeti največji naslov IPv4 ali IPv6: INET_ADDRSTRLEN in INET6_ADDRSTRLEN.)

(Druga hitra opomba je še enkrat omeniti stari način opravljanja stvari: zgodovinska funkcija za to konverzijo je bila imenovana inet_ntoa(). Prav tako je zastarela in ne bo delovala z IPv6.)

Nazadnje, te funkcije delujejo samo s številskimi IP-adresami – ne bodo storile nobenega imenskega imenskega imena DNS-ja v imenu gostitelja, na primer “www.example.com”. Za to boste uporabili getaddrinfo(), kot boste videli kasneje.

3.4.1. Zasebna (ali prekinjena) omrežja

Veliko krajev ima požarni zid, ki skriva omrežje od preostalega sveta za lastno zaščito. In pogosto, požarni zid prevaja “notranje” IP naslove na “zunanji” (da vsi drugi na svetu vejo) naslove IP z uporabo procesa, imenovanega Prevajanje omrežnih naslovov ali NAT.

Ste še živčni? “Kje gre s temi čudnimi stvarmi?”

No, sprostite se in kupite brezalkoholno (ali alkoholno) pijačo, ker kot začetnik vam ni treba skrbeti samo za NAT, saj je to storjeno za vas pregledno. Ampak hotel sem govoriti o omrežju za požarnim zidom, če bi začeli zbirati z omrežnimi številkami, ki ste jih videli.

Na primer, doma imam požarni zid. Imam dva statična naslova IPv4, ki mi jih je dodelila družba DSL, vendar imam v omrežju sedem računalnikov. Kako je to mogoče? Dva računalnika ne moreta deliti istega naslova IP, sicer pa podatki ne vedo, kateremu je treba iti!

Odgovor je: nimajo enakih naslovov IP. So v zasebni mreži s 24 milijoni IP naslovov, ki so mu dodeljeni. Vsi so samo zame. No, vse zame, kar zadeva kogarkoli drugega. Tukaj se dogaja:

Če se prijavim v oddaljeni računalnik, mi pove, da sem prijavljen s 192.0.2.33, kar je javni IP-naslov, ki mi ga je ponudil ponudnik internetnih storitev. Ampak, če vprašam svoj lokalni računalnik, kakšen je njegov IP naslov, piše 10.0.0.5. Kdo prevaja naslov IP od enega do drugega? Tako je, požarni zid! To počne NAT!

10.x.x.x je eno od redkih rezerviranih omrežij, ki jih je mogoče uporabiti le v popolnoma ločenih omrežjih ali v omrežjih, ki so za požarnim zidom. Podrobnosti o tem, katere številke zasebnega omrežja so na voljo za uporabo, so opisane v RFC 1918, vendar je nekaj običajnih, ki jih boste videli, 10.x.x.x in 192.168.x.x, kjer je x 0-255. Manj pogosti je 172.y.x.x, kjer y poteka med 16 in 31.

Omrežja za požarnim zidom NATing ni treba biti na enem od teh rezerviranih omrežij, vendar so pogosto.

(Zabavno dejstvo! Moj zunanji IP-naslov ni res 192.0.2.33. Omrežje 192.0.2.x je rezervirano za “realne” IP-naslove, ki se uporabljajo v dokumentaciji, prav tako kot ta vodnik!)

IPv6 ima tudi zasebna omrežja, v določenem smislu. Začeli se bodo s fdxx: (ali morda v prihodnosti fcXX :), kot je navedeno v RFC 4193. NAT in IPv6 se običajno ne mešata (razen če delate IPv6 v IPv4 prehoda, ki presega obseg Ta dokument) – v teoriji boste imeli toliko naslovov, da vam ne bo treba več uporabljati NAT-a. Toda če želite dodeliti naslove sami v omrežju, ki ne bo usmerjal zunaj, je to, kako to storiti.

4. Skakanje iz IPv4 v IPv6

Ampak samo želim vedeti, kaj spremeniti v svoji kodi, da bi prišel z IPv6! Povej mi zdaj!

V redu! V redu!

Skoraj vse, kar sem tukaj, je nekaj, kar sem prešel, zgoraj, toda kratka različica za nestrpno. (Seveda je več kot to, toda to velja za vodnik.)

  1. Najprej poskusite uporabiti getaddrinfo(), da dobite vse strukture sockaddr info, namesto da pakete strukture ročno. To vam bo obdržalo različico agnostika IP in odpravilo številne nadaljnje korake.
  2. Vsako mesto, za katerega menite, da ste težko kodirali vse, kar je povezano z različico IP-ja, poskusite vključiti v funkcijo pomočnika.
  3. Spremeni AF_INET na AF_INET6.
  4. Spremenite PF_INET na PF_INET6
  5. Spremenite INADDR_ANY dodelitve v naloge in6addr_any, ki so nekoliko drugačne:
    struct sockaddr_in sa;
    struct sockaddr_in6 sa6;
    
    sa.sin_addr.s_addr = INADDR_ANY;  // uporabite svoj naslov IPv4
    sa6.sin6_addr = in6addr_any; // uporabite svoj naslov IPv6

    Tudi vrednost IN6ADDR_ANY_INIT se lahko uporabi kot inicializator, ko je deklarirana struktura in6_addr, tako:

    struct in6_addr ia6 = IN6ADDR_ANY_INIT;
  6. Namesto strukture sockaddr_in uporabite struct sockaddr_in6, seveda dodajte “6” poljam, kot je ustrezno (glej strukture, zgoraj). Ni polja sin6_zero.
  7. Namesto strukture in_addr uporabite struct in6_addr, seveda dodajte »6« na polja, kot je primerno (glej strukture, zgoraj).
  8. Namesto inet_aton() ali inet_addr() uporabite inet_pton().
  9. Namesto inet_ntoa() uporabite inet_ntop().
  10. Namesto gethostbyname() uporabite superior getaddrinfo().
  11. Namesto gethostbyaddr() uporabite vrhunsko getnameinfo() (čeprav gethostbyaddr() lahko še vedno deluje z IPv6).
  12. INADDR_BROADCAST ne deluje več. Namesto tega uporabite IPv6 multicast.

Et voila!

5. Sistemske klice ali prsi

To je odsek, v katerem se vpišemo v sistemske klice (in druge knjižnične klice), ki vam omogočajo dostop do omrežnih funkcij okvira Unix ali katerega koli polja, ki podpira vtičnice API za to zadevo (BSD, Windows, Linux, Mac, Kaj imate.) Ko pokličete eno od teh funkcij, jedro prevzame in vse delo opravi samodejno.

Mesto, kjer se večina ljudi zaljubljuje okoli, je, na kakšen naročilo je treba omeniti. V tem se man strani ne uporabljajo, kot ste verjetno odkrili. No, za pomoč pri tej grozljivi situaciji sem poskušal določiti sistemske klice v naslednjih razdelkih v natančno (približno) enakem vrstnem redu, ki ga boste morali poklicati v svojih programih.

To, skupaj z nekaj kosov vzorčne kode tukaj in tam, nekaj mleka in piškotov (ki se bojim, da se boste morali oskrbeti), in nekaj surovega črevesja in poguma, v internetu pa boste obkrožili podatke, kot je Sin Jon Postela!

(Upoštevajte, da številni odlomki kode spodaj ne vključujejo potrebnega preverjanja napak in zelo pogosto domnevajo, da rezultat iz klicev v getaddrinfo () uspe in vrne veljaven vnos na povezanem seznamu. Oba od teh primerov sta pravilno obravnavana Vendar v samostojnih programih, zato jih uporabite kot model.)

5.1. getaddrinfo() – Pripravite se na začetek!

To je prava delovna sila funkcije z veliko možnosti, vendar je uporaba dejansko precej preprosta. Pomaga pri nastavitvi struktur, ki jih potrebujete kasneje.

Droben zgodovin: zgodilo se je, da bi uporabili funkcijo, imenovano gethostbyname(), da bi naredili pregledovanje DNS-jev. Nato boste ročno naložili te informacije v strukturo sockaddr_in in jo uporabili v svojih klicih.

To ni več potrebno, hvaležen. (Prav tako ni zaželeno, če želite napisati kodo, ki deluje tako za IPv4 kot za IPv6!) V teh sodobnih časih imate zdaj funkcijo getaddrinfo(), ki za vas opravlja vse vrste dobrih stvari, vključno z DNS-ji in imenskimi poizvedbami , In izpolnite vse strukite, ki jih potrebujete, poleg tega!

Oglejmo si!

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *node,     // e.g. "www.example.com" or IP
                const char *service,  // e.g. "http" or port number
                const struct addrinfo *hints,
                struct addrinfo **res);

To funkcijo dajte trije vhodnim parametrom in vam daje kazalec na povezani seznam, res, rezultatov.

Parameter node je ime gostitelja za povezavo ali naslov IP.

Naslednja je service parametrov, ki je lahko številka vrat, na primer »80« ali ime določene storitve (na seznamu vrat IANA ali datoteki /etc/services na vašem Unix računalniku), na primer “http” ali “http”, ftp “ali” telnet “ali” smtp “ali karkoli.

Nazadnje, parameter za hints opozarja na struct addrinfo, ki ste ga že izpolnjevali z ustreznimi informacijami.

Tukaj je vzorec klic, če ste strežnik, ki želi poslušati na IP naslov vašega gostitelja, vrata 3490. Upoštevajte, da to dejansko ne poslušate ali nastavite omrežja; zgolj postavlja strukture, ki jih bomo kasneje uporabljali:

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // bo pokazal rezultate

memset(&hints, 0, sizeof hints); // poskrbite, da je struktura prazna
hints.ai_family = AF_UNSPEC;     // ne skrbi IPv4 ali IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
hints.ai_flags = AI_PASSIVE;     // izpolnite moj IP za mene

if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    exit(1);
}

// servinfo zdaj prikaže povezani seznam 1 ali več struct addrinfos

// ... storite vse, dokler ne potrebujete servinfo več ....

freeaddrinfo(servinfo); // free the linked-list

Upoštevajte, da sem nastavil ai_family družbo AF_UNSPEC, s tem pa rekel, da me ne zanima, če uporabljamo IPv4 ali IPv6. Lahko ga nastavite na AF_INET ali AF_INET6, če želite eno ali drugo posebej.

Tudi tam boste videli zastavo AI_PASSIVE; to pove getaddrinfo (), da dodeli naslov lokalnega gostitelja na vtičnice. To je lepo, ker potem vam ni treba strogo kode. (Ali lahko določite svoj naslov kot prvi parameter getaddrinfo (), kjer trenutno imam NULL, tam zgoraj.)

Potem kličemo. Če pride do napake (getaddrinfo () vrne ničlo), ga lahko natisnemo s funkcijo gai_strerror(), kot vidite. Kljub temu, če vse deluje pravilno, bo servinfo pokazal povezani seznam struct addrinfos, od katerih vsak vsebuje nekakšno strukturo sockaddr, ki jo lahko pozneje uporabimo! Čudovito!

Nazadnje, ko smo na koncu vsi naredili s povezanim seznamom, ki se dobi goaddrinfo(), ki je tako milostno dodeljena za nas, jo lahko (in naj) izpustimo s klicem na freeaddrinfo().

Tukaj je vzorec klic, če ste stranka, ki se želi povezati z določenim strežnikom, recimo “port 3490 www.example.net”. Tudi to se dejansko ne poveže, vendar pa postavlja strukture, ki jih bomo kasneje uporabljali:

int status;
struct addrinfo hints;
struct addrinfo *servinfo;  // bo pokazal rezultate

memset(&hints, 0, sizeof hints); // poskrbite, da je struct prazna
hints.ai_family = AF_UNSPEC;     // ne skrbi IPv4 ali IPv6
hints.ai_socktype = SOCK_STREAM; // TCP stream sockets

// se pripravite na povezavo
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);

// servinfo zdaj kaže na povezani seznam 1 ali več struct addrinfos

// etc.

Stalno govorim, da je servinfo povezan seznam z vsemi vrstami naslovnih informacij. Napišite hiter program za prikaz teh informacij. Ta kratek program bo natisnil naslove IP za katerikoli gostitelj, ki ga navedete v ukazni vrstici:

/*
** showip.c -- show IP addresses for a host given on the command line
*/

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>

int main(int argc, char *argv[])
{
    struct addrinfo hints, *res, *p;
    int status;
    char ipstr[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr,"usage: showip hostname\n");
        return 1;
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // AF_INET or AF_INET6 to force version
    hints.ai_socktype = SOCK_STREAM;

    if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
        return 2;
    }

    printf("IP addresses for %s:\n\n", argv[1]);

    for(p = res;p != NULL; p = p->ai_next) {
        void *addr;
        char *ipver;

        // poiščite kazalec na naslov sam,
        // različna polja v IPv4 in IPv6:
        if (p->ai_family == AF_INET) { // IPv4
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
            addr = &(ipv4->sin_addr);
            ipver = "IPv4";
        } else { // IPv6
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr = &(ipv6->sin6_addr);
            ipver = "IPv6";
        }

        // pretvorite IP v niz in ga natisnite:
        inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
        printf("  %s: %s\n", ipver, ipstr);
    }

    freeaddrinfo(res); // free the linked list

    return 0;
}

Kot vidite, koda pokliče getaddrinfo() na vse, kar vmesnik prenesete v ukazno vrstico, ki izpolni povezani seznam, na katerega se opozarja res, nato pa lahko ponovimo seznam in natisnemo ali naredimo karkoli.

(Tam je nekaj grdega, kjer moramo kopati v različne vrste struct sockaddrs, odvisno od različice IP. Oprostite za to! Nisem prepričan v boljši način okoli nje.)

Sample run! Vsi imajo radi posnetke zaslona:

$ showip www.example.net
IP addresses for www.example.net:

  IPv4: 192.0.2.88

$ showip ipv6.example.com
IP addresses for ipv6.example.com:

  IPv4: 192.0.2.101
  IPv6: 2001:db8:8c00:22::171

Zdaj, ko imamo to pod nadzorom, bomo rezultate, ki jih dobimo od getaddrinfo (), prenesli na druge funkcije socket-a in končno vzpostavili našo mrežno povezavo! Nadaljuj branje!

5.2. socket() – dobite opis datoteke!

Mislim, da ga ne morem več odpreti – govoriti moram o sistemskem klicu socket(). Tukaj je razčlenitev:

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol); 

Toda kaj so ti argumenti? Omogočajo vam, da poveste, kakšne vrste vtičnice želite (IPv4 ali IPv6, tok ali datagram ter TCP ali UDP).

To so bili ljudje, ki bi težko kodirali te vrednote, in to lahko popolnoma storite. (Domena je PF_INET ali PF_INET6, vrsta je SOCK_STREAM ali SOCK_DGRAM in protokol je lahko nastavljen na 0, da izberete ustrezen protokol za določen tip. Ali pa lahko pokličete getprotobyname (), da poiščete želeni protokol, »tcp« ali » Udp “.)

(Ta stvar PF_INET je bližnji sorodnik AF_INET, ki ga lahko uporabite pri inicializaciji polja sin_family v strukturi sockaddr_in. Pravzaprav so tako tesno povezani, da imajo dejansko enako vrednost, mnogi programerji pa bodo klicali socket() In prepustite AF_INET kot prvi argument namesto PF_INET. Zdaj dobite nekaj mleka in piškotkov, ker je to čas za zgodbo. Nekoč nekoč že davno se je zdelo, da je morda družina naslovov (kaj je “AF” V “AF_INET”) podpira več protokolov, na katere se sklicuje njihova protokolarna družina (kaj pomeni “PF” v “PF_INET”). To se ni zgodilo. In vsi so živeli srečno do konca. Torej, najprimernejša stvar je, da uporabite AF_INET v vašem struct sockaddr_in in PF_INET v vašem klicu socket ().)

Kakorkoli, dovolj tega. Kaj res želite storiti je, da uporabite vrednosti iz rezultatov klica na getaddrinfo() in jih vnesite v socket() neposredno tako:

int s;
struct addrinfo hints, *res;

// naredi iskanje
// [se pretvarjamo, da smo že napolnili "namige" struct]
getaddrinfo("www.example.com", "http", &hints, &res);

// [spet bi morali preveriti napake na getaddrinfo() in hoditi
// seznam "res" povezan z iskanjem veljavnih vnosov namesto samo
// Ob predpostavki, da je prvi dober (podobno kot mnogi od teh primerov.)
// Oglejte si razdelek o odjemalcu/strežniku za resnične primere.]

s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

socket() preprosto vrne k vam soketni deskriptor, ki ga lahko uporabite v kasnejših sistemskih klicih ali -1 pri napaki. Svetovna spremenljivka errno je nastavljena na vrednost napake (za več podrobnosti glejte stran errno man in hitra opomba o uporabi errno v večnitnih programih.)

Dobro, lepo, dobro, ampak kakšna je ta vtičnica? Odgovor je, da v resnici ni nobenega dobrega, in morate prebrati in narediti več sistemskih klicev, da bi imeli smisel.

5.3. bind() – V kakšnem pristanišču sem?

Ko imate vtičnico, boste morda morali to vtičnico povezati z vrati na vašem lokalnem računalniku. (To običajno storite, če boste listen() za dohodne povezave na določenih omrežnih igrah v omrežju za več igralcev, če vam to povedo, da “priključite na vrata 192.168.5.10 3490″.) Številko vrat uporablja jedro, ki se bo ujemal s prihajajočim paketom v določen deskriptor vtičnice procesa. Če boste delali le connect() (ker ste odjemalec in ne strežnik), je to verjetno nepotrebno. Vseeno preberite, samo za udarce.

Tukaj je povzetek za sistemski klic bind():

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd je deskriptor datoteke socket, ki ga vrne socket(). my_addr je kazalec na strukturo sockaddr, ki vsebuje informacije o vašem naslovu, in sicer pristanišču in naslovu IP. addrlen je dolžina v bajtih tega naslova.

Že. To je malo za absorbiranje v enem kosu. Naj navedemo primer, ki veže vtičnico gostitelju, na katerem se izvaja program, pristanišče 3490:

struct addrinfo hints, *res;
int sockfd;

// najprej z naslovom naložite naslov getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

getaddrinfo(NULL, "3490", &hints, &res);

// naredite vtičnico:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// bind v pristanišče, na katerega smo šli getaddrinfo():

bind(sockfd, res->ai_addr, res->ai_addrlen);

Z uporabo zastavice AI_PASSIVE povem programu, da se veže na IP gostitelja, na katerem se izvaja. Če se želite povezati z določenim krajevnim naslovom IP, spustite AI_PASSIVE in vnesite naslov IP za prvi argument, da se dobi getaddrinfo ().

bind() vrne tudi -1 pri napaki in nastavi errno na vrednost napake.

Veliko stare kode ročno pakira struct sockaddr_in pred klicem bind (). Očitno je, da je to IPv4-specifična, vendar vas resnično nič ne preprečuje, da bi naredili isto stvar z IPv6, razen da bo z uporabo getaddrinfo () lažje na splošno. Kakorkoli že, stara koda izgleda nekaj takega:

// !!! To je stari način !!!

int sockfd;
struct sockaddr_in my_addr;

sockfd = socket(PF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);     // kratek, omrežni vrstni red
my_addr.sin_addr.s_addr = inet_addr("10.12.110.57");
memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);

bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr);

V zgornji kodi lahko dodelite tudi INADDR_ANY v polje s_addr, če bi se želeli povezati z vašim lokalnim IP naslovom (kot je oznaka AI_PASSIVE, zgoraj.) Različica IPv6 INADDR_ANY je globalna spremenljivka in6addr_any, ki je dodeljena v polje sin6_addr vaše strukture sockaddr_in6. (Obstaja tudi makro IN6ADDR_ANY_INIT, ki ga lahko uporabite v spremenljivkem inicializatorju.)

Še ena stvar, ki jo morate paziti pri klicu bind(): ne gredo v podmorsko številko s številkami vrat. Vsa pristanišča pod 1024 so RESERVED (razen če ste nadrejeni)! Lahko imate katero koli številko vrat, ki je nad tem, do 65535 (pod pogojem, da jih še ne uporablja drug program).

Včasih boste morda opazili, poskusite ponovno vzpostaviti strežnik in povezati () neuspešno in zatrjevati, da je »Naslov že v uporabi«. Kaj to pomeni? No, malenkost vtičnice, ki je bila priključena, še vedno visi v jedru, in to je zaskočitev pristanišča. Lahko počakate, da počisti (nekaj minut ali tako) ali pa v program dodajte kodo, ki ji omogoča ponovno uporabo pristanišča, na primer:

int yes=1;
//char yes='1'; // Solaris people use this

// izgubite napeto sporočilo o napaki »Naslov že v uporabi«
if (setsockopt(listener,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof yes) == -1) {
    perror("setsockopt");
    exit(1);
} 

Ena majhna dodatna zadnja opomba o bind(): časi, ko jih ne boste morali nujno poklicati. Če se connect() na oddaljeni računalnik in vam ni všeč, kakšna je vaša lokalna vrata (tako kot pri telnet, kjer vas skrbi samo za oddaljena vrata), lahko preprosto pokličete connect(), preverite, ali je vtičnica nevezana in jo bo bind) na neuporabljeno lokalno vrata, če je potrebno.

5.4. connect() – Hej, ti!

Samo nekaj minut se pretvarjamo, da ste telnet aplikacija. Vaš uporabnik vam ukazuje (tako kot v filmu TRON), da dobite deskriptor socket datoteke. Soglašate in pokličite socket(). Nato vam uporabnik pove, da se priključite na “10.12.110.57” na pristanišču “23” (standardna telnetna vrata.) Yow! Kaj počneš sedaj?

Lucky for you, program, zdaj preučujete razdelek connect() – kako se povezati z oddaljenim gostiteljem. Torej brutalno preberite! Ni časa za izgubo!

Klic connect() je naslednji:

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); 

sockfd je naš prijazen deskriptor datotečnega vtiča soseske, kot ga je vrnil klic socket(), serv_addr je struct sockaddr, ki vsebuje ciljno vrata in IP naslov, in addrlen je dolžina v bajtih strukture naslova strežnika.

Vse te informacije se lahko naučijo iz rezultatov callad-a getaddrinfo(), ki se skriva.

Ali je to bolj smiselno? Ne morem te slišati od tukaj, zato moram samo upati, da je. Oglejmo si primer, kjer povezava vtičnice “www.example.com”, vrata 3490:

struct addrinfo hints, *res;
int sockfd;

// najprej z naslovom naložite naslov getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo("www.example.com", "3490", &hints, &res);

// naredite vtičnico:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// connect!

connect(sockfd, res->ai_addr, res->ai_addrlen);

Še enkrat, programi starih šol so napolnili svoje strukture sockaddr_ins, da bi se connect(). To lahko storite, če želite. Oglejte si podobno opombo v razdelku bind() zgoraj.

Ne pozabite preveriti povratne vrednosti iz bind() – vrnil se bo -1 pri napaki in nastavil spremenljivko errno.

Prav tako opazite, da nismo poklicali bind(). V bistvu nas ne zanima številka naše lokalne številke; skrbimo samo, kje smo (oddaljeno pristanišče). Kernel nam bo izbral lokalna vrata in spletno mesto, s katerim se povezujemo, bo te podatke samodejno dobilo od nas. Brez skrbi.

5.5. listen() – Ali me bo kdo poklical?

Ok, čas za spremembo ritma. Kaj, če se ne želite povezati z oddaljenim gostiteljem. Recimo, samo za udarce, da želite čakati na dohodne povezave in jih na nek način ravnati. Postopek je dva koraka: najprej listen(), nato accept() (glejte spodaj).

Poslušaj klic je precej preprost, vendar zahteva malo pojasnila:

int listen(int sockfd, int backlog); 

sockfd je običajen deskriptor datoteke socket iz sistemskega klica. backlog je število dovoljenih povezav v dohodni vrsti. Kaj to pomeni? No, dohodne povezave bodo čakale v tej čakalni vrsti, dokler ne sprejmete () jih (glejte spodaj), in to je omejitev števila, v kolikšni meri lahko čakajo na vrsto. Večina sistemov tiho omeji to število na približno 20; lahko verjetno pobegnete z nastavitvijo na 5 ali 10.

Spet, kot je običajno, listen() vrne -1 in nastavi napako errno.

No, kot verjetno predstavljate, moramo klicati bind(), preden pokličemo listen(), tako da strežnik deluje na določenem pristanišču. (Morate imeti možnost povedati svojim prijateljem, kateremu pristanišču se poveže!) Torej, če boste poslušali dohodne povezave, bo zaporedje sistemskih klicev, ki jih boste naredili, naslednje:

getaddrinfo();
socket();
bind();
listen();
/* accept() gre tukaj */ 

To bom pustil na mestu vzorčne kode, saj je to precej samoumevno. (Koda v razdelku accept(), spodaj, je bolj popolna.) Zares prefinjen del tega celotnega sha-banga je poziv za accept().

5.6. accept() – “Hvala za klic pristanišča 3490.”

Pripravite se – klic accept() je nekako čuden! Kaj se bo zgodilo, je to: nekdo, daleč daleč, bo poskusil connect() na vaš računalnik na pristanišču, ki ga listen(). Njihova povezava bo v čakalni vrsti, ki čakajo na accept()ed. Pokličete accept() in poveste, da dobite čakajočo povezavo. Vrnila vam bo povsem nov deskriptor deskriptorjev datotek, ki bo uporabljena za to eno povezavo! To je pravica, nenadoma imate dva deskriptorja datotečnih datotek za ceno enega! Izvirnik še vedno posluša nove povezave, novonastalo pa je končno pripravljeno send() in recv(). So bili tam!

Klic je naslednji:

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

sockfd je opisni list listen(). Preprosto je. addr bo navadno kazalec na lokalno strukturo sockaddr_storage. Tu se bodo prikazale informacije o dohodni povezavi (in s tem lahko določite, kateri gostitelj vas kliče iz katerega pristanišča). addrlen je lokalna celoštevilska spremenljivka, ki mora biti nastavljena na sizeof (struct sockaddr_storage), preden je njen naslov sprejet, da accept(). accept() ne bo dal več kot toliko bajtov v addr. Če dodaja manj, bo spremenila vrednost dodane vrednosti, da bi to addrlen.

Ugani kaj? accept() vrne -1 in nastavi errno, če pride do napake. Betcha tega ni razumel.

Kot prej, je to kupček, ki se absorbira v enem kosu, zato je tukaj vzorec kode fragment za vaše preiskovanje:

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define MYPORT "3490"  // se bodo uporabniki pristanišč povezali
#define BACKLOG 10     // koliko čakajočih čakalnih vezij bo

int main(void)
{
    struct sockaddr_storage their_addr;
    socklen_t addr_size;
    struct addrinfo hints, *res;
    int sockfd, new_fd;

    // !! ne pozabite na preverjanje napak pri teh klicih !!

    // najprej z naslovom naložite naslov getaddrinfo():

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

    getaddrinfo(NULL, MYPORT, &hints, &res);

    // naredite vtičnico, jo povežite in poslušajte:

    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    bind(sockfd, res->ai_addr, res->ai_addrlen);
    listen(sockfd, BACKLOG);

    // zdaj sprejmite dohodno povezavo:

    addr_size = sizeof their_addr;
    new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

    // pripravljen na komuniciranje socket descriptor new_fd!
    .
    .
    .

Spomnimo se, da bomo za vse klice send() in recv() uporabili deskriptor sokov new_fd. Če dobite samo eno povezavo, lahko close() slušalko sockfd, da preprečite več dohodnih povezav na istem pristanišču, če tako želite.

5.7. send() in recv() – Pogovorite se z mano!

Ti dve funkciji sta namenjena za komunikacijo preko vtičnic s tokom ali povezanih podnožij datagrama. Če želite uporabljati redne nepovezane vtičnice datagrama, boste morali videti spodnji razdelek o sendto() in recvfrom().

Klic send():

int send(int sockfd, const void *msg, int len, int flags); 

sockfd je deskriptor soketa, ki ga želite poslati (ali je tisti, ki ga je vrnil socket () ali tisti, ki ste ga dobili s accept().) msg je kazalec na podatke, ki jih želite poslati, in len je dolžina podatke v bajtih. Samo nastavite flags na 0. (Za več informacij o zastavah si oglejte stran za send() man.

Nekatera vzorčna koda je lahko:

char *msg = "Beej je bil tukaj!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
. 

send() vrne število dejansko poslanih bajtov – to je lahko manj od številke, ki ste jo poslali! Glej, včasih mu poveste, da pošljete celotno gobico podatkov in ga preprosto ne morete obvladati. Izginil bo čim več podatkov, kot vam bo uspelo, in vam zaupam, da ostale pošiljate pozneje. Ne pozabite, če se vrednost, ki jo vrne send(), ne ujema z vrednostjo v len, je na vas, da pošljete preostanek niza. Dobra novica je to: če je paket majhen (manj kot 1K ali tako), bo verjetno uspelo poslati celotno stvar vse naenkrat. Ponovno se vrne -1 ob napaki, errno pa je nastavljena na številko napake.

Klic recv() je podoben v mnogih pogledih:

int recv(int sockfd, void *buf, int len, int flags);

sockfd je deskriptor vtičnice za branje, buf je vmesni pomnilnik za prebiranje informacij v, len je največja dolžina vmesnega pomnilnika, znaki pa se znova nastavijo na 0. (Za manjše informacije glejte stran za recv().)

recv() vrne število bajtov, ki se dejansko preberejo v vmesni pomnilnik, ali -1 pri napaki (z ustrezno napako).

Počakajte! recv() lahko vrne 0. To lahko pomeni le eno stvar: oddaljena stran je zaprla povezavo na vas! Povratna vrednost 0 je recv() način, da veste, da se je to zgodilo.

Tam je bilo to enostavno, kajne? Zdaj lahko prenesete podatke naprej in nazaj na vtičnice za pretok! Whee! Ti si Unix Network Programmer!

5.8. sendto() in recvfrom() – Pogovorite se z menoj, slog DGRAM

“To je vse v redu in dandy,” slišim si rekel, “ampak kje me to pusti z nepovezanimi datagram vtičnicami?” Brez problema, amigo. Imamo samo stvar.

Ker datagramovi vtičniki niso povezani z oddaljenim gostiteljem, uganite, kateri del informacij moramo dati pred pošiljanjem paketa? Tako je! Naslov destinacije! Tukaj je škatlica:

int sendto(int sockfd, const void *msg, int len, unsigned int flags,
           const struct sockaddr *to, socklen_t tolen);

Kot vidite, je ta klic v bistvu enak kot klic za send() z dodatkom dveh drugih informacij. To je kazalec na struct sockaddr (ki bo verjetno še en struct sockaddr_in ali struct sockaddr_in6 ali struct sockaddr_storage, ki ste ga oddali v zadnji minuti), ki vsebuje ciljni naslov IP in vrata. Tolen, int globoko navzdol, lahko preprosto nastavite na sizeof * to or sizeof (struct sockaddr_storage).

Če želite dobiti svoje roke v strukturi naslovnega naslova, jo boste verjetno dobili bodisi iz getaddrinfo() ali iz recvfrom() spodaj, ali pa jo boste ročno izpolnili.

Tako kot pri send(), sendto() vrne število dejansko poslanih bajtov (kar bi lahko bilo tudi manj kot število bajtov, ki ste mu povedali, da ga pošljete!) Ali -1 pri napaki.

Podobno so tudi recv() in recvfrom(). Povzetek recvfrom() je:

int recvfrom(int sockfd, void *buf, int len, unsigned int flags,
             struct sockaddr *from, int *fromlen);

Again, to je tako kot recv() z dodatkom nekaj polj. iz je kazalec na lokalno strukturo sockaddr_storage, ki bo napolnjena z naslovom IP in pristaniščem naprave za izvirnike. Fromlen je kazalec na lokalni int, ki ga je treba inicializirati v velikost * od ali sizeof (struct sockaddr_storage). Ko se funkcija vrne, bo odlomek vseboval dolžino dejansko shranjenega naslova.

recvfrom() vrne število prejetih bajtov ali -1 pri napaki (z ustrezno nastavitvijo errno).

Torej, tukaj je vprašanje: zakaj uporabimo struct sockaddr_storage kot vrsto vtičnice? Zakaj ne struct sockaddr_in? Ker se ne želite povezati z IPv4 ali IPv6. Torej uporabljamo generični struct sockaddr_storage, za katerega vemo, da bo dovolj velik.

(Torej… tukaj je še eno vprašanje: zakaj struktura sockaddr ni dovolj velika za kateri koli naslov? sockaddr_storage smo celo oddali v splošni namen struct sockaddr! Zdi se, da je tuja in odvečna, to ni dovolj veliko, in mislim, da bi bilo sprememba na tej točki problematična, zato so naredili novo.)

Ne pozabite, če connect() vtičnico datagrama, lahko preprosto uporabite send() in recv() za vse vaše transakcije. Vtičnica je še vedno datagram vtičnica in paketi še vedno uporabljajo UDP, toda vmesniški vmesnik samodejno dodaja ciljne in izvorne podatke za vas.

 5.9. close() in shutdown() – Iztegni moj obraz!
Dajte! Cel dan ste send() ing in recv() podatke, ki ste jih dobili. Pripravljeni ste, da zaprete povezavo v deskriptorju sokov. To je enostavno. Uporabite lahko samo navadno funkcijo deskriptorja close():

close(sockfd); 

To bo preprečilo nadaljnje branje in pisanje v vtičnico. Kdorkoli poskuša prebrati ali napisati vtičnico na oddaljenem koncu, bo prejela napako.

Samo v primeru, da želite malo več nadzora nad tem, kako se vtičnica zapre, lahko uporabite funkcijo shutdown(). Omogoča vam, da prekinete komunikacijo v določeni smeri ali obeh načinov (tako kot close().) Snemanje:

int shutdown(int sockfd, int how); 

sockfd je deskriptor datoteke, ki ga želite zaustaviti, in kako je eden od teh:

0 Nadaljnje sprejemanje ni dovoljeno
1 Nadaljnje pošiljke niso dovoljene
2 Nadaljnje pošiljanje in sprejemanje ni dovoljeno (takoclose())

shutdown() vrne 0 pri uspehu in -1 pri napaki (z ustrezno nastavitvijo errno.)

Če boste uporabili shutdown() na nepovezanih vtičnicah datagrama, bo sokal preprosto naredil za nadaljnje send() in recv() (ne pozabite, da jih lahko uporabite, če connect() vtičnico datagrama.)

Pomembno je omeniti, da shutdown () dejansko ne zapira datoteke deskriptorja – samo spremeni njegovo uporabnost. Če želite osvoboditi deskriptor vtičnice, morate uporabiti close().

Nič za to.

(Razen, da ne pozabite, da če uporabljate Windows in Winsock, ki bi morali pokličite zaprto closesocket() namesto close().)

5.10. getpeername() – Kdo ste?

Ta funkcija je tako preprosta.

Tako enostavno, skoraj nisem dala svojega oddelka. Toda tukaj je vseeno.

Funkcija getpeername () vam bo povedala, kdo je na drugem koncu priključene vtičnice. Sinopsis:

#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); 

sockfd je deskriptor priključene vtičnice, addr je kazalec na struct sockaddr (ali struct sockaddr_in), ki bo vseboval podatke o drugi strani povezave in addrlen je kazalec na int, ki bi ga bilo treba inicializirati do velikosti * addr ali sizeof (struct sockaddr).

Funkcija vrne -1 pri napaki in ustrezno določa errno.

Ko imate svoj naslov, lahko z uporabo inet_ntop (), getnameinfo () ali gethostbyaddr () natisnete ali dobite več informacij. Ne, ne morete dobiti svojega uporabniškega imena. (V redu, če je v drugem računalniku nameščen identifikacijski demon, je to mogoče. To pa je izven področja uporabe tega dokumenta. Za več informacij glejte RFC 1413.)

5.11. gethostname() – Kdo sem jaz?

Še lažje kot getpeername() je funkcija gethostname(). Vrne ime računalnika, na katerem se izvaja vaš program. Ime lahko nato uporabi ime gethostbyname() spodaj, da bi določili naslov IP vašega lokalnega računalnika.

Kaj bi bilo lahko bolj zabavno? Lahko bi pomislil na nekaj stvari, vendar se ne nanašajo na programiranje vtičnic. Kakorkoli, tukaj je razčlenitev:

#include <unistd.h>

int gethostname(char *hostname, size_t size); 

Argumenti so preprosti: hostname je kazalec na niz znakov, ki bodo vsebovali ime gostitelja ob vrnitvi funkcije, velikost pa je dolžina v bajtih matrike hostname.

Funkcija vrne 0 ob uspešnem zaključku in -1 pri napaki, nastavi errno kot običajno.

6. Ozadje odjemalca-strežnik

To je svet odjemalec-strežnik, otrok. Skoraj vse v omrežju se ukvarja s procesi strank, ki se pogajajo s strežniškimi procesi in obratno. Na primer telnet. Ko se povežete z oddaljenim gostiteljem na portu 23 s telnetom (odjemalcem), se program na tem gostitelju (imenovanem telnetd, strežnik) sproži za življenje. Obvlada dohodno telnetno povezavo, nastavi vas s pozivom za prijavo itd.

[Client-Server Interaction Diagram]

Interakcija med odjemalcem in strežnikom.

Izmenjava informacij med odjemalcem in strežnikom je povzeta v zgornjem diagramu.

Upoštevajte, da lahko par odjemalec-strežnik govorita SOCK_STREAM, SOCK_DGRAM ali karkoli drugega (dokler govorijo isto stvar). Nekateri dobri primeri pari odjemalca-strežnik so telnet/telnetd, ftp/ftpd ali Firefox/Apache . Vsakič, ko uporabljate ftp, je oddaljen program, ftpd, ki vam služi.

Pogosto bo na napravi obstajal samo en strežnik, ta strežnik pa bo z vilicami () reševal več strank. Osnovna rutina je: strežnik bo počakal na povezavo, accept() in fork() otroški postopek, s katerim se bo ukvarjal. To je tisto, kar naredi naš vzorec strežnik v naslednjem poglavju.

6.1. Preprost strežniški strežnik

Vse to strežnik pošilja niz “Zdravo, svet!” prek povezave s tokom. Vse, kar morate storiti, da preizkusite ta strežnik, je zagnati v enem oknu in telnet z njim iz drugega:

$ telnet remotehostname 3490

kjer je remotehostname ime naprave, na katerem ga uporabljate.

Strežniška koda:

/*
** server.c -- predstavitveni strežnik za strežniške vtičnice
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>

#define PORT "3490"  // se bodo uporabniki pristanišč povezali

#define BACKLOG 10     // koliko čakajočih čakalnih vezij bo

void sigchld_handler(int s)
{
    // waitpid() lahko prepisuje errno, zato ga shranimo in obnovimo:
    int saved_errno = errno;

    while(waitpid(-1, NULL, WNOHANG) > 0);

    errno = saved_errno;
}


// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd, new_fd;  // poslušaj sock_fd, nova povezava na new_fd
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_storage their_addr; // informacije o naslovu konektorja
    socklen_t sin_size;
    struct sigaction sa;
    int yes=1;
    char s[INET6_ADDRSTRLEN];
    int rv;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // zanke skozi vse rezultate in se povežite s prvim, kar lahko
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                sizeof(int)) == -1) {
            perror("setsockopt");
            exit(1);
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        break;
    }

    freeaddrinfo(servinfo); // vse je bilo opravljeno s to strukturo

    if (p == NULL)  {
        fprintf(stderr, "server: failed to bind\n");
        exit(1);
    }

    if (listen(sockfd, BACKLOG) == -1) {
        perror("listen");
        exit(1);
    }

    sa.sa_handler = sigchld_handler; // Žanje vse mrtve procese
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    printf("server: waiting for connections...\n");

    while(1) {  // glavna accept() zanke
        sin_size = sizeof their_addr;
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
        if (new_fd == -1) {
            perror("accept");
            continue;
        }

        inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr),
            s, sizeof s);
        printf("server: got connection from %s\n", s);

        if (!fork()) { // to je otroški proces
            close(sockfd); // otrok ne potrebuje poslušalca
            if (send(new_fd, "Hello, world!", 13, 0) == -1)
                perror("send");
            close(new_fd);
            exit(0);
        }
        close(new_fd);  // staršem to ne potrebuje
    }

    return 0;
}

Če ste radovedni, imam kodo v eni veliki main() funkciji za (čutim) sintaktično jasnost. Če se počutite bolje, se ga lahko razdelite na manjše funkcije.

(Tudi ta celoten sigaction() je morda nov za vas – to je v redu. Koda, ki je tam, je odgovorna za žimerske procese, ki se pojavijo kot izhod za otroke (če so). Če naredite veliko zombijev in ne Jih poželi, bo skrbnik sistema postal vznemirjen.)

Podatke iz tega strežnika lahko dobite z uporabo odjemalca, navedenega v naslednjem poglavju.

6.2. Preprost odjemalec toka

Ta tip je še lažji od strežnika. Vse to odjemalec se poveže z gostiteljem, ki ga navedete v ukazni vrstici, pristanišče 3490. Dobi niz, ki ga pošlje strežnik.

Vir odjemalca:

/*
** client.c -- demo odjemalca toka vtičnice
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>

#include <arpa/inet.h>

#define PORT "3490" // bo pristaniški odjemalec povezal 

#define MAXDATASIZE 100 // največje število bajtov, ki jih lahko dobimo takoj 

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(int argc, char *argv[])
{
    int sockfd, numbytes;  
    char buf[MAXDATASIZE];
    struct addrinfo hints, *servinfo, *p;
    int rv;
    char s[INET6_ADDRSTRLEN];

    if (argc != 2) {
        fprintf(stderr,"usage: client hostname\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("client: socket");
            continue;
        }

        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("client: connect");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "client: failed to connect\n");
        return 2;
    }

    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
            s, sizeof s);
    printf("client: connecting to %s\n", s);

    freeaddrinfo(servinfo); // vse je bilo opravljeno s to strukturo

    if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) {
        perror("recv");
        exit(1);
    }

    buf[numbytes] = '\0';

    printf("client: received '%s'\n",buf);

    close(sockfd);

    return 0;
}

Upoštevajte, da če ne izvajate strežnika pred zagonom odjemalca, connect() vrne “Povezava zavrnjena”. Zelo uporabno.

6.3. Datagramove vtičnice

Zgoraj smo že pokrivali osnove podatkovnih vtičnic UDP z našo razpravo o sendto() in recvfrom() zgoraj, zato bom predstavil le nekaj vzorčnih programov: talker.c in listener.c.

poslušalec sedi na stroju, ki čaka dohodni paket na vratih 4950. govornik pošlje paket v to vrata na določenem računalniku, ki vsebuje vse, kar uporabnik vnese v ukazno vrstico.

Tukaj je vir za listener.c:

/*
** listener.c -- predstavitev datagramovih vtičnic "strežnik"
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define MYPORT "4950"    // se bodo uporabniki pristanišč povezali

#define MAXBUFLEN 100

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;
    struct sockaddr_storage their_addr;
    char buf[MAXBUFLEN];
    socklen_t addr_len;
    char s[INET6_ADDRSTRLEN];

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC; // nastavite na AF_INET, da prisilite IPv4
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE; // uporabite moj IP

    if ((rv = getaddrinfo(NULL, MYPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // zanke skozi vse rezultate in se povežite s prvim, kar lahko
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("listener: socket");
            continue;
        }

        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("listener: bind");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "listener: failed to bind socket\n");
        return 2;
    }

    freeaddrinfo(servinfo);

    printf("listener: waiting to recvfrom...\n");

    addr_len = sizeof their_addr;
    if ((numbytes = recvfrom(sockfd, buf, MAXBUFLEN-1 , 0,
        (struct sockaddr *)&their_addr, &addr_len)) == -1) {
        perror("recvfrom");
        exit(1);
    }

    printf("listener: got packet from %s\n",
        inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr),
            s, sizeof s));
    printf("listener: packet is %d bytes long\n", numbytes);
    buf[numbytes] = '\0';
    printf("listener: packet contains \"%s\"\n", buf);

    close(sockfd);

    return 0;
}

Obvestilo, da v našem klicu getaddrinfo() končno uporabljamo SOCK_DGRAM. Upoštevajte tudi, da ni potrebe po listen() ali accept(). To je ena od prednosti uporabe nepovezanih vtičnic datagrama!

Naslednji vir je vir talker.c:

/*
** talker.c -- predstavitev datagrama "odjemalec"
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVERPORT "4950"    // se bodo uporabniki pristanišč povezali

int main(int argc, char *argv[])
{
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    int rv;
    int numbytes;

    if (argc != 3) {
        fprintf(stderr,"usage: talker hostname message\n");
        exit(1);
    }

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;

    if ((rv = getaddrinfo(argv[1], SERVERPORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }

    // Zanke skozi vse rezultate in naredite vtičnico
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) {
            perror("talker: socket");
            continue;
        }

        break;
    }

    if (p == NULL) {
        fprintf(stderr, "talker: failed to create socket\n");
        return 2;
    }

    if ((numbytes = sendto(sockfd, argv[2], strlen(argv[2]), 0,
             p->ai_addr, p->ai_addrlen)) == -1) {
        perror("talker: sendto");
        exit(1);
    }

    freeaddrinfo(servinfo);

    printf("talker: sent %d bytes to %s\n", numbytes, argv[1]);
    close(sockfd);

    return 0;
}

In to je vse, kar ima! Zaženite poslušalca na določenem računalniku, nato pogovorite z drugim. Pazite, da komunicirajo! Zabavno G-razburjenje za celotno jedrsko družino!

Tokrat vam sploh ni treba zagnati strežnika! Sami lahko pogovarjate s pogovori in samo srečno požre pakete v eter, kjer izginejo, če nihče ni pripravljen z recvfrom() na drugi strani. Ne pozabite: podatki, poslani s podnožji datagrama UDP, ne bodo zagotovo prišli!

Razen še en majhen detajl, ki sem ga v preteklosti že omenil: priključene datagramove vtičnice. O tem se moram pogovoriti, ker smo v razdelku datagrama dokumenta. Recimo, da talker kliče connect() in določi naslov listener’ca. Od tega trenutka naprej se lahko talker pošlje samo in prejme z naslova, ki ga je določil connect(). Zaradi tega vam ni treba uporabljati sendto() in recvfrom(); lahko preprosto uporabite send() in recv().

7. Nekoliko napredne tehnike

Te niso preveč napredne, vendar so izstopile iz bolj osnovnih ravni, ki smo jih že pokrili. Dejansko, če ste prišli tako daleč, bi morali biti sami pošteno izpolnjeni v osnovah Unix omrežja programiranje! Čestitke!

Torej greva v pogumen novi svet nekaterih bolj ezoteričnih stvari, ki bi jih morda želeli naučiti o vtičnicah. Na to!

7.1. Blokiranje

Blokiranje. Slišali ste o tem – kaj je zdaj vraga? Na kratko, “blok” je techie žargon za “spanje”. Verjetno ste opazili, da ob zagonu listener zgolj sedi tu, dokler ne pride paket. Kaj se je zgodilo je, da je imenovano recvfrom(), ni bilo podatkov, zato je recvfrom() rečeno, da “blokira” (to je spati tam), dokler ne pridejo nekateri podatki.

Veliko funkcij blokira. accept() bloki. Vse funkcije recv() blokirajo. Razlog, da to lahko storijo, je, ker jim je dovoljeno. Ko prvič ustvarite deskriptor vtiča s socket(), jedro nastavi na blokado. Če ne želite blokirati vtičnice, morate poklicati fcntl():

#include <unistd.h>
#include <fcntl.h>
.
.
.
sockfd = socket(PF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
.
. 

Z nastavitvijo vtičnice na ne-blokiranje, lahko učinkovito “ankete” vtičnico za informacije. Če poskušate prebrati iz nevključne vtičnice in tam ni podatkov, ga ni dovoljeno blokirati – vrnila se bo -1, errno pa bo nastavljena na EAGAIN ali EWOULDBLOCK.

(Počakajte – lahko vrne EAGAIN ali EWOULDBLOCK? Katero preverite? Specifikacija dejansko ne določa, kateri sistem se bo vrnil, zato je za prenosljivost preverite oba.)

Na splošno pa je ta vrsta glasovanja slaba ideja. Če program postavite v zaseden počakaj, iščete podatke v vtičnici, boste porabili čas CPU-ja, kot da se je izogibal slogu. Več elegantna rešitev za preverjanje, če želite preveriti, ali so podatki, ki čakajo na branje, najdete v naslednjem razdelku v select().

7.2. select() – Sinhrono V/I multipleksiranje

Ta funkcija je nekoliko čudna, vendar je zelo uporabna. Izvedite naslednjo situacijo: ste strežnik in želite poslušati dohodne povezave, kot tudi branje od povezav, ki jih že imate.

Ni problema, pravite samo, accept() in nekaj recv () s. Ne tako hitro, baster! Kaj, če blokirate klic accept()? Kako boste hkrati priklicali podatke za recv()? “Uporabljajte nepomembne vtičnice!” Ni šans! Ne želiš biti CPU. Kaj potem?

select() omogoča hkratno spremljanje več vtičnic. Povedal vam bo, katere so pripravljene za branje, ki so pripravljene za pisanje in katere vtičnice so postavile izjeme, če to res želite izvedeti.

V današnjem času je select(), čeprav zelo prenosljiva, ena od najmanjših načinov spremljanja vtičnic. Ena možna alternativa je neuravnotežena ali podobna, ki zajema vse sistemsko odvisne stvari, povezane z obveščanjem socket.

Brez kakršnega koli dodatka, bom ponudil povzetek select():

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int numfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout); 

Funkcija spremlja “sets” deskriptorjev datotek; zlasti readfds, writefds in exceptfdsj. Če želite videti, ali lahko berete iz standardnega vhoda in nekega deskriptorja sock, sockfd, samo dodajte deskriptorje datotek 0 in sockfd v nastavljene vrednosti readfds. Parameter numfds mora biti nastavljen na vrednosti najvišjega deskriptorja datoteke plus eno. V tem primeru mora biti nastavljen na sockfd+1, saj je gotovo višji od standardnega input (0).

Ko select() vrne, se bo readfds spremenil, da bo odražal, kateri od opisnih datotek, ki ste jih izbrali, pripravljen za branje. Lahko jih preskusite z makrom FD_ISSET() spodaj.

Preden nadaljujem veliko več, se bom pogovoril o tem, kako manipulirati s temi kompleti. Vsak niz je tipa fd_set. Na tej vrsti delujejo naslednji makri:

 

FD_SET(int fd, fd_set *set); Dodaj fd do set.
FD_CLR(int fd, fd_set *set); Odstraniti fd iz set.
FD_ISSET(int fd, fd_set *set); Vrni se, če je fd v
set.
FD_ZERO(fd_set *set); Počistite vse vnose iz set.

 

Končno, kaj je to čudno iz strukture timeval? No, včasih nočeš večno čakati, da ti nekdo pošlje nekaj podatkov. Morda vsakih 96 sekund, ki jih želite natisniti “Still Going …” na terminal, čeprav se ni nič zgodilo. Ta časovna struktura vam omogoča, da določite časovno omejitev. Če je čas presežen in select() še vedno ni našel nobenih deskriptorjev za pripravljene datoteke, se bo vrnil, da boste lahko nadaljevali z obdelavo.

Strukturni timeval ima naslednja polja:

struct timeval {
    int tv_sec;     // sekund
    int tv_usec;    // mikrosekunde
}; 

Samo nastavite tv_sec na število sekund, ki jih želite čakati, in nastavite tv_usec na število mikrosekund, ki čakajo. Ja, to je mikrosekundo, ne milisekund. V milisekundi je 1.000 mikrosekund, v sekundi pa 1.000 milisekund. Tako je v sekundi 1.000.000 mikrosekund. Zakaj je “usec”? “U” naj bi izgledal kot grška črka μ (Mu), ki jo uporabljamo za “mikro”. Ko se funkcija vrne, se lahko posodobitev časovne omejitve posodobi, da se prikaže še preostali čas. To je odvisno od kakšne arome Unixa teče.

Juhu! Imamo časovno ločljivost za mikrosekundo! No, ne računajte na to. Verjetno boste morali počakati na del svojega standardnega Unix timeslice, ne glede na to, kako majhen ste postavili strukturo timeval.

Druge zanimive stvari: če ste polja v struct struct timeval nastavili na 0, select() bo takoj začasno prekinil, kar bo v poljih učinkovito preiskovalo vse deskriptorje datotek. Če nastavite zakasnitev parametra na NULL, ne bo nikoli prekinila čakanja, dokler ne bo pripravljen prvi deskriptor datoteke. Nazadnje, če vam ni všeč čakati na določen niz, ga lahko nastavite na NULL v klicu, da select().

Naslednji delček kode čaka 2,5 sekunde za nekaj, kar se prikaže na standardnem vhodu:

/*
** select.c -- a select() demo
*/

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#define STDIN 0  // deskriptor datoteke za standardni vhod

int main(void)
{
    struct timeval tv;
    fd_set readfds;

    tv.tv_sec = 2;
    tv.tv_usec = 500000;

    FD_ZERO(&readfds);
    FD_SET(STDIN, &readfds);

    // ne skrbi writefds in exceptfds:
    select(STDIN+1, &readfds, NULL, NULL, &tv);

    if (FD_ISSET(STDIN, &readfds))
        printf("A key was pressed!\n");
    else
        printf("Timed out.\n");

    return 0;
} 

Če ste na linijskem vmesnem terminalu, mora biti ključ, ki ste ga zadeli, RETURN ali pa bo čas tako dolgo.

Zdaj bi nekateri morda mislili, da je to odličen način, da počakate na podatke v vtičnici datagram-in ste prav: mogoče je. Nekateri Unices lahko izberejo na ta način in nekateri ne morejo. Morate vedeti, kaj vaša lokalna stran pravi o zadevi, če jo želite poskusiti.

Nekateri Unices posodabljajo čas v strukturnem času, tako da odražajo čas, ki še vedno preostane pred časovnim zamikom. Toda drugi ne. Ne zanašajte se na to, če želite biti prenosni. (Uporabite gettimeofday(), če želite slediti času, ki je pretekel. To je bummer, vem, vendar je tako.)

Kaj se zgodi, če vtičnica v branju zapre povezavo? No, v tem primeru select() vrne s tem deskriptorjem nastavljenim kot “pripravljen za branje”. Ko dejansko storite recv() iz nje, se bo vrnila 0. (recv()). Tako veste, da je stranka zaprta povezavo.

Še ena zanimivost o select(): če imate vtičnico, ki jo listen(), lahko preverite, ali obstaja nova povezava, tako da v opisni vrstici za branje vstavite deskriptor datoteke readfds.

In to, moji prijatelji, je hiter pregled funkcije almighty select().

Ampak, po priljubljenem povpraševanju, tukaj je poglobljen primer. Na žalost je razlika med preprostim primerom umazanije, zgoraj in tem tukaj pomembna. Ampak poglejte, nato pa preberite opis, ki sledi temu.

Ta program deluje kot preprost večuporabniški klepet. Zaženite jo v enem oknu, nato pa telnet (“telnet hostname 9034“) iz več drugih oken. Ko vnesete nekaj v eni telnet seji, se mora prikazati v vseh drugih.

/*
** selectserver.c -- sirast večstranski chat server
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORT "9034"   // pristanišče, na katerem poslušamo

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }

    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    fd_set master;    // glavni seznam deskriptorjev datotek
    fd_set read_fds;  // seznam deskriptorjev datotek za select()
    int fdmax;        // največja številka deskriptorja datoteke

    int listener;     // slišan deskriptor
    int newfd;        // nov accept()ed deskriptor sokov
    struct sockaddr_storage remoteaddr; // client address
    socklen_t addrlen;

    char buf[256];    // pufru za podatke o strankah
    int nbytes;

    char remoteIP[INET6_ADDRSTRLEN];

    int yes=1;        // za setsockopt() SO_REUSEADDR, spodaj
    int i, j, rv;

    struct addrinfo hints, *ai, *p;

    FD_ZERO(&master);    // počistite glavne in temp nizove
    FD_ZERO(&read_fds);

    // prinesi vtičnico in jo veže
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) {
        fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
        exit(1);
    }
    
    for(p = ai; p != NULL; p = p->ai_next) {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (listener < 0) { 
            continue;
        }
        
        // izgubite napeto sporočilo o napaki "že v uporabi"
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

        if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) {
            close(listener);
            continue;
        }

        break;
    }

    // če pridemo sem, to pomeni, da se nam ni bilo treba povezati
    if (p == NULL) {
        fprintf(stderr, "selectserver: failed to bind\n");
        exit(2);
    }

    freeaddrinfo(ai); // all done with this

    // poslušaj
    if (listen(listener, 10) == -1) {
        perror("listen");
        exit(3);
    }

    // dodajte poslušalca na glavno enoto
    FD_SET(listener, &master);

    // sledi največji deskriptor datotek
    fdmax = listener; // so far, it's this one

    // glavna zanka
    for(;;) {
        read_fds = master; // copy it
        if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
            perror("select");
            exit(4);
        }

        // potekajo prek obstoječih povezav, ki iščejo podatke za branje
        for(i = 0; i <= fdmax; i++) {
            if (FD_ISSET(i, &read_fds)) { // we got one!!
                if (i == listener) {
                    // handle new connections
                    addrlen = sizeof remoteaddr;
                    newfd = accept(listener,
                        (struct sockaddr *)&remoteaddr,
                        &addrlen);

                    if (newfd == -1) {
                        perror("accept");
                    } else {
                        FD_SET(newfd, &master); // dodajte glavni nastavitvi
                        if (newfd > fdmax) {    // sledite največjim
                            fdmax = newfd;
                        }
                        printf("selectserver: new connection from %s on "
                            "socket %d\n",
                            inet_ntop(remoteaddr.ss_family,
                                get_in_addr((struct sockaddr*)&remoteaddr),
                                remoteIP, INET6_ADDRSTRLEN),
                            newfd);
                    }
                } else {
                    // obravnava podatke od odjemalca
                    if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) {
                        // je prišlo do napake ali povezave, ki jo je zaprt odjemalec
                        if (nbytes == 0) {
                            // povezava je zaprta
                            printf("selectserver: socket %d hung up\n", i);
                        } else {
                            perror("recv");
                        }
                        close(i); // bye!
                        FD_CLR(i, &master); // remove from master set
                    } else {
                        // od stranke dobimo nekaj podatkov
                        for(j = 0; j <= fdmax; j++) {
                            // poslati vsem!
                            if (FD_ISSET(j, &master)) {
                                // razen poslušalca in nas samih
                                if (j != listener && j != i) {
                                    if (send(j, buf, nbytes, 0) == -1) {
                                        perror("send");
                                    }
                                }
                            }
                        }
                    }
                } // END obdeluje podatke od odjemalca
            } // END je dobil novo dohodno povezavo
        } // END kroženje skozi deskriptorje datotek
    } // END for(;;)--in si mislil, da se ne bo nikoli končalo!
    
    return 0;
}

Obvestilo V kodi: master in read_fds imam dva kompleta deskriptorjev datotek. Prvi, master, ima vse deskripte sokov, ki so trenutno priključeni, in deskriptor sokov, ki posluša nove povezave.

Razlog za moj master set je, da select() dejansko changes nabor, ki ga vnesete vanj, da odražajo, katere vtičnice so pripravljene za branje. Ker moram slediti povezavam z enim klicem select() na drugo, jih moram varno shraniti nekam. V zadnjem trenutku kopiram master v read_fds in nato pokličem select().

Ampak to ne pomeni, da moram vsakič, ko dobim novo povezavo, dodati v master komplet? Yup! In vsakič, ko se povezava zapre, jo moram odstraniti iz glavnega sklopa? Ja, res.

Obvestilo preverim, če je vtičnica listener pripravljena za branje. Ko je, to pomeni, da imam novo povezavo, ki je v teku, in jo accept() in jo dodam glavni nastavitvi. Podobno, ko je povezava odjemalca pripravljena za branje in recv() vrne 0, vem, da je stranka zaprta povezavo in jo odstraniti iz master nabora.

Če stranka recv() vrne ničlo, čeprav vem, da so bili nekateri podatki prejeti. Tako dobim, nato pa pojdem po glavnem seznamu in pošljem te podatke vsem ostalim povezanim strankam.

In to, moji prijatelji, je manj kot preprost pregled funkcije almighty select().

Poleg tega je tukaj naknadni bonus: obstaja še ena funkcija, imenovana poll(), ki se obnaša na enak način, kot select(), vendar z drugačnim sistemom za upravljanje deskriptorjev datotek. Preverite!

7.3. Ravnanje z delnim send()

Se spomniš nazaj v razdelku o send(), zgoraj, ko sem rekel, da send() morda ne pošilja vseh bajtov, ki ste jih zahtevali? To pomeni, da želite poslati 512 bajtov, vendar vrne 412. Kaj se je zgodilo s preostalimi 100 bajtov?

No, še vedno so v vašem blažilniku, ki čakajo na pošiljanje. Zaradi okoliščin, na katere ne morete vplivati, se je jedro odločilo, da ne bo poslalo vseh podatkov v enem kosu, zdaj pa, prijatelj moj, vam je na voljo podatke.

Tako lahko naredite tako funkcijo:

#include <sys/types.h>
#include <sys/socket.h>

int sendall(int s, char *buf, int *len)
{
    int total = 0;        // koliko bajtov smo poslali
    int bytesleft = *len; // koliko smo jih pustili poslati
    int n;

    while(total < *len) {
        n = send(s, buf+total, bytesleft, 0);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }

    *len = total; // vrnjena številka, ki je bila dejansko poslana tukaj

    return n==-1?-1:0; // vrnitev -1 ob neuspehu, 0 na uspeh
} 

V tem primeru je s vtičnica, na katero želite poslati podatke, buf je pufr, ki vsebuje podatke, in len je kazalec na int, ki vsebuje število bajtov v pufru.

Funkcija vrne -1 pri napaki (in errno je še nastavljen iz klica za send().) Prav tako je število dejansko poslanih bajtov vrnjeno v len. To bo enako število bajtov, ki ste jih zahtevali za pošiljanje, razen če je prišlo do napake. sendall() bo naredil, da je najboljše, huffing in puffing, da pošljete podatke ven, če pa je napaka, se takoj vrne k vam.

Za popolnost, tukaj je vzorec poziv k funkciji:

char buf[10] = "Beej!";
int len;

len = strlen(buf);
if (sendall(s, buf, &len) == -1) {
    perror("sendall");
    printf("Poslali smo le %d bajtov zaradi napake!\n", len);
} 

Kaj se zgodi na koncu sprejemnika, ko pride del paketa? Če so paketi spremenljive dolžine, kako sprejemnik ve, kdaj se en paket konča, drugi pa se začne? Da, realni scenariji so kraljevska bolečina v osleh. Verjetno morate obkrožiti (zapomnite si, da je odsek za zaprtje podatkov na začetku poti že na začetku?) Preberite za podrobnosti!

Hitro si zapomnite vse ljubitelje Linuxa: včasih se v redkih primerih lahko select() Linuxa vrne “pripravljeno za branje” in potem dejansko ni pripravljena za branje! To pomeni, da bo blokirano na read() po select() pravi, da to ne bo! Zakaj ti malo – ! Kakorkoli že, rešitev za rešitev je, da nastavite zastavo O_NONBLOCK v sprejemno vtičnico, zato napaka z EWOULDBLOCK (ki jo lahko preprosto prezrete, če se zgodi). Oglejte si referenčno stran fcntl() za več informacij o nastavitvi vtičnice na ne-blokiranje.

7.4. Serializacija – Kako pakirati podatke

Dovolj je preprosto pošiljati besedilne podatke po omrežju, ki jih najdete, ampak kaj se zgodi, če želite poslati nekaj “binarnih” podatkov, kot so ints ali floats? Izkazalo se je, da imate nekaj možnosti.

  1. Pretvorite številko v besedilo s funkcijo, kot je sprintf(), nato pa pošljite besedilo. Sprejemnik bo razčlenil besedilo nazaj v številko z uporabo funkcije, kot je strtol().
  2. Samo pošljite podatke surovo, s kazalcem pa pošljete podatke, ki jih želite send().
  3. Številko vnesite v prenosno binarno obliko. Sprejemnik jo bo dekodiral.

Predogled! Samo zvečer!

[Zavese se dvignejo]

Beej pravi: “Raje uporabljam 3. metodo zgoraj!”

[KONEC]

(Preden začnem to poglavje resno, bi vam moral povedati, da obstajajo knjižnice za to, da se to zgodi in da se sami premikate, preostali prenosni in brez napak pa je zelo izziv. Torej poiščite svojo domačo nalogo, preden se odločite za izvajanje Te stvari sami. V to informacijo vključim tiste radovedneže o tem, kako stvari, kot je ta, delujejo.)

Pravzaprav vse metode, zgoraj, imajo svoje pomanjkljivosti in prednosti, ampak, kot sem dejal na splošno, imam raje tretjo metodo. Najprej pa pogovorimo o nekaterih pomanjkljivostih in prednostih za druga dva.

Prva metoda, ki kodira številke kot besedilo pred pošiljanjem, ima prednost, da lahko enostavno natisnete in preberete podatke, ki prihajajo preko žice. Včasih je človeško berljiv protokol odličen za uporabo v razmerah, ki niso širokopasovno intenzivne, na primer z Internet Relay Chat (IRC). Vendar pa je v slabšem položaju, da se pretvori v pretvorbo in rezultati skoraj vedno zajemajo več prostora kot prvotna številka!

Druga metoda: posredovanje neobdelanih podatkov. Ta je precej enostavna (vendar nevarna!): Vzemite kazalec na podatke, ki jih želite poslati, in pokličite z njim.

double d = 3490.15926535;

send(s, &d, sizeof d, 0);  /* NEVARNOST--nepremično! */

Sprejemnik dobi takole:

double d;

recv(s, &d, sizeof d, 0);  /* NEVARNOST--nepremično! */

Hitro, preprosto – kaj ni všeč? No, se izkaže, da vse arhitekture predstavljajo dvojno (ali int za to zadevo) z enako bitno predstavitvijo ali celo enako bajtno naročanje! Koda je nedvomno neprenosljiva. (Hej, morda ne potrebujete prenosljivosti, v tem primeru je to lepo in hitro.)

Pri pakiranju vrst integerov smo že videli, kako lahko funkcijo htons() – funkcije funkcij pomagajo ohraniti stvari prenosljive tako, da pretvorite številke v omrežni vrstni red in kako to storiti. Na žalost ni podobnih funkcij za tipe plovcev. Je vse upanje izgubljeno?

Ne bojte se! (Ali se bojite tam za trenutek? Ne? Niti malo?) Obstaja nekaj, kar lahko storimo: lahko paketno (ali “marshal” ali “serialize” ali eno od tisoč milijonov drugih imen) podatke v znano binarno obliko, ki jo sprejemnik lahko odpošlje na oddaljeni strani.

Kaj mislim z “znanim binarnim formatom”? No, že videli smo primer htons(), kajne? Spreminja (ali “kodira”, če si to tako misliš) številko iz katere koli oblike gostitelja je v omrežni vrstni red. Če želite številko preusmeriti (unencode), sprejemnik pokliče ntohs().

Ampak, nisem samo dokončal rekel, da takšne funkcije ni bilo za druge vrste, ki niso celovite? Ja. Jaz sem. In ker v C ni standardnega načina, je to malo piščanca (to je brezplačno punvo za vas Python fans).

Stvari je, da se podatki pakirajo v znano obliko in pošljejo preko žice za dekodiranje. Na primer, za pakiranje plovcev, tukaj je nekaj hitrega in umazanega z veliko prostora za izboljšave:

#include <stdint.h>

uint32_t htonf(float f)
{
    uint32_t p;
    uint32_t sign;

    if (f < 0) { sign = 1; f = -f; }
    else { sign = 0; }
        
    p = ((((uint32_t)f)&0x7fff)<<16) | (sign<<31); // celoten del in znak
    p |= (uint32_t)(((f - (int)f) * 65536.0f))&0xffff; // frakcija

    return p;
}

float ntohf(uint32_t p)
{
    float f = ((p>>16)&0x7fff); // celoten del
    f += (p&0xffff) / 65536.0f; // fraction

    if (((p>>31)&0x1) == 0x1) { f = -f; } // označite bitni niz

    return f;
}

Zgornja koda je vrsta naivnega izvajanja, ki shranjuje float v 32-bitno številko. Visoki bit (31) se uporablja za shranjevanje znaka števila (“1″ pomeni negativno), in naslednji sedem bitov (30-16) se uporabljajo za shranjevanje celotnega števila delov plovca. Končno se preostali bitovi (15-0) uporabijo za shranjevanje delnega dela števila.

Uporaba je dokaj enostavna:

#include <stdio.h>

int main(void)
{
    float f = 3.1415926, f2;
    uint32_t netf;

    netf = htonf(f);  // pretvoriti v "mrežno" obliko
    f2 = ntohf(netf); // pretvorite nazaj v test

    printf("Original: %f\n", f);        // 3.141593
    printf(" Network: 0x%08X\n", netf); // 0x0003243F
    printf("Unpacked: %f\n", f2);       // 3.141586

    return 0;
}

Na strani plus je majhna, preprosta in hitro. Na minus strani, to ni učinkovita uporaba prostora in obseg je močno omejen – poskusite shraniti številko več kot 32767 tam in to ne bo zelo veselo! V zgornjem primeru lahko vidite tudi, da zadnjih nekaj decimalnih mest ni pravilno shranjenih.

Kaj lahko storimo namesto tega? No, standard za shranjevanje številk s plavajočo vejico je znan kot IEEE-754. Večina računalnikov uporablja to obliko interno za to, da se počne s plavajočo matematično točko, zato v teh primerih strogo gledano ni treba pretvoriti. Ampak, če želite, da je vaša izvorna koda prenosljiva, je to predpostavka, ki je ne morete nujno narediti. (Po drugi strani, če želite, da stvari postanejo hitro, morate to optimizirati na platformah, ki jih ne potrebujete! To je tisto, kar htons() in ilk do.)

Tukaj je nekaj kode, ki kodira floats in podvoji v format IEEE-754. (V glavnem – ne kodira NaN ali Infinity, vendar bi se lahko spremenil, da bi to naredil.)

#define pack754_32(f) (pack754((f), 32, 8))
#define pack754_64(f) (pack754((f), 64, 11))
#define unpack754_32(i) (unpack754((i), 32, 8))
#define unpack754_64(i) (unpack754((i), 64, 11))

uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
    long double fnorm;
    int shift;
    long long sign, exp, significand;
    unsigned significandbits = bits - expbits - 1; // -1 for sign bit

    if (f == 0.0) return 0; // get this special case out of the way

    // preverite znak in začnite normalizirati
    if (f < 0) { sign = 1; fnorm = -f; }
    else { sign = 0; fnorm = f; }

    // dobite normalizirano obliko f in sledite eksponentu
    shift = 0;
    while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
    while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
    fnorm = fnorm - 1.0;

    // izračuna binarno obliko (neplavajočo) pomembnih podatkov
    significand = fnorm * ((1LL<<significandbits) + 0.5f);

    // dobite pristranskega eksponenta
    exp = shift + ((1<<(expbits-1)) - 1); // shift + bias

    // vrnite končni odgovor
    return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}

long double unpack754(uint64_t i, unsigned bits, unsigned expbits)
{
    long double result;
    long long shift;
    unsigned bias;
    unsigned significandbits = bits - expbits - 1; // -1 for sign bit

    if (i == 0) return 0.0;

    // pull the significand
    result = (i&((1LL<<significandbits)-1)); // mask
    result /= (1LL<<significandbits); // convert back to float
    result += 1.0f; // add the one back on

    // obravnavati eksponenta
    bias = (1<<(expbits-1)) - 1;
    shift = ((i>>significandbits)&((1LL<<expbits)-1)) - bias;
    while(shift > 0) { result *= 2.0; shift--; }
    while(shift < 0) { result /= 2.0; shift++; }

    // podpišite
    result *= (i>>(bits-1))&1? -1.0: 1.0;

    return result;
}

Na vrhu sem dal nekaj priročnih makrov za pakiranje in razpakiranje 32-bitnih (verjetno float) in 64-bitnih (verjetno dvojnih) številk, vendar pa lahko funkcija pack754() pokliče neposredno in se kodira, Podatkov (katerih izpiski so rezervirani za normalno število števila).

Tukaj je uporaba vzorca:

#include <stdio.h>
#include <stdint.h> // definira vrste uintN_t
#include <inttypes.h> // definira makroje PRIx

int main(void)
{
    float f = 3.1415926, f2;
    double d = 3.14159265358979323, d2;
    uint32_t fi;
    uint64_t di;

    fi = pack754_32(f);
    f2 = unpack754_32(fi);

    di = pack754_64(d);
    d2 = unpack754_64(di);

    printf("float before : %.7f\n", f);
    printf("float encoded: 0x%08" PRIx32 "\n", fi);
    printf("float after  : %.7f\n\n", f2);

    printf("double before : %.20lf\n", d);
    printf("double encoded: 0x%016" PRIx64 "\n", di);
    printf("double after  : %.20lf\n", d2);

    return 0;
}

Zgornja koda proizvaja ta izhod:

float before : 3.1415925
float encoded: 0x40490FDA
float after  : 3.1415925

double before : 3.14159265358979311600
double encoded: 0x400921FB54442D18
double after  : 3.14159265358979311600

Drugo vprašanje, ki ga imate, je, kako sestavite pakete? Na žalost, za vas, lahko prevajalnik prosto doda oblazinjenje po vsem mestu v strukturo, kar pomeni, da ne morete prenesti celotne stvari preko žice v enem kosu. (Ali se vam ne moti, da to ne morete storiti, to ne morete storiti?) Oprostite, da citiram prijatelja: “Kadarkoli gre kaj narobe, vedno kriva Microsoft.” Ta morda ni Microsoftova Krivda, seveda, vendar izjava prijatelja je povsem resnična.)

Nazaj na to: najboljši način pošiljanja strukture preko žice je, da vsako polje shranite neodvisno, nato pa jih razpakirate v strukturo, ko pridejo na drugo stran.

To je veliko dela, kaj misliš. Da, je. Ena stvar, ki jo lahko naredite, je, da napišete pomočno funkcijo, ki vam pomaga pri pakiranju podatkov. To bo zabavno! Resnično!

V knjigi “Praksa programiranja” Kernighana in Pikea izvajajo printf () podobne funkcije, imenovane pack () in unpack (), ki to storijo ravno to. Povezal bi se z njimi, vendar očitno te funkcije niso na spletu z ostalim vira iz knjige.

(Praksa programiranja je odlično branje. Zeus shrani koščico vsakič, ko ga priporočam.)

Na tej točki bom zapustil kazalec na API jezikov C API za tipizirane parametre BSD, ki ga nisem nikoli uporabljal, vendar se zdi popolnoma ugleden. Programatorji Python in Perl bodo želeli preveriti, ali so za jezikovne funkcije pack() in unpack() izpolnjene iste stvari. In Java ima velik-ol Serializable vmesnik, ki ga lahko uporabljate na podoben način.

Toda, če želite napisati lasten pripomoček za pakiranje v C, je K & P trik uporabljati spremenljive liste argumentov, da naredite printf() -like funkcije za izdelavo paketov. Tukaj je različica, ki sem jo sam skuhala na podlagi tega, kar upam, da bo dovolj, da vam predstavim, kako lahko takšna stvar deluje.

(Ta koda se nanaša na funkcije paketa 755 () zgoraj. Funkcije packi * () delujejo kot znana družina htons(), razen če se pakirajo v char znaki namesto na drugo celo število.)

#include <stdio.h>
#include <ctype.h>
#include <stdarg.h>
#include <string.h>

/*
** packi16() -- shranite 16-bitni int v char bufer (podobno htons())
*/ 
void packi16(unsigned char *buf, unsigned int i)
{
    *buf++ = i>>8; *buf++ = i;
}

/*
** packi32() -- shranite 32-bitni int v char buffer (podobno htonl())
*/ 
void packi32(unsigned char *buf, unsigned long int i)
{
    *buf++ = i>>24; *buf++ = i>>16;
    *buf++ = i>>8;  *buf++ = i;
}

/*
** packi64() -- shranite 64-bitni int v char buffer (podobno htonl())
*/ 
void packi64(unsigned char *buf, unsigned long long int i)
{
    *buf++ = i>>56; *buf++ = i>>48;
    *buf++ = i>>40; *buf++ = i>>32;
    *buf++ = i>>24; *buf++ = i>>16;
    *buf++ = i>>8;  *buf++ = i;
}

/*
** unpacki16() -- razpakirajte 16-bitni int iz čarobnega pufra (podobno ntohs())
*/ 
int unpacki16(unsigned char *buf)
{
    unsigned int i2 = ((unsigned int)buf[0]<<8) | buf[1];
    int i;

    // change unsigned numbers to signed
    if (i2 <= 0x7fffu) { i = i2; }
    else { i = -1 - (unsigned int)(0xffffu - i2); }

    return i;
}

/*
** unpacku16() -- razpakirajte 16-bitno nepodpisano iz vmesnega pomnilnika (podobno ntohs())
*/ 
unsigned int unpacku16(unsigned char *buf)
{
    return ((unsigned int)buf[0]<<8) | buf[1];
}

/*
** unpacki32() -- razpakirajte 32-bitni int iz vmesnega pomnilnika (podobno ntohl())
*/ 
long int unpacki32(unsigned char *buf)
{
    unsigned long int i2 = ((unsigned long int)buf[0]<<24) |
                           ((unsigned long int)buf[1]<<16) |
                           ((unsigned long int)buf[2]<<8)  |
                           buf[3];
    long int i;

    // change unsigned numbers to signed
    if (i2 <= 0x7fffffffu) { i = i2; }
    else { i = -1 - (long int)(0xffffffffu - i2); }

    return i;
}

/*
** unpacku32() -- razpakirajte 32-bitno nepodpisano iz vmesnega pomnilnika (podobno ntohl())
*/ 
unsigned long int unpacku32(unsigned char *buf)
{
    return ((unsigned long int)buf[0]<<24) |
           ((unsigned long int)buf[1]<<16) |
           ((unsigned long int)buf[2]<<8)  |
           buf[3];
}

/*
** unpacki64() -- razpakirajte 64-bitni int iz čarobnega pufra (podobno ntohl())
*/ 
long long int unpacki64(unsigned char *buf)
{
    unsigned long long int i2 = ((unsigned long long int)buf[0]<<56) |
                                ((unsigned long long int)buf[1]<<48) |
                                ((unsigned long long int)buf[2]<<40) |
                                ((unsigned long long int)buf[3]<<32) |
                                ((unsigned long long int)buf[4]<<24) |
                                ((unsigned long long int)buf[5]<<16) |
                                ((unsigned long long int)buf[6]<<8)  |
                                buf[7];
    long long int i;

    // spremenite nepodpisane številke v podpisano
    if (i2 <= 0x7fffffffffffffffu) { i = i2; }
    else { i = -1 -(long long int)(0xffffffffffffffffu - i2); }

    return i;
}

/*
** unpacku64() -- razpakirajte 64-bitno nepodpisano iz vmesnega pomnilnika (kot ntohl())
*/ 
unsigned long long int unpacku64(unsigned char *buf)
{
    return ((unsigned long long int)buf[0]<<56) |
           ((unsigned long long int)buf[1]<<48) |
           ((unsigned long long int)buf[2]<<40) |
           ((unsigned long long int)buf[3]<<32) |
           ((unsigned long long int)buf[4]<<24) |
           ((unsigned long long int)buf[5]<<16) |
           ((unsigned long long int)buf[6]<<8)  |
           buf[7];
}

/*
** pack() -- shrani podatke, ki jih narekuje formatni niz v medpomnilniku
**
**   bits |signed   unsigned   float   string
**   -----+----------------------------------
**      8 |   c        C         
**     16 |   h        H         f
**     32 |   l        L         d
**     64 |   q        Q         g
**      - |                               s
**
**  (16-bitna nepodpisana dolžina se samodejno pripne na nizov)
*/ 

unsigned int pack(unsigned char *buf, char *format, ...)
{
    va_list ap;

    signed char c;              // 8-bit
    unsigned char C;

    int h;                      // 16-bit
    unsigned int H;

    long int l;                 // 32-bit
    unsigned long int L;

    long long int q;            // 64-bit
    unsigned long long int Q;

    float f;                    // floats
    double d;
    long double g;
    unsigned long long int fhold;

    char *s;                    // strings
    unsigned int len;

    unsigned int size = 0;

    va_start(ap, format);

    for(; *format != '\0'; format++) {
        switch(*format) {
        case 'c': // 8-bit
            size += 1;
            c = (signed char)va_arg(ap, int); // promoted
            *buf++ = c;
            break;

        case 'C': // 8-bit unsigned
            size += 1;
            C = (unsigned char)va_arg(ap, unsigned int); // promoted
            *buf++ = C;
            break;

        case 'h': // 16-bit
            size += 2;
            h = va_arg(ap, int);
            packi16(buf, h);
            buf += 2;
            break;

        case 'H': // 16-bit unsigned
            size += 2;
            H = va_arg(ap, unsigned int);
            packi16(buf, H);
            buf += 2;
            break;

        case 'l': // 32-bit
            size += 4;
            l = va_arg(ap, long int);
            packi32(buf, l);
            buf += 4;
            break;

        case 'L': // 32-bit unsigned
            size += 4;
            L = va_arg(ap, unsigned long int);
            packi32(buf, L);
            buf += 4;
            break;

        case 'q': // 64-bit
            size += 8;
            q = va_arg(ap, long long int);
            packi64(buf, q);
            buf += 8;
            break;

        case 'Q': // 64-bit unsigned
            size += 8;
            Q = va_arg(ap, unsigned long long int);
            packi64(buf, Q);
            buf += 8;
            break;

        case 'f': // float-16
            size += 2;
            f = (float)va_arg(ap, double); // spodbujati
            fhold = pack754_16(f); // convert to IEEE 754
            packi16(buf, fhold);
            buf += 2;
            break;

        case 'd': // float-32
            size += 4;
            d = va_arg(ap, double);
            fhold = pack754_32(d); // convert to IEEE 754
            packi32(buf, fhold);
            buf += 4;
            break;

        case 'g': // float-64
            size += 8;
            g = va_arg(ap, long double);
            fhold = pack754_64(g); // convert to IEEE 754
            packi64(buf, fhold);
            buf += 8;
            break;

        case 's': // string
            s = va_arg(ap, char*);
            len = strlen(s);
            size += len + 2;
            packi16(buf, len);
            buf += 2;
            memcpy(buf, s, len);
            buf += len;
            break;
        }
    }

    va_end(ap);

    return size;
}

/*
** unpack() -- razpakirajte podatke, ki jih narekuje formatni niz v pufru
**
**   bits |signed   unsigned   float   string
**   -----+----------------------------------
**      8 |   c        C         
**     16 |   h        H         f
**     32 |   l        L         d
**     64 |   q        Q         g
**      - |                               s
**
**  (String je pridobljen na podlagi shranjene dolžine, vendar je lahko "s"
**  napolnjena z maksimalno dolžino)
*/
void unpack(unsigned char *buf, char *format, ...)
{
    va_list ap;

    signed char *c;              // 8-bit
    unsigned char *C;

    int *h;                      // 16-bit
    unsigned int *H;

    long int *l;                 // 32-bit
    unsigned long int *L;

    long long int *q;            // 64-bit
    unsigned long long int *Q;

    float *f;                    // floats
    double *d;
    long double *g;
    unsigned long long int fhold;

    char *s;
    unsigned int len, maxstrlen=0, count;

    va_start(ap, format);

    for(; *format != '\0'; format++) {
        switch(*format) {
        case 'c': // 8-bit
            c = va_arg(ap, signed char*);
            if (*buf <= 0x7f) { *c = *buf;} // re-sign
            else { *c = -1 - (unsigned char)(0xffu - *buf); }
            buf++;
            break;

        case 'C': // 8-bit unsigned
            C = va_arg(ap, unsigned char*);
            *C = *buf++;
            break;

        case 'h': // 16-bit
            h = va_arg(ap, int*);
            *h = unpacki16(buf);
            buf += 2;
            break;

        case 'H': // 16-bit unsigned
            H = va_arg(ap, unsigned int*);
            *H = unpacku16(buf);
            buf += 2;
            break;

        case 'l': // 32-bit
            l = va_arg(ap, long int*);
            *l = unpacki32(buf);
            buf += 4;
            break;

        case 'L': // 32-bit unsigned
            L = va_arg(ap, unsigned long int*);
            *L = unpacku32(buf);
            buf += 4;
            break;

        case 'q': // 64-bit
            q = va_arg(ap, long long int*);
            *q = unpacki64(buf);
            buf += 8;
            break;

        case 'Q': // 64-bit unsigned
            Q = va_arg(ap, unsigned long long int*);
            *Q = unpacku64(buf);
            buf += 8;
            break;

        case 'f': // float
            f = va_arg(ap, float*);
            fhold = unpacku16(buf);
            *f = unpack754_16(fhold);
            buf += 2;
            break;

        case 'd': // float-32
            d = va_arg(ap, double*);
            fhold = unpacku32(buf);
            *d = unpack754_32(fhold);
            buf += 4;
            break;

        case 'g': // float-64
            g = va_arg(ap, long double*);
            fhold = unpacku64(buf);
            *g = unpack754_64(fhold);
            buf += 8;
            break;

        case 's': // string
            s = va_arg(ap, char*);
            len = unpacku16(buf);
            buf += 2;
            if (maxstrlen > 0 && len > maxstrlen) count = maxstrlen - 1;
            else count = len;
            memcpy(s, buf, count);
            s[count] = '\0';
            buf += len;
            break;

        default:
            if (isdigit(*format)) { // track max str len
                maxstrlen = maxstrlen * 10 + (*format-'0');
            }
        }

        if (!isdigit(*format)) maxstrlen = 0;
    }

    va_end(ap);
}

In tukaj je demonstracijski program zgornje kode, ki pakira nekaj podatkov v buf in ga nato razdeli v spremenljivke. Upoštevajte, da pri klicanju unpack () z argumentom niza (določite format “s”) je pametno, da pred njim nastavite največje število dolžin, da preprečite prekoračitev presežka, npr. “96s”. Bodite pozorni pri razpakiranju podatkov, ki jih dobite prek omrežja – zlonamerni uporabnik lahko pošlje napačno izdelane pakete v naporu, da napadejo vaš sistem!

#include <stdio.h>

// različni bitji za vrste s plavajočo vejico--
// se razlikuje za različne arhitekture
typedef float float32_t;
typedef double float64_t;

int main(void)
{
    unsigned char buf[1024];
    int8_t magic;
    int16_t monkeycount;
    int32_t altitude;
    float32_t absurdityfactor;
    char *s = "Veliko neomejeno Zot! Našli ste Runestaff!";
    char s2[96];
    int16_t packetsize, ps2;

    packetsize = pack(buf, "chhlsf", (int8_t)'B', (int16_t)0, (int16_t)37, 
            (int32_t)-5, s, (float32_t)-3490.6677);
    packi16(buf+1, packetsize); // store packet size in packet for kicks

    printf("packet is %" PRId32 " bytes\n", packetsize);

    unpack(buf, "chhl96sf", &magic, &ps2, &monkeycount, &altitude, s2,
        &absurdityfactor);

    printf("'%c' %" PRId32" %" PRId16 " %" PRId32
            " \"%s\" %f\n", magic, ps2, monkeycount,
            altitude, s2, absurdityfactor);

    return 0;
}

Ne glede na to, ali imate svojo kodo ali pa uporabite nekoga drugega, je dobra zamisel, da imate splošen nabor paketov za pakiranje podatkov, da bi obdržali hrošče in ne vsakič zapakirali vsakega bitnika ročno.

Pri pakiranju podatkov, kakšen je dober format za uporabo? Odlično vprašanje. Na srečo RFC 4506, standard za predstavitev zunanjih podatkov, že definira binarne formate za množico različnih tipov, kot so vrste s plavajočo vejico, vrste integerov, nizi, neobdelani podatki itd. Predlagam, da se sklada s tem, če boste Podatke sami. Toda niste dolžni. Paketna policija ni ravno zunaj vaših vrat. Vsaj mislim, da niso.

Vsekakor pa kodiranje podatkov nekako ali drugače, preden ga pošljete, je pravi način za stvari!

7.5. Sindrom enkapsulacije podatkov

Kaj resnično pomeni zaokrožiti podatke? V najpreprostejšem primeru to pomeni, da boste na njej držali glavo z nekaterimi identifikacijskimi podatki ali dolžino paketa ali obema.

Kako bi moral biti vaš glava videti? No, to je samo nekaj binarnih podatkov, ki predstavljajo, kar menite, da je potrebno za dokončanje projekta.

Vau. To je nejasno.

V redu. Recimo, da imate program za večkratno uporabo, ki uporablja SOCK_STREAM. Ko uporabnik tipa (“pravi”) nekaj, je treba na strežnik prenesti dve informaciji: kaj je bilo rečeno in kdo je to rekel.

Do sedaj je tako dobro? “V čem je problem?” sprašujete.

Težava je, da so lahko sporočila različnih dolžin. Ena oseba, imenovana “tom”, bi lahko rekla: “Živjo” in lahko bi rekla še ena oseba, imenovana “Benjamin”: “Hej, kaj se dogaja?”

Torej send() vse te stvari strankam, ko pridejo. Vaš odhodni tok podatkov izgleda takole:

t o m H i B e n j a m i n H e y g u y s w h a t i s u p ?

In tako naprej. Kako stranka ve, kdaj se začne eno sporočilo in drugo ustavi? Lahko bi, če želite, vsa sporočila enake dolžine in samo pokličite sendall(), ki smo jo izvedli zgoraj. Toda to propada pasovno širino! Ne želimo send () 1024 bajtov tako, da lahko “tom” pove “Hi”.

Torej, podatke zajemamo v majhni glavi in strukturi paketov. Tako stranka kot strežnik vedo, kako pakirati in razpakirati (včasih imenovane “marshal” in “unmarshal”) te podatke. Ne poglej zdaj, vendar začenjamo definirati protokol, ki opisuje, kako se odjemalec in strežnik komunicirajo!

V tem primeru predpostavimo, da je uporabniško ime fiksna dolžina 8 znakov, označena z ‘\0′. In potem predpostavimo, da so podatki spremenljiva, do največ 128 znakov. Oglejmo si vzorčno strukturo paketov, ki bi jo lahko uporabili v tej situaciji:

  1. len (1 bajt, nepodpisano) -Skupna dolžina paketa, ki šteje 8-bajtno uporabniško ime in podatke o klepetu.
  2. name(8 bajtov) – uporabniško ime, NUL-oblazinjen, če je potrebno.
  3. chatdata (n-bajtov)-samih podatkov, ne več kot 128 bajtov. Dolžina paketa je treba izračunati kot dolžino teh podatkov plus 8 (dolžina imena polja, zgoraj).

Zakaj sem izbral 8-bajtne in 128-bajtne omejitve za polja? Izstrelil sem jih iz zraka, ob predpostavki, da bodo dovolj dolgo. Morda je 8 bajtov preveč omejujoče za vaše potrebe, lahko pa imate 30-byte ime polja ali karkoli. Izbira je odvisna od vas.

Z uporabo zgornje opredelitve paketa bi prvi paket vseboval naslednje podatke (v hex in ASCII):

   0A     74 6F 6D 00 00 00 00 00      48 69
(length)  T  o  m    (padding)         H  i

In druga je podobna:

   18     42 65 6E 6A 61 6D 69 6E      48 65 79 20 67 75 79 73 20 77 ...
(length)  B  e  n  j  a  m  i  n       H  e  y     g  u  y  s     w  ...

(Dolžina je shranjena v vrstnem redu omrežne baze, seveda. V tem primeru je samo en bajt, tako da ni pomembno, vendar na splošno želite, da se vsa binarna cela števila shranijo v vrstni red omrežnega bajta v svojih paketih. )

Ko pošiljate te podatke, bi morali biti varni in uporabiti ukaz, podoben sendall(), zgoraj, zato veste, da so vsi podatki poslani, tudi če je za pošiljanje () potrebnih več klicev, da bi jih vse prenesli.

Prav tako, ko prejemate te podatke, morate opraviti nekaj dodatnega dela. Če želite biti varni, morate domnevati, da boste morda prejeli delni paket (kot bi morda prejeli “18 42 65 6E 6A” iz Benjamina zgoraj, toda to je vse, kar smo dobili v tem pozivu za recv ()). Poklicati moramo recv () znova in znova, dokler paket ni v celoti sprejet.

Ampak kako? No, vemo, koliko bajtov moramo skupno prejeti, da je paket popolen, saj je to število na sprednji strani paketa. Vemo tudi, da je največja velikost paketa 1 + 8 + 128 ali 137 bajtov (ker smo tako opredelili paket).

Tu je dejansko nekaj stvari, ki jih lahko naredite tukaj. Ker veste, da se vsak paket začne z dolžino, lahko pokličete recv () samo, da dobite dolžino paketa. Potem, ko imate to, jo lahko pokličete znova, natančno določite preostalo dolžino paketa (po možnosti večkrat, da dobite vse podatke), dokler ne dobite celotnega paketa. Prednost te metode je, da potrebujete samo dovolj velik pufer, ki je dovolj velik za en paket, medtem ko je pomanjkljivost, da morate vsaj dvakrat poklicati recv (), da bi dobili vse podatke.

Druga možnost je, da pokličete recv () in recite, da je znesek, ki ste ga pripravljeni sprejeti, največje število bajtov v paketu. Potem karkoli dobite, jo držite na zadnji strani vmesnika in na koncu preverite, ali je paket končan. Seveda lahko dobite nekaj naslednjega paketa, zato boste morali imeti prostor za to.

Kaj lahko storite, je razglasiti dovolj velik obseg za dva paketa. To je vaša delovna paleta, kjer boste ob prihodu rekonstruirali pakete.

Vsakič, ko si podatke recv (), jo dodate v delovno pufru in preverite, ali je paket končan. To pomeni, da je število bajtov v pufru večje ali enako dolžini, navedeni v glavi (+1, ker dolžina v glavi ne vključuje bajta za dolžino same.) Če je število bajtov v varnostni pas je manjši od 1, paket ni popolnoma popoln, očitno. Za to morate narediti poseben primer, čeprav je prvi bajt smeti in se ne morete zanašati na pravilno dolžino paketa.

Ko je paket končan, lahko s tem storite, kaj boste. Uporabite ga in ga odstranite iz delovnega pufra.

Dajte! Ali ste že žongli v vaši glavi? No, tukaj je drugi od ene dva punch: morda ste prebrali mimo konca enega paketa in na naslednji v enem pozivu recv(). To pomeni, da imate delovni vmesni pomnilnik z enim celotnim paketom in nepopoln del naslednjega paketa! Prekleto vraga. (Ampak to je razlog, zakaj ste naredili delovni pufra, ki je dovolj velika, da imate dva paketa – v primeru, da se je to zgodilo!)

Ker poznate dolžino prvega paketa iz glave in ste spremljali število bajtov v delovnem pufru, lahko odštejete in izračunate, koliko bajtov v delovnem pufru spada v drugo (nepopolno ) paket. Ko ste ravnali s prvim, ga lahko izbrišete iz delovnega vmesnega pomnilnika in premaknete delni drugi paket navzdol do sprednje strani vmesnega pomnilnika, tako da je vse pripravljeno za naslednjo recv().

(Nekateri od vas bralci bodo opozorili, da dejansko premikanje delnega drugega paketa na začetek delovnega medpomnilnika potrebuje čas, program pa je lahko kodiran, da ga ne potrebujete z uporabo krožnega pufra. Na žalost, za ostale vas je razprava o Krožni odbojniki so izven področja uporabe tega članka. Če ste še vedno radovedni, zgrabi knjigo podatkovnih struktur in pojdite od tam.)

Nikoli nisem rekel, da je bilo enostavno. Ok, rekla sem, da je bilo enostavno. In to je; Potrebuješ samo prakso in kmalu bo prišla k vam naravno. Z Excaliburjem prisežem!

7.6. Oddajni paketi – Pozdravljeni, Svet!

Do sedaj je ta vodnik govoril o pošiljanju podatkov z enega gostitelja na drugega gostitelja. Vendar je mogoče, vztrajam, da lahko z ustreznim pooblastilom istočasno pošljete podatke več gostiteljem!

Z UDP (samo UDP, ne TCP) in standardnim protokolom IPv4, se to izvaja s pomočjo mehanizma, imenovanega broadcasting. Z IPv6 radiodifuzija ni podprta, zato se moraš zateči k pogosto boljši tehniki multicasting, ki se žal ne bom pogovarjal v tem trenutku. Ampak zadostna zvezdna očesna prihodnost – mi smo zaljubljeni v 32-bitni prisotni.

Ampak počakaj! Ne moreš samo zbežati in začeti oddajati Willy-nilly; Nastaviti morate vtičnico SO_BROADCAST, preden lahko v omrežju pošljete oddajo paketov. To je kot eden tistih majhnih plastičnih prevlek, ki so jih postavili prek stikala za izstrelitev izstrelkov! To je količina moči, ki jo držite v rokah!

Toda resno je, da obstaja nevarnost za uporabo oddajnih paketov, in sicer: vsak sistem, ki sprejema oddajni paket, mora razveljaviti vse čebulastne sloje podatkovne enkapsulacije, dokler ne ugotovi, na katerem pristanišču so podatki namenjeni. In potem prenese podatke ali jih zavrže. V obeh primerih je veliko dela za vsak stroj, ki sprejema oddajni paket, in ker je vse v lokalnem omrežju, je to lahko veliko strojev, ki opravljajo veliko nepotrebnega dela. Ko je prva igra Doom prišla ven, je bila pritožba glede njegove omrežne kode.

Zdaj, obstaja več kot en način, da se koža poči… Počakaj malo. Ali obstaja resnično več kot en način kože mačke? Kakšen izraz je to? Uh, in prav tako obstaja več kot en način pošiljanja oddajnega paketa. Torej, da bi prišli do mesa in krompirja celotne stvari: kako določite naslovni naslov za oddajno sporočilo? Obstajata dva načina:

  1. Pošljite podatke na oddajni naslov določenega podomrežja. To je omrežna številka podomrežja z vsemi enim bitji, ki so nastavljeni za gostiteljski del naslova. Na primer, doma je moje omrežje 192.168.1.0, moja netmask je 255.255.255.0, zato je zadnji bajt naslova moja številka gostitelja (ker so prvi trije bajti v skladu z omrežno masko omrežna številka). Moj naslov oddaja je torej 192.168.1.255. V Unixu vam bo ukaz ifconfig dejansko dal vse te podatke. (Če ste radovedni, bitna logika, da dobite svoj oddajni naslov network_number OR (NOT netmask).) To vrsto oddajnega paketa lahko pošljete v oddaljena omrežja in lokalno omrežje, vendar tvegate, da paket ki ga spusti ciljni usmerjevalnik. (Če jih ne bi zapustili, bi lahko nekaj naključnih smurfov začelo poplavljati svoj LAN z oddajnim prometom.)
  2. Pošljite podatke na naslov “globalno”. To je 255.255.255.255, aka INADDR_BROADCAST. Mnogi stroji bodo samodejno potisni IN to s svojo številko omrežja, ki jo bo pretvoril v omrežni oddajni naslov, nekateri pa ne. Spreminja se. Usmerjevalniki te vrste oddajnih paketov ne posredujejo iz vašega lokalnega omrežja, ironično dovolj.

Torej, kaj se zgodi, če poskušate poslati podatke na oddajni naslov, ne da bi najprej nastavili vtičnico SO_BROADCAST? No, poberimo dober stari talker in listener in poglejmo, kaj se zgodi.

$ talker 192.168.1.2 foo
sent 3 bytes to 192.168.1.2
$ talker 192.168.1.255 foo
sendto: Permission denied
$ talker 255.255.255.255 foo
sendto: Permission denied

Da, sploh ni srečen… ker nismo nastavili vtičnice SO_BROADCAST. Naredite to, in zdaj lahko sendto() kjerkoli želite!

Dejansko je to edina razlika med aplikacijo UDP, ki se lahko odda in ki ne more. Torej, vzemimo staro talker strežnik in dodamo še en razdelek, ki nastavi vtičnico SO_BROADCAST. Mi bomo poklicali ta program broadcaster.c:

/*
** broadcaster.c -- a datagram "client" like talker.c, except
**                  this one can broadcast
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define SERVERPORT 4950    // se bodo uporabniki pristanišč povezali

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in their_addr; // informacije o naslovu konektorja
    struct hostent *he;
    int numbytes;
    int broadcast = 1;
    //char broadcast = '1'; // če to ne deluje, poskusite to

    if (argc != 3) {
        fprintf(stderr,"usage: broadcaster hostname message\n");
        exit(1);
    }

    if ((he=gethostbyname(argv[1])) == NULL) {  // dobite informacije o gostitelju
        perror("gethostbyname");
        exit(1);
    }

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    // ta klic je tisto, kar omogoča pošiljanje oddajnih paketov:
    if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast,
        sizeof broadcast) == -1) {
        perror("setsockopt (SO_BROADCAST)");
        exit(1);
    }

    their_addr.sin_family = AF_INET;     // host byte order
    their_addr.sin_port = htons(SERVERPORT); // short, network byte order
    their_addr.sin_addr = *((struct in_addr *)he->h_addr);
    memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);

    if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,
             (struct sockaddr *)&their_addr, sizeof their_addr)) == -1) {
        perror("sendto");
        exit(1);
    }

    printf("sent %d bytes to %s\n", numbytes,
        inet_ntoa(their_addr.sin_addr));

    close(sockfd);

    return 0;
}

Kaj se razlikuje med tem in “normalno” situacijo odjemalca/strežnika UDP? Nič! (Razen, da je v tem primeru dovoljeno pošiljati oddaje paketov.) Kot tak, nadaljujte in zaženite stari programski listener UDP v enem oknu in broadcaster v drugem. Sedaj bi morali biti sposobni storiti vse tiste, ki niso uspeli, zgoraj.

$ broadcaster 192.168.1.2 foo
sent 3 bytes to 192.168.1.2
$ broadcaster 192.168.1.255 foo
sent 3 bytes to 192.168.1.255
$ broadcaster 255.255.255.255 foo
sent 3 bytes to 255.255.255.255

In morate videti, da se listener odzove, da je dobil pakete. (Če se listener ne odzove, se lahko zgodi, ker je vezan na naslov IPv6. Poskusite spremeniti AF_UNSPEC v seznamu listener.c v AF_INET in sili IPv4.)

No, to je nekaj vznemirljivo. Zdaj pa sprožite listener na drugem računalniku poleg vas v istem omrežju, da boste imeli dve kopiji, po eno na vsakem računalniku in spet zagnati broadcaster s svojim oddajnim naslovom… Hej! Oba listener dobita paket, čeprav ste enkrat poklicali samo sendto()! Cool!

Če listener dobi podatke, ki jih pošljete neposredno nanj, ne pa tudi podatki o oddajnem naslovu, lahko pride do požarnega zidu na vašem lokalnem računalniku, ki blokira pakete. (Da, Pat in Bapper, hvala, ker ste se zavedali, preden sem to storila, zato moja vzorčna koda ni delovala. Rekel sem, da vas bom omenil v vodniku, in tu ste. nyah.)

Spet bodite previdni pri oddajnih paketih. Ker bo vsak stroj na LANu prisiljen obravnavati paket, ne glede na to, ali ga recvfrom() ali ne, lahko predstavlja precej tovora celotnemu računalniškemu omrežju. Vsekakor jih je treba uporabljati zmerno in ustrezno.

8. Pogosta vprašanja

Kje lahko dobim datoteke z glavo?

Če jih še nimate v sistemu, jih verjetno ne potrebujete. Preverite priročnik za svojo platformo. Če gradite v sistemu Windows, morate vključiti samo #include <winsock.h>.

Kaj naj storim, ko bind() poroča “Naslov že v uporabi”?

Uporabiti morate setsockopt () z možnostjo SO_REUSEADDR v vtičnici za poslušanje. Oglejte si razdelek o povezovanju () in razdelek o izbiri () za primer.

Kako dobim seznam odprtih vtičnic v sistemu?

Uporabite netstat. Preverite stran man za vse podrobnosti, vendar morate vnesti nekaj dobrih rezultatov, samo natipkate:

$ netstat

Edini trik določa, katera vtičnica je povezana s katerim programom. :-)

Kako si lahko ogledam usmerjevalno tabelo?

Zaženite ukaz za pot (v /sbin na večini Linuxov) ali ukaz netstat -r.

Kako lahko zagnavam odjemalske in strežniške programe, če imam samo en računalnik? Ali ne potrebujem omrežja za pisanje omrežnih programov?

Na srečo, skoraj vsi stroji izvajajo loopback omrežno “napravo”, ki sedi v jedru in se pretvarja, da je omrežna kartica. (To je vmesnik, naveden kot “lo” v usmerjevalni tabeli.)

Pretvarjaj se, da si prijavljen v stroj, imenovan “goat”. Zaženite stranko v enem oknu in strežnik v drugem. Ali pa zaženite strežnik v ozadju (“server &“) in zaženite odjemalca v istem oknu. Povzetek naprave za povratne zanke je, da lahko bodisi client goat ali client localhost (saj je “localhost” verjetno opredeljen v vaši datoteki /etc/hosts), stranko pa se bo pogovarjal s strežnikom brez omrežja!

Skratka, za katero koli kodo ni potrebna nobena sprememba, ki bi jo lahko izvajali na enem samem omrežju brez omrežja! Huzah!

Kako naj povem, ali je oddaljena stran zaprta?

Poveste lahko, ker bo recv() vrnil 0.

Kako izvajam pripomoček “ping”? Kaj je ICMP? Kje lahko najdem več o surovih vtičnicah in SOCK_RAW?

Vsa vaša vprašanja o surovih vtičnicah bodo odgovorjena v W. Richard Stevensovih programskih knjigah UNIX Networking. Oglejte si tudi ping/ podimenik v izvorni kodi programa Stevens UNIX Networking, ki je na voljo na spletu.

Kako spremeniti ali skrajšati časovno omejitev klica za connect()?

Namesto da bi vam povedal ravno enak odgovor, ki bi vam ga dal W. Richard Stevens, vas bom lib/ connect_nonb.c v izvorni kodi UNIX omrežnega programja.

Bistvo tega je, da naredite vtičnico socket z socket(), jo nastavite na ne-blokiranje, pokličite connect() in če vse gre dobro connect() bo takoj vrnil -1, errno pa bo nastavljen na EINPROGRESS. Nato pokličete select() s katerim koli časovnim časom, ki ga želite, pri čemer deskriptor vtičnice v bralnih in zapisovalnih nizih. Če ne prekine, pomeni, da je klic vzpostavljen. Na tej točki boste morali uporabiti getockopt() z možnostjo SO_ERROR, da dobite povratno vrednost iz klica connect(), ki bi morala biti nič, če ni bilo napake.

Nazadnje boste verjetno želeli, da se vtičnica znova blokira, preden začnete prenašati podatke prek nje.

Upoštevajte, da ima to dodatno prednost, ker program omogoča, da naredi nekaj drugega, medtem ko se povezuje. Lahko bi na primer nastavili časovno omejitev na nekaj nizkega, na primer 500 ms, in vsakič posodobite indikator na zaslonu, nato ponovno pokličite select(). Ko pokličete select() in časovno omejitev, recimo 20-krat, boste vedeli, da je čas, da prekinete povezavo.

Kot sem rekel, preverite izvor Stevensa za odličen primer.

Kako lahko izdelam sistem Windows?

Najprej izbrišite Windows in namestite Linux ali BSD. } ;-). Ne, pravzaprav samo v razdelku o stavbi za Windows v uvodu.

Kako zgradim za Solaris/SunOS? Nadaljujem z napakami povezovalca, ko poskušam sestaviti!

Napake povezovalca se zgodijo, ker Sunbox-i ni samodejno sestavljen v knjižnicah vtičnic. Oglejte si poglavje o zgradbi za Solaris/SunOS v uvodu za primer, kako to storiti.

Zakaj select() še naprej pada na signal?

Signali pogosto povzročijo blokirane sistemske klice za vrnitev -1 z errno nastavljenim na EINTR. Ko nastavite obdelovalec signala s sigaction (), lahko nastavite zastavo SA_RESTART, ki naj bi znova zagnala sistemski klic po prekinitvi.

Seveda to ne deluje vedno.

Moja najljubša rešitev za to vključuje izjavo goto. Saj veš, da to razdira svoje profesorje do konca, zato pojdi na to!

select_restart:
if ((err = select(fdmax+1, &readfds, NULL, NULL, NULL)) == -1) {
    if (errno == EINTR) {
        // neki signal nas je prekinil, zato ga ponovno zaženite
        goto select_restart;
    }
    // tukaj je resnična napaka:
    perror("select");
} 

Seveda vam v tem primeru ni treba uporabljati goto; Lahko uporabite druge strukture za nadzor. Ampak mislim, da je izjava goto dejansko čistejša.

Kako lahko izvajam časovno omejitev za klic na recv()?

Uporabite select()! Omogoča vam, da določite časovni parameter za deskriptorje sokov, iz katerega želite brati. Lahko pa celotno funkcijo zavijete v eno samo funkcijo, na primer:

#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>

int recvtimeout(int s, char *buf, int len, int timeout)
{
    fd_set fds;
    int n;
    struct timeval tv;

    // nastavite datotečni deskriptor
    FD_ZERO(&fds);
    FD_SET(s, &fds);

    // nastavite struct timeval za časovno omejitev
    tv.tv_sec = timeout;
    tv.tv_usec = 0;

    // wait until timeout or data received
    n = select(s+1, &fds, NULL, NULL, &tv);
    if (n == 0) return -2; // timeout!
    if (n == -1) return -1; // error

    // podatki morajo biti tukaj, zato naredite normalno recv()
    return recv(s, buf, len, 0);
}
.
.
.
// Vzorec klic v recvtimeout():
n = recvtimeout(s, buf, sizeof buf, 10); // 10 second timeout

if (n == -1) {
    // prišlo je do napake
    perror("recvtimeout");
}
else if (n == -2) {
    // prišlo je do zakasnitve
} else {
    // dobil nekaj podatkov v buf
}
.
.
. 

Upoštevajte, da se recvtimeout() v primeru časovne omejitve vrne -2. Zakaj ne bi vrnili 0? No, če se spomnite, je povratna vrednost 0 na pozivu za recv() pomeni, da je oddaljena stran zaprla povezavo. Torej, za to vrnjeno vrednost že govorimo, in -1 pomeni “napako”, zato sem izbral -2 kot svoj časovni indikator.

Kako šifriram ali stisnem podatke, preden jih pošljem skozi vtičnico?

Eden preprostih načinov za šifriranje je uporaba SSL-ja (varna vtičnica), vendar to ni v obsegu tega vodnika. (Za več informacij si oglejte projekt OpenSSL.)

Toda ob predpostavki, da želite priključiti ali implementirati svoj kompresor ali šifrirni sistem, je samo razmišljanje o vaših podatkih, ki potekajo skozi zaporedje korakov med obema koncema. Vsak korak na nek način spremeni podatke.

  1. strežnik bere podatke iz datoteke (ali kjerkoli)
  2. strežnik šifrira/stisne podatke (dodate ta del)
  3. strežnik send() s šifriranimi podatki

Zdaj pa obratno:

  1. odjemalec recv() s šifriranih podatkov
  2. dešifrira/dekompresira podatke odjemalca (dodate ta del)
  3. stranka zapisuje podatke v datoteko (ali kjerkoli)

Če boste stisnili in šifrirali, najprej ne zapišite stiskanja. :-)

Dokler stranka pravilno razveljavi, kaj stori, bo stanje na koncu konec, ne glede na to, koliko vmesnih korakov dodate.

Torej, vse, kar morate storiti, da uporabite mojo kodo, je najti mesto med tem, kje se podatki berejo in podatki se pošiljajo (z uporabo send()) preko omrežja, in vnesite kodo, ki tam šifrira.

Kaj je to “PF_INET”, ki ga vidim? Je to povezano z AF_INET?

Ja, ja. Za podrobnosti si oglejte razdelek socket().

Kako lahko napišem strežnik, ki sprejema ukaze lupine od odjemalca in jih izvaja?

Za poenostavitev lahko rečemo, da sta povezava connect() s, send() s in close() s povezava (to pomeni, da poznejših sistemskih klicev brez ponovnega povezovanja odjemalca ni.)

Postopek, ki mu ga sledi, je naslednji:

  1. connect() na strežnik
  2. send(“/sbin/ls > /tmp/client.out”)
  3. close() povezavo

Medtem strežnik obdeluje podatke in jih izvaja:

  1. accept() povezavo od stranke
  2. recv(str) ukazni niz
  3. close() povezavo
  4. system(str) za zagon ukaza

Pazite! Obstajati, da strežnik izvede, kar pravi odjemalec, je, kot da daje oddaljeni dostop do lupine, in ljudje lahko v računu delajo stvari, ko se povezujejo s strežnikom. Na primer, v zgornjem primeru, kaj če odjemalec pošlje “rm -rf ~“? Izbriše vse v računu, to je tisto!

Torej dobite pametno in strankam preprečite, da bi uporabljali katerokoli drugo, razen za nekaj pripomočkov, za katere veste, da so varni, na primer foobar korist:

if (!strncmp(str, "foobar", 6)) {
    sprintf(sysstr, "%s > /tmp/server.out", str);
    system(sysstr);
} 

Toda še vedno ste nevarni, žal: kaj če stranka vstopi v “foobar; rm -rf ~“? Najvarnejša stvar je, da napišemo malo rutino, ki v argumentih za ukaz namesti znak za izhod v sili (“\”) pred vsemi ne-alfanumeričnimi znaki (vključno s presledki, če je to primerno).

Kot vidite, je varnost zelo velika težava, ko strežnik začne izvajati stvari, ki jih odjemalec pošilja.

Pošiljam množico podatkov, a ko sem recv(), prejmejo samo 536 bajtov ali 1460 bajtov hkrati. Ampak, če ga zaganjam na lokalnem računalniku, hkrati dobim vse podatke. Kaj se dogaja?

Hitreje pritisnete na MTU-največjo velikost, ki jo lahko nosi fizični medij. Na lokalnem računalniku uporabljate napravo za povratno zanko, ki lahko obdeluje 8K ali več brez problema. Toda na ethernetu, ki lahko obvlada le 1500 bajtov z glavo, si zadel to omejitev. Nad modemom, s 576 MTU (spet z glavo), ste zadeli še nižjo mejo.

Najprej morate poskrbeti, da bodo vsi podatki poslani. (Za podrobnosti glej izvedbo funkcije sendall(). Ko ste prepričani o tem, potem morate poklicati recv() v zanki, dokler niso vsi vaši podatki brati.

Preberite poglavje Son of Data Encapsulation za podrobnosti o prejemu popolnih paketov podatkov z več klicev na recv().

Jaz sem na okencu Windows in nimam sistemskega klica fork () ali kakršnega koli strukturnega pomena. Kaj storiti?

Če so kje drugje, bodo v knjižnicah POSIX, ki so morda poslane s svojim prevajalnikom. Ker nimam okenca Windows, vam resnično ne morem povedati odgovora, vendar se mi zdi, da se spomnimo, da Microsoft ima sloj združljivosti POSIX in to je, če bi bila fork (). (In morda celo sigaction.)

Poiščite pomoč, ki je bila priložena VC ++ za “vilice” ali “POSIX”, in preverite, ali vam poda kakršne koli sledi.

Če to sploh ne deluje, odklopite fork()/sigaction stvari in ga nadomestite z ekvivalentom Win32: CreateProcess (). Ne vem, kako uporabljati CreateProcess() – potrebuje bazilionske argumente, vendar jih je treba vključiti v dokumente, ki so bili priloženi VC ++.

Jaz sem za požarni zid, kako naj ljudem zunaj požarnega zidu poznam svoj naslov IP, da se lahko povežejo z mojim računalnikom?

Na žalost je namen požarnega zidu preprečiti, da bi se ljudje zunaj požarnega zidu povezali z napravami znotraj požarnega zidu, zato jim je omogočeno, da se to v bistvu šteje za kršitev varnosti.

To ne pomeni, da je vse izgubljeno. Na eni strani lahko še vedno connect() preko požarnega zidu, če gre za nekakšno masquerading ali NAT ali kaj podobnega. Samo oblikujte svoje programe, tako da boste vedno tisti, ki sproži povezavo, in vam bo v redu.

Če to ni zadovoljivo, lahko zaprosite, da vaši sysadmini zapreti luknjo v požarnem zidu, da se bodo ljudje lahko povezali z vami. Požarni zid vam lahko pošlje bodisi prek svoje programske opreme NAT bodisi preko proxyja ali podobnega.

Zavedajte se, da luknja v požarnem zidu ni nič, kar je treba vzeti rahlo. Zagotoviti morate, da ljudem ne daste dostopa do notranjega omrežja; če ste začetnik, je veliko težje narediti programsko opremo varno, kot si mislite.

Ne skrbi za tvojega sysadmina. ;-)

Kako napišem sniffer v paketu? Kako vnesem svoj ethernet vmesnik v brezpotjujoči način?

Za tiste, ki ne vedo, ko je omrežna kartica v “promiskuni način”, bo posredovala VSE pakete operacijskemu sistemu, ne samo tistim, ki so bili naslovljeni na ta določen računalnik. (Govorimo o eternetskih naslovih, ne naslovih IP, ampak ker je ethernet nižji od IP-ja, so tudi vsi IP-naslovi učinkovito posredovani. Za več informacij si oglejte poglavje Nizka raven nesmisel in mrežna teorija.)

To je osnova za delovanje souporabnika paketa. Vmesnik postavi v promiskuni način, nato pa OS dobi vsak posamezen paket, ki gre na žico. Imeli boste vtičnico neke vrste, iz katere lahko preberete te podatke.

Na žalost se odgovor na vprašanje razlikuje glede na platformo, vendar če ste Google za, na primer, “windows promiscuous ioctl”, boste verjetno dobili nekje. Obstaja tisto, kar izgleda kot dostojno pisanje v Linux Journal, kot tudi.

Kako lahko nastavim časovno omejitev po meri za vtičnico TCP ali UDP?

Odvisno je od vašega sistema. Morda iščete omrežje za SO_RCVTIMEO in SO_SNDTIMEO (za uporabo s setsockopt()), da vidite, ali vaš sistem podpira takšno funkcionalnost.

Stran za mano Linuxa predlaga uporabo alarm() ali setitimer() kot nadomestka.

Kako naj povem, katera vrata so na voljo za uporabo? Ali obstaja seznam “uradnih” številk vrat?

Ponavadi to ni težava. Če pišete, recimo, spletni strežnik, potem je dobro, da uporabite znana vrata 80 za vašo programsko opremo. Če pišete samo svoj lasten specializiran strežnik, nato izberite naključno izbrano pristanišče (vendar večje od 1023) in poskusite ga.

Če je pristanišče že v uporabi, boste pri poskusu bind() dobili napako “Naslov že v uporabi”. Izberite drugo pristanišče. (Dobra ideja je, da uporabniku programske opreme omogočite, da določi nadomestna vrata s konfiguracijsko datoteko ali stikalom v ukazni vrstici.)

Obstaja seznam uradnih številk vrat, ki jih vzdržuje organ za dodeljene številke na internetu (IANA). Samo zato, ker nekaj (več kot 1023) v tem seznamu ne pomeni, da ne morete uporabljati pristanišča. Na primer, DOOM-ov program DOOM uporablja isto pristanišče kot “mdqs”, karkoli je to. Vse je pomembno, da nihče drug na istem računalniku ne uporablja tega pristanišča, kadar ga želite uporabiti.

9. Man strani

V Unixovem svetu obstaja veliko priročnikov. Imajo majhne dele, ki opisujejo posamezne funkcije, ki jih imate na voljo.

Seveda bi bilo priročno prevec stvar, ki bi jo bilo treba natipati. Mislim, nihče v svetu Unixa, vključno z mano, rad všeč. Dejansko bi lahko nadaljeval in v veliki meri o tem, koliko raje raje, ampak na kratko in ne bom vam dolgčas z dolgimi vetrovi o tem, kako popolnoma iznenađujuće bi bilo, da sem v skoraj vseh okoliščinah v celoti.

[Aplavz]

Hvala vam. O čem prihajajo, je, da se te strani v Unix svetu imenujejo »man strani« in tu sem vključil svojo osebno skrajšano različico za vaše branje užitek. Stvar je v tem, da so mnoge od teh funkcij bolj splošni namen, kot sem jih pustil, vendar bom predstavil samo dele, ki so pomembni za programiranje internetnih vtičnic.

Ampak počakaj! To ni vse, kar je narobe z mojimi stranmi:

  • Nepopolni so in prikazujejo le osnove iz priročnika.
  • V resničnem svetu je veliko več man stran od tega.
  • Drugačne so od tistih v vašem sistemu.
  • Datoteke glave so lahko različne za nekatere funkcije v vašem sistemu.
  • Parametri funkcije so lahko različni za nekatere funkcije v vašem sistemu.

Če želite resnične informacije, preverite svoje lokalne strani Unixa, tako da vnesete man karkoli, kjer je “karkoli” nekaj, za kar ste neverjetno zainteresirani, na primer “sprejem”. (Prepričan sem, da ima Microsoft Visual Studio nekaj podobnega v svojem oddelku za pomoč. Toda “človek” je boljši, ker je en bajt bolj jedrnat kot “pomoč”.

Torej, če so to napačne, zakaj jih celo sploh vključite v vodnik? No, obstaja nekaj razlogov, vendar je najboljše, da (a) so te različice posebej usmerjene v mrežno programiranje in jih je lažje prebaviti kot resnične, in (b) te različice vsebujejo primere!

Oh! Če govorim o primerih, ne skušam v celoti preveriti napake, ker resnično poveča dolžino kode. Vendar morate popolnoma preveriti napake, kadar koli delate sistemske klice, razen če ste popolnoma 100% prepričani, da to ne bo uspelo, verjetno pa bi morali storiti tudi takrat!

 9.1. accept()

Sprejemajte dohodno povezavo v vtičnici za poslušanje

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

Opis

Ko enkrat preizkusite težave pri pridobivanju vtičnice SOCK_STREAM in ga nastavite za dohodne povezave s listen (), potem pokličete accept(), da dejansko dobite nov deskriptor sokov, ki ga boste uporabili za nadaljnjo komunikacijo z novo povezanim odjemalcem.

Stara vtičnica, ki jo uporabljate za poslušanje, je še vedno prisotna in bo uporabljena za nadaljnje sprejemanje accept(), ko pridejo.

s The listen()ing deskriptor vtičnice.
addr To se izpolni z naslovom spletnega mesta, ki je
povezovanje z vami.
addrlen To je izpolnjeno z sizeof() na
struktura vrnjena v addr parameter. Lahko varno
Prezrite, če mislite, da ste dobili
struct
sockaddr_in
nazaj, kar veste, da ste, kajti to je tip
Za mimo addr.

accept () bo ponavadi blokiral, lahko pa uporabite select () in si ogledate, ali je pripravljen za branje. Če je tako, potem obstaja nova povezava, ki čaka na accept()! Juhu! Lahko pa nastavite tudi zastavo O_NONBLOCK v vtičnico za poslušanje z uporabo fcntl() in potem ne bo nikoli blokirala, namesto da bi se vrnila -1 z errno nastavljeno na EWOULDBLOCK.

Deskriptor vtičnice, ki ga vrne accept(), je deskriptor bona fide, ki je odprt in povezan z oddaljenim gostiteljem. Zapreti jo close(), ko končate z njo.

Vrnjena vrednost

accept() vrne novo povezani deskriptor vtiča ali -1 pri napaki, z ustrezno nastavitvijo errno.

Primer

struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;

// najprej dodajte naslove struktur z getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

getaddrinfo(NULL, MYPORT, &hints, &res);

// naredite vtičnico, jo povežite in poslušajte:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);

// zdaj sprejmite dohodno povezavo:

addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

// pripravljen za komunikacijo na deskriptorju socketa new_fd!

Poglej tudi

socket(), getaddrinfo(), listen(), struct sockaddr_in

9.2. bind()

Pridružite vtičnico z naslovom IP in številko vrat

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

Opis

Ko se oddaljeni računalnik želi povezati s strežniškim programom, potrebuje dva podatka: naslov IP in številko vrat. Klic bind() vam omogoča, da storite samo to.

Najprej pokličete getaddrinfo(), da naložite struct sockaddr z naslovnim naslovom in podatki o pristaniščih. Potem pokličete socket(), da dobite deskriptor vtiča in nato prenesete vtičnico in naslov v povezavo(), IP naslov in vrata pa so čarobno (z uporabo dejanske magije), ki se vežejo v vtičnico!

Če ne poznate svojega IP-naslova ali če v napravi imate samo en naslov IP-ja ali vam ni všeč, kateri od naslovov IP-naslova naprave je uporabljen, lahko preprosto prenesete zastavico AI_PASSIVE v parameter za namige na getaddrinfo(). Kaj to naredi je, da izpolnite del IP-naslov struct sockaddr s posebno vrednostjo, ki pove bind(), da mora samodejno izpolniti IP-naslov tega gostitelja.

Kaj kaj? Kakšna posebna vrednost je naložena v IP naslov struct sockaddr, da bo samodejno izpolnjevala naslov s trenutnim gostiteljem? Povedal vam bom, vendar upoštevajte, da je to le, če ste ročno izpolnili struct sockaddr; če ne, uporabite rezultate getaddrinfo(), kot je navedeno zgoraj. V IPv4 je polje sin_addr.s_addr strukture struct sockaddr_in nastavljeno na INADDR_ANY. V IPv6 je polje sin6_addr strukture struct sockaddr_in6 dodeljeno iz globalne spremenljivke in6addr_any. Ali pa, če deklarirate novo strukturo in6_addr, jo lahko inicializirate na IN6ADDR_ANY_INIT.

Nazadnje, parameter addrlen mora biti nastavljen na sizeof my_addr.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

// sodoben način opravljanja stvari z getaddrinfo()

struct addrinfo hints, *res;
int sockfd;

// najprej dodajte naslove struktur z getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // uporabite IPv4 ali IPv6, kar koli
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // izpolnite moj IP za mene

getaddrinfo(NULL, "3490", &hints, &res);

// naredite vtičnico:
// (morate dejansko hoditi po "res" povezanem seznamu in preverjanju napak!)

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// ga povežite s pristaniščem, v katerega smo vstopili, da dobimo getaddrinfo():

bind(sockfd, res->ai_addr, res->ai_addrlen);
// primer pakiranja strukture z roko, IPv4

struct sockaddr_in myaddr;
int s;

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(3490);

// lahko določite naslov IP:
inet_pton(AF_INET, "63.161.169.137", &(myaddr.sin_addr));

// ali ga lahko samodejno izberete:
myaddr.sin_addr.s_addr = INADDR_ANY;

s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&myaddr, sizeof myaddr);

Poglej tudi

getaddrinfo(), socket(), struct sockaddr_in, struct in_addr

9.3. connect()

Povežite vtičnico s strežnikom

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *serv_addr,
            socklen_t addrlen);

Opis

Ko naredite vtičnico socket s klicem socket(), lahko connect() to vtičnico z oddaljenim strežnikom z uporabo imenovanega sistemskega klica connect(). Vse, kar morate storiti, je, da mu pošljete deskriptor vtičnice in naslov strežnika, ki vas zanima bolje spoznati. (Oh in dolžina naslova, ki se običajno prenese na takšne funkcije.)

Običajno se te informacije pojavijo kot rezultat klica za getaddrinfo(), vendar lahko, če želite, izpolnite svoj lastni struct sockaddr.

Če v deskriptorju vtičnice še niste klicali bind(), se samodejno poveže z vašim naslovom IP in naključnim lokalnim vratom. To je ponavadi v redu z vami, če niste strežnik, saj vam res ne zanima, kakšna je vaša lokalna vrata; samo skrbi, kaj je oddaljena vrata, zato jo lahko postavite v parameter serv_addr. Lahko pokličete bind(), če resnično želite, da je vaša vtičnica za stranke na določenem IP-naslovu in pristanišču, vendar je to zelo redko.

Ko je vtičnica connect() ed, lahko send() in recv() podatke o njem na vsebino vašega srca.

Posebna opomba: če connect() UOCK vtič SOCK_DGRAM na oddaljeni gostitelj, lahko uporabite send() in recv() ter sendto() in recvfrom(). Če želiš.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

// connect to www.example.com port 80 (http)

struct addrinfo hints, *res;
int sockfd;

// najprej z naslovom getaddrinfo () naložite naslovne strukture():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;

// lahko namesto "http" v naslednji vrstici postavimo "80":
getaddrinfo("www.example.com", "http", &hints, &res);

// naredite vtičnico:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// ga povežite z naslovom in pristaniščem, ki smo ga prenesli v getaddrinfo():

connect(sockfd, res->ai_addr, res->ai_addrlen);

Poglej tudi

socket(), bind()

9.4. close()

Zaprite deskriptor vtičnice

Prototipi

#include <unistd.h>

int close(int s);

Opis

Ko končate s pomočjo vtičnice za kakršnokoli dementno shemo, ki ste jo sestavili, in ne želite send() ali recv(), ali pa sploh karkoli naredite s vtičnico, jo lahko close() in osvobojena bo, nikoli več ne bo uporabljena.

Oddaljena stran lahko pove, ali se to zgodi na dva načina. Ena: če oddaljena stran pokliče recv(), bo vrnila 0. Dva: če oddaljena stran kliče send(), bo prejela signal SIGPIPE in send() se bo vrnil -1 in errno bo nastavljen na EPIPE.

Uporabniki operacijskega sistema Windows: funkcija, ki jo potrebujete, se imenuje closesocket (), ne pa close(). Če poskušate uporabiti close() v deskriptorju vtičnice, je mogoče, da se bo Windows razjezil… In vam ni všeč, ko je jezen.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

s = socket(PF_INET, SOCK_DGRAM, 0);
.
.
.
// veliko lotta stvari...*BRRRONNNN!*
.
.
.
close(s);  // res ne.

Poglej tudi

socket(), shutdown()

9.5. getaddrinfo(), freeaddrinfo(), gai_strerror()

Pridobite informacije o imenu in/ali storitvi gostitelja in naložite struct sockaddr z rezultatom.

Prototipi

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *nodename, const char *servname,
                const struct addrinfo *hints, struct addrinfo **res);

void freeaddrinfo(struct addrinfo *ai);

const char *gai_strerror(int ecode);

struct addrinfo {
  int     ai_flags;          // AI_PASSIVE, AI_CANONNAME, ...
  int     ai_family;         // AF_xxx
  int     ai_socktype;       // SOCK_xxx
  int     ai_protocol;       // 0 (auto) or IPPROTO_TCP, IPPROTO_UDP 

  socklen_t  ai_addrlen;     // dolžina ai_addr
  char   *ai_canonname;      // kanonično ime za nodename
  struct sockaddr  *ai_addr; // binarni naslov
  struct addrinfo  *ai_next; // naslednjo strukturo na povezanem seznamu
};

Opis

getaddrinfo() je izvrstna funkcija, ki bo vrnila podatke o določenem gostitelju (na primer njenemu IP naslovu) in naložila strukturo sockaddr za vas, skrbela za podrobne podatke (npr. Če je to IPv4 ali IPv6.) Nadomesti Stare funkcije gethostbyname () in getservbyname (). Opis spodaj vsebuje veliko informacij, ki bi lahko bile malo zastrašujoče, dejanska uporaba pa je precej preprosta. Morda bi bilo vredno, da najprej preverite primere.

Ime gostitelja, ki vas zanima, gre v parameter nodename. Naslov je lahko ime gostitelja, na primer “www.example.com” ali naslov IPv4 ali IPv6 (posredovan kot niz). Ta parameter je lahko tudi NULL, če uporabljate zastavico AI_PASSIVE (glejte spodaj).

Parameter servname je v bistvu številka vrat. Lahko je številka vrat (sprejeta kot niz, npr. “80”), ali pa je lahko ime storitve, na primer “http” ali “tftp” ali “smtp” ali “pop” itd. lahko najdete na seznamu pristanišč IANA ali v datoteki /etc/services.

Končno, za vhodne parametre imamo namige. To je res, kjer lahko določite, kaj bo storila funkcija getaddrinfo(). Nič celotne strukture pred uporabo z memset(). Oglejmo si polja, ki jih morate nastaviti pred uporabo.

Ai_flags je mogoče nastaviti na različne stvari, vendar je nekaj pomembnih. (Večkratne zastavice lahko določite z bitno-OR jih skupaj z operaterjem.) Preverite svojo stran s strani za celoten seznam zastavic.

AI_CANONNAME povzroči, da se ai_canonname rezultata izpopolni s kanonskim (dejanskim) imenom gostitelja. AI_PASSIVE povzroči, da se IP-naslov rezultata zapolni z INADDR_ANY (IPv4) ali in6addr_any (IPv6); to povzroči poznejši klic pri povezovanju (), da samodejno izpolni naslov IP strukture sockaddr z naslovom trenutnega gostitelja. To je odlično za nastavitev strežnika, ko ne želite trdno kodirati naslova.

Če uporabljate zastavo AI_PASSIVE, lahko v nodename prenese NULL (ker ga bo bind() za vas pozneje izpolnil.)

Če nadaljujete z vnosnimi parametri, boste verjetno želeli nastaviti ai_family na AF_UNSPEC, ki pove, da getaddrinfo() poišče naslove IPv4 in IPv6. Lahko se tudi omejite na enega ali drugega z AF_INET ali AF_INET6.

Nato je treba polje socktype nastaviti na SOCK_STREAM ali SOCK_DGRAM, odvisno od katere vrste vtičnice želite.

Nazadnje, pustite ai_protocol na 0, da samodejno izberete vrsto protokola.

Zdaj, ko dobite vse te stvari tam, lahko končno pokličete na getaddrinfo()!

Seveda se tukaj začne zabava. Res bo zdaj usmeril na povezani seznam struct addrinfos in skozi ta seznam lahko preberete vse naslove, ki se ujemajo s tem, kar ste prenesli z namigi.

Zdaj je mogoče dobiti nekaj naslovov, ki ne delujejo zaradi enega ali drugega razloga, zato je to, kaj naredi Linux man page, z listanjem klicanje socket() in connect() (ali bind()), ponovno nastavite strežnik z zastavico AI_PASSIVE), dokler ne uspe.

Končno, ko končate s povezanim seznamom, morate pokličite freeaddrinfo(), da sprostite pomnilnik (ali bo ušel, nekateri ljudje pa bodo vznemirjeni.)

Vrnjena vrednost

Vrne nič na uspeh, ali nenavadno pri napaki. Če vrne nenavadno, lahko uporabite funkcijo gai_strerror(), da v povratni vrednosti dobite različico, ki jo je mogoče natisniti.

Primer

// kodo za odjemalca, ki se povezuje s strežnikom
// in sicer vtičnica za pretok www.example.com na port 80 (http)
// either IPv4 or IPv6

int sockfd;  
struct addrinfo hints, *servinfo, *p;
int rv;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use AF_INET6 to force IPv6
hints.ai_socktype = SOCK_STREAM;

if ((rv = getaddrinfo("www.example.com", "http", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    exit(1);
}

// zopet prek vseh rezultatov in se povežite s prvim, kar lahko
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype,
            p->ai_protocol)) == -1) {
        perror("socket");
        continue;
    }

    if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        perror("connect");
        close(sockfd);
        continue;
    }

    break; // če pridemo sem, smo se morali uspešno povezati
}

if (p == NULL) {
    // na koncu seznama ni bilo povezave
    fprintf(stderr, "failed to connect\n");
    exit(2);
}

freeaddrinfo(servinfo); // vse je bilo opravljeno s to strukturo
// kodo za strežnik, ki čaka na povezave
// in sicer tokovno vtičnico na pristanišču 3490, na tem gostiteljevem IP-naslovu
// bodisi IPv4 ali IPv6.

int sockfd;  
struct addrinfo hints, *servinfo, *p;
int rv;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // uporabite AF_INET6 za silo IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // uporabite moj naslov IP

if ((rv = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
    exit(1);
}

// zanke skozi vse rezultate in se povežite s prvim, kar lahko
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype,
            p->ai_protocol)) == -1) {
        perror("socket");
        continue;
    }

    if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        close(sockfd);
        perror("bind");
        continue;
    }

    break; // če pridemo sem, smo se morali uspešno povezati
}

if (p == NULL) {
    // zanemarili konec seznama brez uspešnega povezovanja
    fprintf(stderr, "failed to bind socket\n");
    exit(2);
}

freeaddrinfo(servinfo); // vse je bilo opravljeno s to strukturo

Poglej tudi

gethostbyname(), getnameinfo()

9.6. gethostname ()

Vrne ime sistema

Prototipi

#include <sys/unistd.h>

int gethostname(char *name, size_t len);

Opis

Vaš sistem ima ime. Vsi delajo. To je nekoliko bolj Unixy stvar kot ostalo mrežno stvari, o katerih smo govorili, vendar še vedno ima svoje uporabe.

Na primer, lahko dobite ime gostitelja in pokličite gethostbyname(), če želite izvedeti svoj naslov IP.

Ime parametra bi moralo usmeriti na varovalo, ki bo imelo ime gostitelja, len pa je velikost tega vmesnika v bajtih. Gethostname () ne bo prepisal konca vmesnega pomnilnika (lahko bi vrnil napako ali pa bi le prenehal pisati), NUL pa bo prekinil niz, če bi bilo prostora v pufru.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

char hostname[128];

gethostname(hostname, sizeof hostname);
printf("My hostname: %s\n", hostname);

Poglej tudi

gethostbyname()

9.7. gethostbyname(), gethostbyaddr()

Pridobite naslov IP za ime gostitelja ali obratno

Prototipi

#include <sys/socket.h>
#include <netdb.h>

struct hostent *gethostbyname(const char *name); // DEPRECATED!
struct hostent *gethostbyaddr(const char *addr, int len, int type);

Opis

OPOZORILO: ti dve funkciji nadomeščata getaddrinfo() in getnameinfo()! Zlasti gethostbyname() ne deluje dobro z IPv6.

Te funkcije preslikavajo med imena gostiteljev in naslove IP. Na primer, če imate »www.example.com«, lahko uporabite gethostbyname(), da dobite njen IP naslov in ga shranite v strukturo in_addr.

Nasprotno, če imate strukturo in_addr ali strukturo in6_addr, lahko uporabite gethostbyaddr(), da dobite ime gostitelja nazaj. gethostbyaddr() je združljiv z IPv6, vendar morate namesto tega uporabiti novejšo shinier getnameinfo().

(Če imate niz, ki vsebuje naslov IP v formatu dots-and-numbers, za katerega želite poiskati ime gostitelja, bi bilo bolje, če bi uporabili getaddrinfo() z zastavico AI_CANONNAME.)

gethostbyname () traja niz, kot je “www.yahoo.com”, in vrne strukturni hostent, ki vsebuje tone informacij, vključno z naslovom IP. (Drugi podatki so uradno ime gostitelja, seznam vzdevkov, vrsta naslova, dolžina naslovov in seznam naslovov – to je splošna struktura, ki je zelo enostavna za uporabo za naše posebne namene, ko vidite, kako. )

gethostbyaddr() vzame strukturo in_addr ali struct in6_addr in vam prinese ustrezno ime gostitelja (če obstaja), zato je nekako v obratni smeri gethostbyname(). Kar zadeva parametre, čeprav je addr char *, dejansko želite prenesti kazalec na strukturo in_addr. len mora biti sizeof (struct in_addr), tip naj bo AF_INET.

Torej, kaj je ta struktura gostiteljica, ki se vrne? Ima številna polja, ki vsebujejo informacije o zadevnem gostitelju.

char *h_name Resnično kanonično ime gostitelja.
char **h_aliases Seznam vzdevkov, do katerih lahko dostopate
nizi – zadnji element je NULL
int h_addrtype Vrsto naslova rezultata, ki bi res moral biti
AF_INET za naše namene.
int length Dolžina naslovov v bajtih, ki je 4 za
IP naslov (različica 4).
char **h_addr_list Seznam naslovov IP za ta gostitelj. Čeprav to
je char**, to je res vrsta struct
in_addr*
s v preobleki. Zadnji element elementa je
NULL.
h_addr Splošno opredeljen vzdevek za
h_addr_list[0]. Če želite le za stari naslov IP
gostitelja (ja, lahko imajo več kot enega) samo uporabite to polje.

Vrnjena vrednost

Vrne kazalec na rezultat strukturnega gostitelja na uspeh ali NULL pri napaki.

Namesto običajnega perror() in vseh tistih stvari, ki bi jih običajno uporabljali za poročanje o napakah, imajo te funkcije vzporedne rezultate v spremenljivki h_errno, ki jo lahko natisnete z uporabo funkcije herror() ali hstrerror(). To deluje tako kot klasične funkcije errno, perror() in strerror(), na katere ste navajeni.

Primer

// TO JE NAPRAVANA METODA ZA PRIDOBITEV HOSTNIH IMEN
// namesto tega uporabite getaddrinfo ()!

#include <stdio.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int i;
    struct hostent *he;
    struct in_addr **addr_list;

    if (argc != 2) {
        fprintf(stderr,"usage: ghbn hostname\n");
        return 1;
    }

    if ((he = gethostbyname(argv[1])) == NULL) {  // dobite informacije o gostitelju
        herror("gethostbyname");
        return 2;
    }

    // natisne informacije o tem gostitelju:
    printf("Official name is: %s\n", he->h_name);
    printf("    IP addresses: ");
    addr_list = (struct in_addr **)he->h_addr_list;
    for(i = 0; addr_list[i] != NULL; i++) {
        printf("%s ", inet_ntoa(*addr_list[i]));
    }
    printf("\n");

    return 0;
}
// To je bilo podhranjeno
// namesto tega uporabite getnameinfo ()!

struct hostent *he;
struct in_addr ipv4addr;
struct in6_addr ipv6addr;

inet_pton(AF_INET, "192.0.2.34", &ipv4addr);
he = gethostbyaddr(&ipv4addr, sizeof ipv4addr, AF_INET);
printf("Host name: %s\n", he->h_name);

inet_pton(AF_INET6, "2001:db8:63b3:1::beef", &ipv6addr);
he = gethostbyaddr(&ipv6addr, sizeof ipv6addr, AF_INET6);
printf("Host name: %s\n", he->h_name);

Poglej tudi

getaddrinfo(), getnameinfo(), gethostname(), errno, perror(), strerror(), struct in_addr

9.8. getnameinfo()

Poiščite ime gostitelja in ime storitve za dano struct sockaddr.

Prototipi

#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                char *host, size_t hostlen,
                char *serv, size_t servlen, int flags);

Opis

Ta funkcija je nasprotna od getaddrinfo(), to pomeni, da ta funkcija vzame že naložen struct sockaddr in na njej išče ime in ime storitve. Zamenja stare funkcije gethostbyaddr() in getservbyport().

Morate prenesti kazalec na struct sockaddr (ki je v resnici verjetno struktura sockaddr_in ali struct sockaddr_in6, ki ste jo oddali) v parametru sa, in dolžino te strukture v salenu.

Posledično ime gostitelja in ime storitve se zapišejo na območje, na katerega se opozarjajo parametri gostitelja in servisa. Seveda morate navesti maksimalne dolžine teh pufrov v hostlen in servlen.

Končno lahko pretehtate več zastave, vendar pa nekaj dobrih. NI_NOFQDN bo povzročil, da gostitelj vsebuje samo ime gostitelja, ne celotno ime domene. NI_NAMEREQD povzroči, da funkcija ne uspe, če ni mogoče najti imena z iskanjem po DNS-ju (če ne navedete te zastavice in ne najdete imena, getnameinfo() bo v gostiteljico postavil nizno različico IP-naslova .)

Kot vedno preverite lokalne strani strani za celotno zajemanje.

Povratna vrednost

Vrne nič na uspeh, ali pa ni nič na napaki. Če je povratna vrednost nič, se lahko prenese na gai_strerror(), da dobi človeško berljiv niz. Za več informacij glejte getaddrinfo.

Primer

struct sockaddr_in6 sa; // could be IPv4 if you want
char host[1024];
char service[20];

// pretvarjaj se, da je poln dobre informacije o gostitelju in pristanišču...

getnameinfo(&sa, sizeof sa, host, sizeof host, service, sizeof service, 0);

printf("   host: %s\n", host);    // e.g. "www.example.com"
printf("service: %s\n", service); // e.g. "http"

Poglej tudi

getaddrinfo(), gethostbyaddr()

9.9. getpeername()

Vrnite naslov informacije o oddaljeni strani povezave

Prototipi

#include <sys/socket.h>

int getpeername(int s, struct sockaddr *addr, socklen_t *len);

Opis

Ko accept() ed oddaljeno povezavo ali connect() ed s strežnikom, imate zdaj tisto, kar je znano kot peer. Vaš kolega je preprosto računalnik, s katerim ste povezani, označen z naslovom IP in vmesnikom. Torej …

getpeername() preprosto vrne strukturo sockaddr_in, napolnjeno z informacijami o računalniku, s katerim ste povezani.

Zakaj se imenuje “ime”? No, obstaja veliko različnih vrst vtičnic, ne samo internetnih vtičnic, kot jih uporabljamo v tem vodniku, zato je bilo “ime” dober splošni izraz, ki je zajel vse primere. V našem primeru pa je “ime” vrstnika, to je IP naslov in vrata.

Čeprav funkcija vrne velikost nastalega naslova v len, morate prednaložiti len z velikostjo addr.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

// predpostavimo, da je s povezana vtičnica

socklen_t len;
struct sockaddr_storage addr;
char ipstr[INET6_ADDRSTRLEN];
int port;

len = sizeof addr;
getpeername(s, (struct sockaddr*)&addr, &len);

// se ukvarjajo z IPv4 in IPv6:
if (addr.ss_family == AF_INET) {
    struct sockaddr_in *s = (struct sockaddr_in *)&addr;
    port = ntohs(s->sin_port);
    inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
} else { // AF_INET6
    struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
    port = ntohs(s->sin6_port);
    inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
}

printf("Peer IP address: %s\n", ipstr);
printf("Peer port      : %d\n", port);

Poglej tudi

gethostname(), gethostbyname(), gethostbyaddr()

9.10. errno

Drži kodo napake za zadnji sistemski klic

Prototipi

#include <errno.h>

int errno;

Opis

To je spremenljivka, ki vsebuje podatke o napaki za veliko sistemskih klicev. Če se boste spomnili, stvari, kot so socket() in listen(), vrnejo -1 na napako in nastavijo točno vrednost errno, da bi vas posebej obvestili o napaki.

V naslovni datoteki errno.h so naštete kup stalnih simboličnih imen za napake, kot so EADDRINUSE, EPIPE, ECONNREFUSED itd. Strani vašega lokalnega človeka vam bodo povedali, katere kode lahko vrnete kot napako, in jih lahko uporabite ob času zagona Da na različne načine obravnavajo različne napake.

Ali pa bolj pogosto lahko pokličete perror() ali strerror(), da dobite človeško berljivo različico napake.

Ena stvar, ki je za vas večnitne navdušence, je, da je na večini sistemov errno definirana na nitno varen način. (To pomeni, da to dejansko ni globalna spremenljivka, ampak se obnaša tako, kot bi bila globalna spremenljivka v okolju z enojnim navojem.)

Povratna vrednost

Vrednost spremenljivke je najnovejša napaka, ki se je pojavila, kar je lahko koda za “uspeh”, če je zadnje dejanje uspelo.

Primer

s = socket(PF_INET, SOCK_STREAM, 0);
if (s == -1) {
    perror("socket"); // or use strerror()
}

tryagain:
if (select(n, &readfds, NULL, NULL) == -1) {
    // prišlo je do napake!!

    // če smo bili samo prekinjeni, preprosto ponovno zaženite klic select():
    if (errno == EINTR) goto tryagain;  // AAAA!  goto!!!

    // drugače je resnejša napaka:
    perror("select");
    exit(1);
}

Poglej tudi

perror (), strerror ()

9.11. fcntl()

Kontrolni deskriptorji

Prototipi

#include <sys/unistd.h>
#include <sys/fcntl.h>

int fcntl(int s, int cmd, long arg);

Opis

Ta funkcija se običajno uporablja za zaklepanje datotek in druge datoteke, ki so usmerjene v datoteko, vendar ima tudi nekaj povezanih funkcij, ki jih morda občasno vidite ali uporabljate.

Parameter s je deskriptor vtičnice, na katerega želite delovati, mora biti cmd nastavljen na F_SETFL, arg pa lahko eden od naslednjih ukazov. (Kot sem rekel, obstaja več za fcntl (), kot sem pustil tukaj, vendar poskušam ostati usmerjen v vtičnico.)

O_NONBLOCK Set the socket to be non-blocking. See the section on
blocking for more details.
O_ASYNC Nastavite vtičnico za asinhroni vhod / izhod. Ko so podatki
pripravljen biti recv()‘d na vtičnici, signal SIGIO bo dvignjen. To je redko videti,
in izven obsega priročnika. Mislim, da je na voljo samo
nekatere sisteme.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Različne uporabe sistemskega klica fcntl() dejansko imajo različne vračilne vrednosti, vendar jih tukaj nisem pokril, ker niso povezani s sokovniki. Več informacij najdete na strani lokalne strani fcntl().

Primer

int s = socket(PF_INET, SOCK_STREAM, 0);

fcntl(s, F_SETFL, O_NONBLOCK);  // nastavljeno na ne-blokiranje
fcntl(s, F_SETFL, O_ASYNC);     // nastavljen na asinhroni V / I

Poglej tudi

Blocking, send()

9.12. htons(), htonl(), ntohs(), ntohl()

Pretvori več-bajtne celoštevilske vrste iz vrstnega reda gostitelja v omrežni vrstni red

Prototipi

#include <netinet/in.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

Opis

Samo zato, da bi bili res nesrečni, različni računalniki uporabljajo različna bajtna naročila za svoje multibyte celo število (to je vsako celo število, ki je večje kot znak.) Povzetek tega je, da če send() dvomestni kratki int iz Intel okvira na Mac (preden so postali tudi Intel škatle, mislim tudi) mislim, da je eden od računalnikov številka 1, druga pa bo pomislila na 256 in obratno.

Način, kako se lotiti tega problema je, da vsi razkrijejo svoje razlike in se strinjajo, da sta Motorola in IBM imela prav, Intel pa je to naredil čuden način, zato smo vse svoje pretvorbe bajtov pretvorili v “velikansko”, preden jih pošljemo Ven. Ker je Intel maloštevilen stroj, je veliko bolj politično pravilna, da pokličete našo prednostno bajtno naročanje “Network Byte Order”. Torej te funkcije pretvorijo iz vašega domačega bajtnega naloga v omrežni vrstni red in nazaj.

(To pomeni, da te funkcije na Intelih te funkcije zamenjajo vse bajte okoli sebe, pri PowerPC pa ne storijo ničesar, ker so bajti že v omrežnem vrstnem redu, vendar jih morate vedno uporabljati v kodi, saj bi ga nekdo želel zgraditi na Intel stroj in še vedno delujejo pravilno.)

Upoštevajte, da so vključene vrste 32-bitne (4-bajtne, verjetno int) in 16-bitne (2-bajtne, zelo verjetno kratke) številke. 64-bitni stroji imajo lahko htonll() za 64-bitne ints, vendar jih nisem videl. Samo napisati svoje.

Kakorkoli, način delovanja teh funkcij je, da se najprej odločite, ali pretvarjate iz naročila bajtov gostitelja (vaše naprave) ali iz omrežnega vrstnega reda. Če je »gostitelj« prva črka funkcije, ki jo želite poklicati, “h”. V nasprotnem primeru je “n” za “omrežje”. Sredina imena funkcije je vedno “do”, ker pretvarjate iz enega v drugo, predzadnje pismo pa prikazuje, na kaj pretvarjate. Zadnja črka je velikost podatkov, “s” za kratko ali “l” za dolgo. Tako:

htons() host to network short
htonl() host to network long
ntohs() network to host short
ntohl() network to host long

Vrnjena vrednost

Vsaka funkcija vrne pretvorjeno vrednost.

Primer

uint32_t some_long = 10;
uint16_t some_short = 20;

uint32_t network_byte_order;

// pretvoriti in poslati
network_byte_order = htonl(some_long);
send(s, &network_byte_order, sizeof(uint32_t), 0);

some_short == ntohs(htons(some_short)); // this expression is true

9.13. inet_ntoa (), inet_aton (), inet_addr

Pretvorite naslove IP iz niza pik in števila v strukturo in_addr in nazaj

Prototipi

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// Vsi so zmedeni!  Use inet_pton()  or inet_ntop() instead!!

char *inet_ntoa(struct in_addr in);
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp);

Opis

Te funkcije so zastarele, ker se ne ukvarjajo z IPv6! Namesto tega uporabite inet_ntop() ali inet_pton()! Tu so vključeni, ker jih še vedno najdemo v naravi.

Vse te funkcije se pretvorijo iz strukture in_addr (del strukture sockaddr_in, najverjetneje) v niz v formatu dots-and-numbers (npr. “192.168.5.10”) in obratno. Če imate v ukazni vrstici ali na drugem naslovu IP naslov, je to najlažji način, da dobite strukturo in_addr za povezavo() ali karkoli. Če potrebujete več moči, poskusite nekatere od funkcij DNS, na primer gethostbyname() ali poskušajte državni udar v vaši državi.

Funkcija inet_ntoa() pretvori omrežni naslov v strukturo in_addr v niz formatov dots-and-numbers. “N” v “ntoa” pomeni omrežje, a “a” pomeni ASCII iz zgodovinskih razlogov (tako je “suffix” omrežje v ASCII “toa” podoben prijatelj v knjižnici C, ki se imenuje atoi() pretvori ASCII niz v celo število.)

Funkcija inet_aton () je nasprotna, pretvorba iz niza pik in številk v in_addr_t (kar je tip polja s_addr v vaši strukturi in_addr.)

Nazadnje, funkcija inet_addr () je starejša funkcija, ki v bistvu ista stvar kot inet_aton (). Teoretično je zastarelo, vendar ga boste videli veliko in policija ne bo prišla, če jo boste uporabljali.

Vrnjena vrednost

inet_aton() vrne ničlo, če je naslov veljaven in vrne nič, če je naslov neveljaven.

inet_ntoa() vrne niz pik in številk v statični pufer, ki je prepisan z vsakim klicem v funkcijo.

inet_addr() vrne naslov kot in_addr_t ali -1, če je prišlo do napake. (To je enak rezultat, kot če ste skušali pretvoriti niz “255.255.255.255”, ki je veljaven IP-naslov. Zato je inet_aton() boljši.)

Primer

struct sockaddr_in antelope;
char *some_addr;

inet_aton("10.0.0.1", &antelope.sin_addr); // shranite IP v antilopa

some_addr = inet_ntoa(antelope.sin_addr); // return the IP
printf("%s\n", some_addr); // prints "10.0.0.1"

// in ta klic je enak kot klic inet_aton () zgoraj:
antelope.sin_addr.s_addr = inet_addr("10.0.0.1");

Poglej tudi

inet_ntop(), inet_pton(), gethostbyname(), gethostbyaddr()

9.14. inet_ntop(), inet_pton()

Pretvorite naslove IP v človeško berljivo obliko in nazaj.

Prototipi

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src,
                      char *dst, socklen_t size);

int inet_pton(int af, const char *src, void *dst);

Opis

Te funkcije so za obravnavanje človeško berljivih naslovov IP in njihovo pretvorbo v njihovo binarno predstavitev za uporabo z različnimi funkcijami in sistemskimi klici. “N” pomeni “omrežje” in “p” za “predstavitev”. Ali “predstavitev besedila”. Toda to lahko misliš kot “tiskati”. “ntop” je “omrežje za tiskanje”. Vidiš?

Včasih ne gledate na kup binarnih številk, ko pogledate naslov IP. Želite to v lepi natisljivi obliki, na primer 192.0.2.180 ali 2001: db8: 8714: 3a90 :: 12. V tem primeru je inet_ntop() za vas.

inet_ntop() prevzame družino naslovov v parametru af (bodisi AF_INET ali AF_INET6). Parameter src bi moral biti kazalec na strukturo in_addr ali struct in6_addr, ki vsebuje naslov, ki ga želite pretvoriti v niz. Končno, dst in velikost sta kazalec na ciljni niz in največja dolžina tega niza.

Kakšna naj bo največja dolžina dst stringa? Kakšna je največja dolžina za naslove IPv4 in IPv6? Na srečo obstaja nekaj makrov, ki vam bodo pomagali. Največje dolžine so: INET_ADDRSTRLEN in INET6_ADDRSTRLEN.

V drugih primerih lahko imate v nizu berljive črke, ki vsebujejo naslov IP in ga želite zapakirati v struct sockaddr_in ali struct sockaddr_in6. V tem primeru je nasprotna funkcija inet_pton() tista, za katero ste.

inet_pton() ima tudi družino naslovov (AF_INET ali AF_INET6) v parametru af. Parameter src je kazalec na niz, ki vsebuje naslov IP v obliki, ki jo je mogoče natisniti. Nenazadnje parameter dst kaže, kje je treba shraniti rezultat, kar je verjetno struktura in_addr ali struct in6_addr.

Te funkcije ne opravljajo DNS lookups-za to potrebujete getaddrinfo().

Vrnjena vrednost

inet_ntop() vrne parameter dst za uspeh ali NULL ob neuspehu (in errno je nastavljen).

inet_pton() vrne 1 na uspeh. Vrne -1, če je prišlo do napake (errno je nastavljeno) ali 0, če vhod ni veljaven IP-naslov.

Primer

// IPv4 demo of inet_ntop() and inet_pton()

struct sockaddr_in sa;
char str[INET_ADDRSTRLEN];

// ta naslov IP shranite v sa:
inet_pton(AF_INET, "192.0.2.33", &(sa.sin_addr));

// sedaj ga vrni in natisni
inet_ntop(AF_INET, &(sa.sin_addr), str, INET_ADDRSTRLEN);

printf("%s\n", str); // prints "192.0.2.33"
// predstavitev IPv6 inet_ntop () in inet_pton ()
// (v bistvu enako, razen s kupom 6-ih vrženih okoli)

struct sockaddr_in6 sa;
char str[INET6_ADDRSTRLEN];

// ta naslov IP shranite v sa:
inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &(sa.sin6_addr));

// sedaj ga vrni in natisni
inet_ntop(AF_INET6, &(sa.sin6_addr), str, INET6_ADDRSTRLEN);

printf("%s\n", str); // prints "2001:db8:8714:3a90::12"
// funkcijo pomočnika, ki jo lahko uporabite:

//Pretvorite naslov struct sockaddr v niz, IPv4 in IPv6:

char *get_ip_str(const struct sockaddr *sa, char *s, size_t maxlen)
{
    switch(sa->sa_family) {
        case AF_INET:
            inet_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
                    s, maxlen);
            break;

        case AF_INET6:
            inet_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
                    s, maxlen);
            break;

        default:
            strncpy(s, "Unknown AF", maxlen);
            return NULL;
    }

    return s;
}

Poglej tudi

getaddrinfo()

9.15. listen()

Povejte vtičnico, da poslušate dohodne povezave

Prototipi

#include <sys/socket.h>

int listen(int s, int backlog);

Opis

Lahko uporabite deskriptor vtičnice (izdelan s sistemskim klicem socket()) in povejte, da poslušate dohodne povezave. To je tisto, kar razlikuje strežnike od strank, fantje.

backlog  lahko pomeni nekaj različnih stvari, odvisno od sistema, na katerem ste, vendar je rahlo toliko, kolikšne čakalne povezave lahko imate, preden jedro začne zavračati nove. Torej, ko pridejo nove povezave, jih morate hitro accept(), tako da se zaostanek ne napolni. Poskusite ga nastaviti na 10 ali tako, in če vaše stranke začnejo prejemati »Povezava zavrnjena« pod obremenitvijo, jo nastavite višje.

Pred klicem listen() mora vaš strežnik klicati bind(), da se priključi na določeno številko vrat. Ta številka vrat (na naslovu strežnika) bo tista, s katero se bodo stranke povezale.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

struct addrinfo hints, *res;
int sockfd;

// najprej z naslovom getaddrinfo () naložite naslovne strukture ():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;     // fill in my IP for me

getaddrinfo(NULL, "3490", &hints, &res);

// naredite vtičnico:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

// ga povežite s pristaniščem, v katerega smo vstopili, da dobimo getaddrinfo():

bind(sockfd, res->ai_addr, res->ai_addrlen);

listen(sockfd, 10); // nastavite s strežnik (poslušanje) vtičnice

// Potem na nekem mestu sprejmete accept()

Poglej tudi

accept(), bind(), socket()

9.16. perror(), strerror()

Natisnite napako kot človeško berljiv niz

Prototipi

#include <stdio.h>
#include <string.h>   // for strerror()

void perror(const char *s);
char *strerror(int errnum);

Opis

Ker toliko funkcij vrne -1 pri napaki in nastavite vrednost spremenljivke errno kot nekaj števila, bi bilo lepo, če bi ga lahko enostavno natisnili v obliki, ki je bila smiselna za vas.

Usmiljeno, perror() naredi to. Če želite, da se pred napako natisne večji opis, ga lahko usmerite s parametrom s (ali lahko pustite s kot NULL in nič dodatnega ne bo natisnjeno.)

Na kratko, ta funkcija zajema errno vrednosti, na primer ECONNRESET, in jih lepo natisne, na primer “Ponastavitev povezave s peerjem”.

Funkcija strerror() je zelo podobna perror(), razen če vrne kazalec na niz sporočil o napaki za dano vrednost (navadno vnesete spremenljivko errno.)

Vrnjena vrednost

strerror() vrne kazalec na niz sporočil o napaki.

Primer

int s;

s = socket(PF_INET, SOCK_STREAM, 0);

if (s == -1) { // some error has occurred
    // natisne "napaka vtičnice:" + sporočilo o napaki:
    perror("socket error");
}

// podobno:
if (listen(s, 10) == -1) {
    // to natisne "napaka:" + sporočilo o napaki iz errno:
    printf("an error: %s\n", strerror(errno));
}

Poglej tudi

errno

9.17. poll()

Preizkusite dogodke v več vtičnicah hkrati

Prototipi

#include <sys/poll.h>

int poll(struct pollfd *ufds, unsigned int nfds, int timeout);

Opis

Ta funkcija je zelo podobna select(), saj oba gledata nizi deskriptorjev datotek za dogodke, kot so vhodni podatki pripravljeni za recv(), podatkovni vmesnik, pripravljen za send(), izven podatkov o stanju, pripravljen za recv( ), napake itd.

Osnovna ideja je, da prenese niz nfds struct pollfds v ufds, skupaj s časovnim zamikom v milisekundah (1000 milisekund v sekundi). Časovna omejitev je lahko negativna, če želite čakati večno. Če se dogodek ne zgodi na nobenem od deskriptorjev sokov, se bo poll() vrnila.

Vsak element v matriki struct pollfds predstavlja en deskriptor soketov in vsebuje naslednja polja:

struct pollfd {
    int fd;         // deskriptor vtičnice
    short events;   // bitna slika dogodkov, ki nas zanimajo
    short revents;  // ko se glasovalo () vrne, bitna slika dogodkov, ki so se zgodila
};

Preden pokličete poll(), naložite fd s deskriptorjem soketa (če nastavite fd na negativno število, se ta struktura pollfd ne upošteva, njegovo polje reventov pa je nastavljeno na nič), nato pa poljubno zgradite polje dogodkov z bitno -Izberite naslednje makre:

POLLIN Opozorite me, ko so podatki pripravljeni
recv() na tej vtičnici.
POLLOUT Opozorite me, ko lahko send() podatke v to
vtičnica brez blokiranja.
POLLPRI Opozorite me, ko so podatki izven pasu pripravljeni
recv() na tej vtičnici.

Ko se bo klic povratnega poll() vrnil, bo polje revnih zgrajeno kot bitno-ALI zgornjih polj, ki vam pove, kateri deskriptorji so dejansko imeli ta dogodek. Poleg tega so lahko tudi ta druga polja:

POLLERR V tej vtičnici je prišlo do napake.
POLLHUP Oddaljena stran povezave je prekinjena.
POLLNVAL Nekaj je bilo napačno s deskriptorjem sokov
fd—mogoče je to neinicializirano?

Vrnjena vrednost

Vrne število elementov v matrico ufds, ki so imele dogodek na njem; to je lahko nič, če je prišlo do časovne omejitve. Vrne tudi -1 po napaki (in ustrezno bo nastavljeno errno.)

Primer

int s1, s2;
int rv;
char buf1[256], buf2[256];
struct pollfd ufds[2];

s1 = socket(PF_INET, SOCK_STREAM, 0);
s2 = socket(PF_INET, SOCK_STREAM, 0);

// pretend we've connected both to a server at this point
//connect(s1, ...)...
//connect(s2, ...)...

// set up the array of file descriptors.
//
// in this example, we want to know when there's normal or out-of-band
// data ready to be recv()'d...

ufds[0].fd = s1;
ufds[0].events = POLLIN | POLLPRI; // check for normal or out-of-band

ufds[1].fd = s2;
ufds[1].events = POLLIN; // check for just normal data

// Čakati na dogodke v vtičnicah, 3,5 sekunde
rv = poll(ufds, 2, 3500);

if (rv == -1) {
    perror("poll"); // prišlo je do napake v poll()
} else if (rv == 0) {
    printf("Timeout occurred!  No data after 3.5 seconds.\n");
} else {
    // preveri dogodke na s1:
    if (ufds[0].revents & POLLIN) {
        recv(s1, buf1, sizeof buf1, 0); // receive normal data
    }
    if (ufds[0].revents & POLLPRI) {
        recv(s1, buf1, sizeof buf1, MSG_OOB); // out-of-band data
    }

    // preveri dogodke na s2:
    if (ufds[1].revents & POLLIN) {
        recv(s1, buf2, sizeof buf2, 0);
    }
}

Poglej tudi

select()

9.18. recv(), recvfrom()

Prejemajte podatke v vtičnici

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int s, void *buf, size_t len, int flags);
ssize_t recvfrom(int s, void *buf, size_t len, int flags,
                 struct sockaddr *from, socklen_t *fromlen);

Opis

Ko imate vtičnico in priključeno, lahko preberete dohodne podatke z oddaljene strani z uporabo recv() (za vtičnice TCP SOCK_STREAM) in recvfrom() (za UDP SOCK_DGRAM vtičnice).

Obe funkciji sta opisni socket s, kazalec na pufer buf, velikost (v bajtih) vmesnika in niz zastavic, ki nadzirajo delovanje funkcij.

Poleg tega recvfrom() vzpostavi struct sockaddr*, od tega vam bo povedal, od kod prihajajo podatki, in bo izpolnil fromlen z velikostjo struct sockaddr. (Poleg tega morate inicializirati fromlen velikosti od ali struct sockaddr.)

Kakšne čudne zastave lahko preidete v to funkcijo? Tukaj je nekaj od njih, vendar bi morali preveriti svoje lokalne človeške strani za več informacij in kaj je dejansko podprto v vašem sistemu. Pojasnili ste jih – ali skupaj, ali pa nastavite flags na 0, če želite, da je redna vanilla recv().

MSG_OOB Prejmite podatke iz pasu.
Tako dobite podatke, ki so vam bili poslani z
MSG_OOB zastava v send(). Kot prejemnik
stran, boste imeli signal SIGURG
povedal, da so nujni podatki. V svojem vodniku za to
signal, lahko pokličete recv() s tem
MSG_OOB zastava.
MSG_PEEK Če želite poklicati recv() “samo za
pretvarjati se”, lahko jo pokličete s to zastavo. To vam bo povedalo, kaj je
čaka v pufru, ko kličete recv() “zares”
(i.e. brez the MSG_PEEK zastava. To je kot a
predogled v drugi recv() klic.
MSG_WAITALL Povej recv() da se ne vrnejo do vseh podatkov
ste navedli v len parameter. Ne bo upošteval tvojega
želi v izjemnih okoliščinah, kot če bi prekinil signal
klica ali če se pojavi kakšna napaka ali če oddaljena stran zapre
povezave itd. Ne bodi jezna z njo.

Ko pokličete recv(), bo blokiral, dokler ne bo nekaj podatkov za branje. Če ne želite blokirati, nastavite vtičnico za neblokiranje ali preverite z select() ali poll(), da preverite, ali so vhodni podatki pred klicem recv() ali recvfrom().

Vrnjena vrednost

Vrne število dejansko prejetih bajtov (kar je lahko manj kot ste zahtevali v parametru len) ali -1 pri napaki (in ustrezno bo nastavljen errno).

Če je oddaljena stran zaprta povezavo, se recv() vrne 0. To je običajna metoda za ugotavljanje, ali je oddaljena stran zaprla povezavo. Normalna je dobra, uporniška!

Primer

// stream sockets and recv()

struct addrinfo hints, *res;
int sockfd;
char buf[512];
int byte_count;

// dobite informacije o gostitelju, naredite vtičnico in jo povežite
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
getaddrinfo("www.example.com", "3490", &hints, &res);
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
connect(sockfd, res->ai_addr, res->ai_addrlen);

// v redu! Zdaj, ko smo povezani, lahko prejmemo nekaj podatkov!
byte_count = recv(sockfd, buf, sizeof buf, 0);
printf("recv()'d %d bytes of data in buf\n", byte_count);
// datagram sockets and recvfrom()

struct addrinfo hints, *res;
int sockfd;
int byte_count;
socklen_t fromlen;
struct sockaddr_storage addr;
char buf[512];
char ipstr[INET6_ADDRSTRLEN];

// dobite informacije o gostitelju, naredite vtičnico, ga priklopite na vrata 4950
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;  // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_PASSIVE;
getaddrinfo(NULL, "4950", &hints, &res);
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);

// ni treba sprejeti (), samo recvfrom ():

fromlen = sizeof addr;
byte_count = recvfrom(sockfd, buf, sizeof buf, 0, &addr, &fromlen);

printf("recv()'d %d bytes of data in buf\n", byte_count);
printf("from IP address %s\n",
    inet_ntop(addr.ss_family,
        addr.ss_family == AF_INET?
            ((struct sockadd_in *)&addr)->sin_addr:
            ((struct sockadd_in6 *)&addr)->sin6_addr,
        ipstr, sizeof ipstr);

Poglej tudi

send(), sendto(), select(), poll(), Blocking

9.19. select()

Preverite, ali so deskriptorji vtičnic pripravljeni za branje / pisanje

Prototipi

#include <sys/select.h>

int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
           struct timeval *timeout);

FD_SET(int fd, fd_set *set);
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_ZERO(fd_set *set);

Opis

Funkcija select() vam omogoča, da istočasno preverite več vtičnic in ugotovite, ali imajo podatke, ki čakajo, da se jih prikaže recv() d, ali če jim lahko send() podatke brez blokiranja ali če je prišlo do neke izjeme.

Napolnite svoje skupine deskriptorjev s pomočjo makrov, na primer FD_SET() zgoraj. Ko imate nastavljeno, ga prenesete v funkcijo kot eden od naslednjih parametrov: readfds , če želite vedeti, kdaj je katera od vtičnic v nizu pripravljena na podatke recv(), writefds, če je writefds koli vtičnica pripravljena na send() podatke in/ali exceptfds, če morate vedeti, kdaj se pojavijo izjeme (napake) na kateri koli vtičnici. Vsi ali vsi ti parametri so lahko NULL, če te vrste dogodkov ne zanima. Ko select() vrne, se bodo vrednosti v nizih spremenile, da bodo prikazane, ki so pripravljene za branje ali pisanje, in ki imajo izjeme.

Prvi parameter, n je najštevilčen deskriptor sokov (samo ints, zapomni si?) In eno.

Nazadnje, struct timeval, timeout, na koncu – to vam omogoča, da poveste, izberite (), kako dolgo naj preverijo te nastavitve. Vrnila se bo po preteku časa ali ko pride do dogodka, kar nastopi prej. Strukturni timeval ima dva polja: tv_sec je število sekund, ki mu je dodano tv_usec, število mikrosekund (1.000.000 mikrosekund na sekundo).

Pomožni makri naredijo naslednje:

FD_SET(int fd, fd_set *set); Add fd to the set.
FD_CLR(int fd, fd_set *set); Odstraniti fd iz set.
FD_ISSET(int fd, fd_set *set); Vrni se, če fd je v
set.
FD_ZERO(fd_set *set); Počistite vse vnose iz set.

Opomba za uporabnike Linuxa: izbira Linuxa() v Linuxu lahko vrne “pripravljeno za branje” in ni dejansko pripravljena za branje, kar povzroči blokiranje poznejšega poziva za read(). O tej bugi lahko delate tako, da nastavite zastavico O_NONBLOCK v sprejemni vtičnici, tako da napaka pri EWOULDBLOCK, nato pa se ta napaka ne upošteva. Oglejte si referenčno stran fcntl() za več informacij o nastavitvi vtičnice na ne-blokiranje.

Vrnjena vrednost

Vrne število deskriptorjev v nizu za uspeh, 0, če je čas dosegel, ali -1 pri napaki (in ustrezno bo nastavljen errno.) Poleg tega so nastavki spremenjeni, da prikažejo, katere vtičnice so pripravljene.

Primer

int s1, s2, n;
fd_set readfds;
struct timeval tv;
char buf1[256], buf2[256];

// se pretvarjamo, da smo oba strežnika povezali na tej točki
//s1 = socket(...);
//s2 = socket(...);
//connect(s1, ...)...
//connect(s2, ...)...

// Čisto nastavite pred časom
FD_ZERO(&readfds);

// dodajte naše deskriptorje v niz
FD_SET(s1, &readfds);
FD_SET(s2, &readfds);

// ker smo dobili s2 sekundo, je "večja", zato jo uporabimo za
// the n param in select()
n = s2 + 1;

// počakajte, da je katerakoli vtičnica pripravljena za recv() d (časovna omejitev 10,5 sekund)
tv.tv_sec = 10;
tv.tv_usec = 500000;
rv = select(n, &readfds, NULL, NULL, &tv);

if (rv == -1) {
    perror("select"); // error occurred in select()
} else if (rv == 0) {
    printf("Timeout occurred!  No data after 10.5 seconds.\n");
} else {
    // eden ali oba deskriptorja imajo podatke
    if (FD_ISSET(s1, &readfds)) {
        recv(s1, buf1, sizeof buf1, 0);
    }
    if (FD_ISSET(s2, &readfds)) {
        recv(s2, buf2, sizeof buf2, 0);
    }
}

Poglej tudi

poll()

9.20. setsockopt(), getsockopt()

Nastavite različne možnosti za vtičnico

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

int getsockopt(int s, int level, int optname, void *optval,
               socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval,
               socklen_t optlen);

Opis

Sockets so precej nastavljive zveri. Pravzaprav so tako konfigurirni, sploh ne bom pokrivala vse tukaj. Kakorkoli že, odvisno od sistema. Ampak govoril bom o osnovah.

Očitno te funkcije dobijo in določijo določene možnosti v vtičnici. V okencu Linux so vse informacije o vtičnicah na strani s strani za vtičnico v razdelku 7. (Vpišite: “man 7 socket“, da dobite vse te dobrote.)

Kar zadeva parametre, s je vtičnica, o kateri govorite, raven je treba nastaviti na SOL_SOCKET. Nato nastavite optname na ime, ki vas zanima. Ponovno si oglejte svojo stran s podatki o vseh možnostih, vendar je tukaj nekaj najbolj zabavnih:

SO_BINDTODEVICE Priključite to vtičnico na ime simbolične naprave, kot je
eth0 namesto uporabe bind() da ga povežete z IP-jem
naslov. Vnesite ukaz ifconfig pod Unixom, da si ogledate
imena naprav.
SO_REUSEADDR Omogoča drugim vtičnicam bind() v to pristanišče, razen če
že obstaja aktivna vtičnica za poslušanje, ki se veže na vrata. To
vam omogoča, da obiščete sporočila o napakah “Naslov že v uporabi”
ko poskusite znova zagnati strežnik po crashu.
SO_BROADCAST Omogoča datagram UDP (SOCK_DGRAM) Vtičnice za pošiljanje in prejemanje
pakete, poslane na oddajni naslov in iz nje. Ali
nič—Nič!!—do vtičnic TCP-ja!
Hahaha!

Kar zadeva parameter optval, je običajno kazalec na int, ki označuje zadevno vrednost. Za booleans je nič enačen, neveljavni pa je resničen. In to je absolutno dejstvo, razen če je drugačno v vašem sistemu. Če ni nobenega parametra, je lahko optval NULL.

Končni parameter, optlen, mora biti nastavljen na dolžino optval, verjetno sizeof (int), vendar se razlikuje glede na možnost. Upoštevajte, da je v primeru getsockopt () to kazalec na socklen_t in določa največji objekt velikosti, ki bo shranjen v optval (za preprečitev prelivanja prekrival). In getsockopt () bo spremenil vrednost optlen, da bo odražal število dejansko nastavljenih bajtov.

Opozorilo: v nekaterih sistemih (predvsem v Sun in Windows) je možnost lahko char, namesto int, in je nastavljena na primer na vrednost znaka ‘1’ namesto na int vrednost 1. Ponovno preverite lastne človeške strani za več informacij z “man setsockopt” in “man 7 socket“!

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

int optval;
int optlen;
char *optval2;

// Nastavite SO_REUSEADDR v vtičnici na true (1):
optval = 1;
setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);

// povezavo vtičnice z imenom naprave (morda ne bo delovala na vseh sistemih):
optval2 = "eth1"; // 4 bytes long, so 4, below:
setsockopt(s2, SOL_SOCKET, SO_BINDTODEVICE, optval2, 4);

// Glejte, če je postavljena zastavica SO_BROADCAST:
getsockopt(s3, SOL_SOCKET, SO_BROADCAST, &optval, &optlen);
if (optval != 0) {
    print("SO_BROADCAST enabled on s3!\n");
}

Poglej tudi

fcntl ()

9.21. send(), sendto()

Pošljite podatke preko vtičnice

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int s, const void *buf, size_t len, int flags);
ssize_t sendto(int s, const void *buf, size_t len,
               int flags, const struct sockaddr *to,
               socklen_t tolen);

Opis

Te funkcije pošljejo podatke v vtičnico. Na splošno velja, da se send() uporablja za priključene vtičnice TCP SOCK_STREAM, in sendto() se uporablja za nepovezane vtičnice datagrama UDP SOCK_DGRAM. Pri nepovezanih vtičnicah morate vsakič, ko ga pošljete, določiti cilj paketa, zato zadnji parametri sendto() določajo, kam gre paket.

Z obema send() in sendto() je parameter s vtičnica, buf je kazalec na podatke, ki jih želite poslati, len je število bajtov, ki jih želite poslati, in zastave vam omogočajo, da določite več informacij o tem, kako podatke je treba poslati. Označite zastave na nič, če želite, da so “normalni” podatki. Tukaj je nekaj najpogosteje uporabljenih zastav, vendar za več podrobnosti preverite lokalne strani za send().

MSG_OOB Pošlji
kot podatki izven pasu. TCP to podpira,
in to je način, da sporočite prejemnemu sistemu, da so ti podatki višji
prednost pred običajnimi podatki. Sprejemnik bo prejel signal
SIGURG In nato lahko prejme te podatke brez predhodnega
prejema vse ostale običajne podatke v čakalni vrsti.
MSG_DONTROUTE Ne pošiljajte teh podatkov prek usmerjevalnika, samo ga hranite
lokalno.
MSG_DONTWAIT Če send() blokirati, ker je izhoden
promet je zamašen, ga vrnite EAGAIN. To je kot a “omogoči ne blokira samo za to pošiljanje.” Oglejte si
oddelek o blocking za več
podrobnosti.
MSG_NOSIGNAL Če ti send() do oddaljenega gostitelja, ki je
nič več recv()ing, običajno boste dobili signal SIGPIPE. Dodajanje te zastavice to preprečuje
signal, da se dvigne.

Vrnjena vrednost

Vrne število dejansko poslanih bajtov ali -1 pri napaki (in ustrezno bo nastavljeno errno.) Upoštevajte, da je število dejansko poslanih bajtov lahko manjše od števila, ki ste jo zahtevali za pošiljanje! Oglejte si poglavje o ravnanju z delnim send() za funkcijo pomočnika, da se obrnete k temu.

Tudi če je vtičnica zaprla obe strani, bo proces, ki kliče send(), dobil signal SIGPIPE. (Razen če je bila send() pošljite z oznako MSG_NOSIGNAL.)

Primer

int spatula_count = 3490;
char *secret_message = "The Cheese is in The Toaster";

int stream_socket, dgram_socket;
struct sockaddr_in dest;
int temp;

// najprej s vtičnimi vtičnicami TCP:

// predpostavimo, da so vtičnice izdelane in priključene
//stream_socket = socket(...
//connect(stream_socket, ...

// pretvori v omrežni vrstni red
temp = htonl(spatula_count);
// send data normally:
send(stream_socket, &temp, sizeof temp, 0);

// pošljite tajno sporočilo iz pasu:
send(stream_socket, secret_message, strlen(secret_message)+1, MSG_OOB);

// zdaj s podnožji datagrama UDP:
//getaddrinfo(...
//dest = ...  // assume "dest" holds the address of the destination
//dgram_socket = socket(...

// običajno pošljete tajno sporočilo:
sendto(dgram_socket, secret_message, strlen(secret_message)+1, 0, 
       (struct sockaddr*)&dest, sizeof dest);

Poglej tudi

recv(), recvfrom()

9.22. shutdown()

Ustavi še naprej pošiljanje in sprejemanje v vtičnico

Prototipi

#include <sys/socket.h>

int shutdown(int s, int how);

Opis

To je to! Imam ga! V tej vtičnici ni dovoljeno pošiljati send(), vendar pa še vedno želim podatke o njej objaviti! Ali obratno! Kako lahko to storim?

Ko close() deskriptor vtičnice, za bralno in pisno zaprta obe strani vtičnice in osvobodi deskriptor vtičnice. Če želite samo eno ali drugo zapreti, lahko uporabite ta poziv za shutdown().

Kar zadeva parametre s, je s očitno socket, na katero želite izvedeti to dejanje, in kakšno dejanje je mogoče določiti s parametrom, kako. Kako je lahko SHUT_RD, da preprečite nadaljnje recv() s, SHUT_WR, da prepove nadaljnje send() s ali SHUT_RDWR, če želite to storiti.

Upoštevajte, da shutdown() ne sprosti deskriptorja vtičnice, zato morate še vedno zapreti () vtičnico, tudi če je bila popolnoma zaprta.

To je redko uporabljen sistemski klic.

Vrnjena vrednost

Vrne nič na uspeh, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

int s = socket(PF_INET, SOCK_STREAM, 0);

// ...naredi nekaj pošiljk () in stvari tukaj...

// in zdaj, ko smo končali, ne dovolite več pošiljati () s:
shutdown(s, SHUT_WR);

Poglej tudi

close()

9.23. socket()

Dodelite deskriptor vtičnice

Prototipi

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

Opis

Vrne nov deskriptor sokov, s katerim lahko uporabite sockety stvari. To je na splošno prvi klic v neverjetnem procesu pisanja vtičnice in lahko uporabite rezultat za poznejše klice listen(), bind(), accept() ali različne druge funkcije.

Pri običajni uporabi dobite vrednosti teh parametrov iz klica na getaddrinfo(), kot je prikazano v spodnjem primeru. Vendar jih lahko izpolnite z roko, če res želite.

domain domain opisuje kakšno vtičnico
vas zanima. To lahko, verjemite mi, široko paleto
stvari, ampak ker je to vtičnica, bo to PF_INET za IPv4 in
PF_INET6 za IPv6.
type Tudi, type Parameter je lahko nekaj stvari,
vendar ga boste verjetno tudi nastavili SOCK_STREAM za zanesljivo TCP sockets (send(), recv()) ali SOCK_DGRAM za nezanesljiv hiter UDP sockets (sendto(),
recvfrom().)(Druga zanimiva vrsta vtičnice je SOCK_RAW ki se lahko uporablja za gradnjo
pakete z roko. Zelo je kul.)
protocol Končno, protocol parameter pove, kateremu protokolu
uporabite z določeno vrsto vtičnice. Kot sem že omenil, na primer,
SOCK_STREAM uporablja TCP. Na srečo, ko uporabljate
SOCK_STREAM ali SOCK_DGRAM, Lahko samo
protokol nastavite na 0, samodejno pa bo uporabil pravilen protokol.
V nasprotnem primeru lahko uporabite getprotobyname() da pogledaš gor
pravilno število protokolov.

Vrnjena vrednost

Novi deskriptor vtičnice, ki bo uporabljen pri naslednjih klicih, ali -1 pri napaki (in errno bo ustrezno nastavljen.)

Primer

struct addrinfo hints, *res;
int sockfd;

// najprej z naslovom getaddrinfo () naložite naslovne strukture ():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;     // AF_INET, AF_INET6, or AF_UNSPEC
hints.ai_socktype = SOCK_STREAM; // SOCK_STREAM or SOCK_DGRAM

getaddrinfo("www.example.com", "3490", &hints, &res);

// naredite vtičnico z informacijami, pridobljenimi iz getaddrinfo ():
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

Poglej tudi

accept(), bind(), getaddrinfo(), listen()

9.24. struct sockaddr in prijatelji

Strukture za ravnanje z internetnimi naslovi

Prototipi

#include <netinet/in.h>

// Vsi kazalci za naslovne strukture so pogosto prikazani na kazalce
// na to vrsto pred uporabo v različnih funkcijah in sistemskih klicih:

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};


// IPv4 AF_INET sockets:

struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET, AF_INET6
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // glej strukturo in_addr, spodaj
    char             sin_zero[8];  // Nič, če želite
};

struct in_addr {
    unsigned long s_addr;          // nalaganje z inet_pton ()
};


// IPv6 AF_INET6 sockets:

struct sockaddr_in6 {
    u_int16_t       sin6_family;   // naslov družine, AF_INET6
    u_int16_t       sin6_port;     // Številka vrat, vrstni red omrežnega bajta
    u_int32_t       sin6_flowinfo; // IPv6 flow information
    struct in6_addr sin6_addr;     // IPv6 address
    u_int32_t       sin6_scope_id; // Scope ID
};

struct in6_addr {
    unsigned char   s6_addr[16];   // load with inet_pton()
};


// Splošna struktura vtičnice, ki je dovolj velika, da jo drži
// struct sockaddr_in ali struct sockaddr_in6 podatki:

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // vse to je oblazinjenje, specifično izvajanje, ga prezri:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};

Opis

To so osnovne strukture za vse syscalls in funkcije, ki se ukvarjajo z internetnimi naslovi. Pogosto boste uporabili getaddrinfo (), da napolnite te strukture in jih nato preberete, ko boste morali.

Struktura sockaddr_in in struct sockaddr_in6 v pomnilniku imata enako začetno strukturo kot struct sockaddr, zato lahko brez kaznivega dejanja svobodno postavite kazalec ene vrste v drugo, razen možnega konca vesolja.

Samo šališ se na to stvar o koncu vesolja… če se vesolje konča, ko strukturo sockaddr_in * postavite v strukturo sockaddr *, obljubim ti, da je to čisto naključje in ne bi smeli niti skrbeti.

Torej, upoštevajte to v mislih, ne pozabite, da vsakič, ko funkcija pravi, da potrebuje strukturo sockaddr *, lahko s to enostavno in brezplačno prenesete strukturo sockaddr_in *, struct sockaddr_in6 * ali struct sockadd_storage * v to vrsto.

Struct sockaddr_in je struktura, uporabljena pri naslovih IPv4 (npr. “192.0.2.10”). Ima družino naslovov (AF_INET), pristanišče v sin_port in naslov IPv4 v sin_addr.

Obstaja tudi to polje sin_zero v strukturi sockaddr_in, za katerega nekateri trdijo, da mora biti nastavljen na nič. Drugi ljudje ne zahtevajo ničesar o tem (dokumentacija o Linuxu sploh sploh ne omenja), in nastavitev na nič ni videti dejansko potrebna. Torej, če se vam zdi všeč, ga nastavite na nič z uporabo memset ().

Zdaj je ta struktura in_addr čudna zver v različnih sistemih. Včasih je nora zveza z vsemi vrstami #defines in drugih neumnosti. Toda, kar morate storiti, je v tej strukturi uporabljeno le polje s_addr, ker ga mnogi sistemi izvajajo samo.

Struct sockadd_in6 in struct in6_addr so zelo podobni, razen če se uporabljajo za IPv6.

Struct sockaddr_storage je struktura, ki jo lahko prenesete, da sprejmete () ali recvfrom (), ko poskušate napisati kodo različice agnostike in ne veste, ali bo novi naslov IPv4 ali IPv6. Struktura struct sockaddr_storage je dovolj velika, da drži oba tipa, za razliko od prvotne male strukture sockaddr.

Primer

// IPv4:

struct sockaddr_in ip4addr;
int s;

ip4addr.sin_family = AF_INET;
ip4addr.sin_port = htons(3490);
inet_pton(AF_INET, "10.0.0.1", &ip4addr.sin_addr);

s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&ip4addr, sizeof ip4addr);
// IPv6:

struct sockaddr_in6 ip6addr;
int s;

ip6addr.sin6_family = AF_INET6;
ip6addr.sin6_port = htons(4950);
inet_pton(AF_INET6, "2001:db8:8714:3a90::12", &ip6addr.sin6_addr);

s = socket(PF_INET6, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)&ip6addr, sizeof ip6addr);

Poglej tudi

accept(), bind(), connect(), inet_aton(), inet_ntoa()

Original: http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html