Winsocki võrgu raamatukogu. Nimetatud torude ja pistikupesade klientide võrdlus. Aitab teooriast, andke mulle WinSock

Tere päevast, Kallid külastajad ja ajaveebi lugejad, sattusin hiljuti ühes foorumis arutelule, mis on Windowsi pesad ja kuidas neid vaadata, ning mõtlesin, et see oleks hea teema artikli jaoks. Mõtlesin ja kirjutasin :)). Arvan, et see märkus on kasulik algajatele süsteemiadministraatoritele, kes mõistavad, kuidas leida OSI mudeli transporditasemel probleeme või kontrollida rakenduse saadavust pordi numbri järgi. Tahan märkida, et need teadmised on põhilised ja nende arusaamine annab teile suurepärase aluse edasiseks tööks igas ettevõttes.

Windowsi pistikupesade rakenduste kontseptsioon

Mis on pesa? See on sisuliselt RAM-i ala, kus rakendus töötab kindlas võrgupordis (TCP/UDP) ja see on rakendus, mis kuulab soovitud porti. See, mis programmeerijate ees seisis, oli lihtne teabe teisaldamine ühe arvuti RAM-ist teise arvuti RAM-i. Seejärel saab seda arvutite abil kujutada järgmiselt:

  • Video
  • Pilt
  • Tekstifail

Windowsi pesa number on RAM-i lahtri number, millega rakendus on seotud. Rakendus, mis on ühendatud RAM-i teatud piirkonnaga, hakkab sinna andmeid kirjutama ja selle mälupiirkonna pesa hakkab edastama väikeste pakettidena, näiteks 65 kB, teisele võrguseadmele. Teisel pool asetatakse need jupid ka RAM-i, soovitavalt samas järjestuses ja selle külje pesa hakkab neid sõeluma ja mõnest rakendusest kasutajale esitama.

Windowsi rakenduste pesade loend

Mul on Windows 8.1 operatsioonisüsteem, näitan sellel kõike, viimati parandasime muide selles vea, et vbs-i käivitamist ei leitud. Et näha, millised pesad millistele rakendustele vastavad ja millised TCP/UDP pordid, tuleb minna kataloogi

C:\Windows\System32\drivers\etc

ja leia sealt teenuste fail, sellel pole laiendit, aga saad selle hiire parema nupuga tavalist notepadi kasutades avada, minu jaoks on selleks notepad++.

Selle faili avamisel näete teenuse (rakenduse) nime, pesa numbrit (TCP/UDP) ja kirjeldust. Näiteks näete, et ftp-server töötab portidel 20 ja 21. Sisuliselt on see koht, kus süsteem seab standardid, mille järgi teenused peaksid töötama.

Kuidas arvutis rakenduste pistikupesasid vaadata

Kasutan kahte meetodit. Kujutagem ette olukorda, kus olete installinud teatud rakenduse, kõik töötab, proovite seda teisest arvutist võrgu kaudu juurde pääseda, kuid te ei saa seda teha. Keelate selle arvuti tulemüüri ja kõik hakkab tööle, väljund on blokeeritud selle rakenduse mõne pordi poolt. Kaks utiliiti aitavad meil seda arvutada, esimene käsurealt ja teisel on mugav graafiline liides.

Avage käsurida administraatorina. Kirjutame sellesse käsu:

Netstat utiliidi ja selle kasutamise kohta lisateabe saamiseks lugege linki. Selle tulemusena saate kokkuvõtliku tabeli, mis sisaldab seda huvitavat:

  • Protokolli tüüp - TCP või UDP
  • Saatja aadress koos portidega
  • Saaja aadress koos portidega
  • Olek – kas kuulab või ühendatud ja suletud
  • PID on rakendust identifitseeriv number

Nagu näites näete, on mul palju seansse portidel 443 ja 80, peamiselt Google Chrome'i brauseris.

Pärast pesa hõivamist ei luba rakendus enam sellel teisel rakendusel avada. Pistikupesa kestab umbes 10 minutit.

Kuidas muuta pistikupesa eluiga

Windowsi operatsioonisüsteemis TTL-i või, nagu seda nimetatakse ka pistikupesa elueaks, muutmiseks peate kasutama registrit. Avage Windows 8.1 registriredaktor. Mine jaotisse

HKLM\System\CurrentControlSet\Services\Tcpip\Parameters

Seal on võti TcpTimedWaitDelay, kui seda pole, peate selle looma. Sisestage vajalik kümnendväärtus. TcpTimedWaitDelay – see parameeter määrab aja, mille jooksul ühendus jääb enne sulgemist olekusse TIME_WAIT. Kui ühendus on olekus TIME_WAIT, ei saa pesapaari uuesti kasutada (see on nn 2MSL olek). Vastavalt RFC793-le peaks see väärtus olema kaks korda pikem kui võrgu maksimaalne paketi eluiga.

Windowsi rakenduse PID-i väljaselgitamiseks peate paremklõpsama alal Start ja valima kontekstimenüüst Tegumihaldur

Leidke tegumihalduris protsessi ID väli. Kui seda seal pole, lisage see.

Winsokk kõigile (1. osa)

Niisiis, mis on Winsock ja millega seda süüakse? Lühidalt öeldes on Winsock liides, mis lihtsustab võrgurakenduste arendamist Windowsi all. Kõik, mida me peame teadma, on see, et Winsock on liides rakenduse ja andmeedastust teostava transpordiprotokolli vahel.

Sisearhitektuuri detailidesse me ei lasku, sest meid ei huvita, kuidas see sisemiselt üles ehitatakse, vaid see, kuidas Winsocki poolt kasutajale tööks pakutavaid funktsioone kasutada. Meie ülesanne on konkreetsete näidete abil mõista WinsockAPI toimemehhanismi. "Milleks seda kasutada saab? Lõppude lõpuks on raamatukogusid, mis lihtsustavad võrkudega töötamist ja millel on lihtne liides?" - te küsite. Olen selle väitega osaliselt nõus, kuid minu arvates ei saa eksisteerida täiesti universaalseid, kõikidele ülesannetele orienteeritud raamatukogusid. Ja pealegi on palju mõnusam kõike ise nuputada, tundmata end kohmetuna “musta kasti” ees, mille tööpõhimõttest sa aru ei saa, vaid kasutad ainult töövahendina :) Kogu materjal on mõeldud algajatele. Arvan, et selle valdamisega probleeme ei teki. Kui teil on veel küsimusi, kirjutage [e-postiga kaitstud]. Vastan kõigile. Näidete illustreerimiseks kasutame Microsoft VC++ koodifragmente. Nii et alustame!

Winsock – kust alustada?

Niisiis, esimene küsimus on - kui Winsock on olemas, siis kuidas seda kasutada? Tegelikkuses pole kõik nii keeruline. Esimene etapp – teekide ja päiste ühendamine.

#include "winsock.h" või #include "winsock2.h" – olenevalt sellest, millist Winsocki versiooni te kasutate
Samuti peavad projekti kaasama kõik vastavad lib-failid (Ws2_32.lib või Wsock32.lib)

2. samm - lähtestamine.

Nüüd saame WinsockAPI funktsioone turvaliselt kasutada. ( täielik nimekiri funktsioonid leiate MSDN-i vastavatest jaotistest).

Winsocki lähtestamiseks helistage funktsioonile WSAStartup

int WSAStartup(WORD wVersionRequested, (in) LPWSADATA lpWSAData (out));


WORD-parameeter wVersionRequested – madal bait – versioon, kõrge bait – subversioon, Winsocki liides. Võimalikud versioonid on 1.0, 1.1, 2.0, 2.2... Selle parameetri “kokkupanemiseks” kasutame makrot MAKEWORD. Näiteks: MAKEWORD (1, 1) - versioon 1.1. Hilisemaid versioone eristab uute funktsioonide ja laiendusmehhanismide olemasolu. Parameeter lpWSAData on kursor WSADATA struktuurile. Funktsioonist naastes sisaldab see struktuur teavet selle WinsockAPI versiooni kohta, mille initsialiseerisime. Põhimõtteliselt võib seda ignoreerida, aga kui kedagi huvitab, mis seal sees on, siis ärge olge laisk, avage dokumentatsioon;)

Praktikas näeb see välja järgmine:

WSADATA ws;
//...
if (FILED (WSAStartup (MAKEWORD(1, 1), 1), &ws)))
{
// Viga...
viga = WSAGetLastError();
//...
}


Sel juhul saate laiendatud veateavet, kutsudes välja WSAGetLastError(). See funktsioon tagastab veakoodi (tüüp int)

3. samm - pistikupesa loomine.

Niisiis, võime jätkata järgmise etapiga - luua Winsockis peamine suhtlusvahend - pistikupesa. WinsockAPI vaatenurgast on pesa käepide, millega saab andmeid vastu võtta või saata. Praktikas näeb kõik välja nii: loome teatud omadustega sokli ja kasutame seda ühenduse loomiseks, andmete vastuvõtmiseks/edastamiseks jne. Nüüd teeme väikese kõrvalekaldumise... Seega tuleb sokli loomisel täpsustada selle parameetrid: socket kasutab TCP/IP protokolli ehk IPX (kui TCP/IP, siis mis tüüpi jne). Kuna selle artikli järgmised jaotised keskenduvad TCP/IP-protokollile, keskendume seda protokolli kasutavate pistikupesade funktsioonidele. Saame luua kahte peamist tüüpi TCP/IP-protokolli kasutavaid pesasid - SOCK_STREAM ja SOCK_DGRAM (jätame RAW-pesa esialgu rahule :)). Erinevus seisneb selles, et esimest tüüpi pesade puhul (neid nimetatakse ka TCP-ks ehk ühendusepõhiseks pesaks) peab pesa andmete saatmiseks pidevalt hoidma ühendust adressaadiga, samas on tagatud paketi kättetoimetamine adressaadini. . Teisel juhul pole püsiühendust vaja, kuid infot selle kohta, kas pakett saabus või mitte, on võimatu hankida (nn UDP ehk ühenduseta pesad). Nii esimest kui teist tüüpi pistikupesadel on oma praktilised rakendused. Alustame tutvumist TCP (ühenduspõhiste) pistikupesadega.

Esiteks deklareerime seda:

Pistikupesa saate luua pesa funktsiooni abil

SOCKET pesa (int af (in), // protokoll (TCP/IP, IPX...)
int tüüp (in), // sokli tüüp (SOCK_STREAM/SOCK_DGRAM)
int protokoll (in) // jaoks Windowsi rakendused võibolla 0
);


Näide:

if (INVALID_SOCKET == (s = pesa (AF_INET, SOCK_STREAM, 0)))
{
// Viga...
viga = WSAGetLastError();
// ...
}


Vea korral tagastab funktsioon INVALID_SOCKET. Sel juhul saate laiendatud veateavet, kutsudes välja WSAGetLastError().

4. samm - looge ühendus.

Eelmises näites lõime pistikupesa. Mida me sellega nüüd tegema peaksime? :) Nüüd saame seda pesa kasutada andmete vahetamiseks teiste winsocki klientidega ja palju muud. Teise masinaga ühenduse loomiseks peate teadma selle IP-aadressi ja porti. Kaugmasin peab "kuulama" seda porti sissetulevate ühenduste jaoks (st see toimib serverina). Sel juhul on meie rakendus klient.

Ühenduse loomiseks kasutage ühendamise funktsiooni.

int connect (SOCKET s, // pesa (meie pesa)
const struct sockaddr FAR *nimi, // aadress
int namelen // aadressi pikkus
);


Näide:

//Deklareerige aadressi salvestamiseks muutuja
sockaddr_in s_addr;

// Täitke see:
ZeorMemory(&s_addr, suurus(s_addr));
// aadressi tüüp (TCP/IP)
s_addr.sin_family = AF_INET;
//serveri aadress. Sest TCP/IP tähistab aadresse numbrilisel kujul, seejärel tõlkimiseks
// aadressid kasutavad funktsiooni inet_addr.
s_addr.sin_addr.S_un.S_addr = inet_addr("193.108.128.226");
// Port. Kasutame funktsiooni htons, et teisendada pordi number tavalisest numbrist //TCP/IP esitusviisiks.
s_addr.sin_port = htons(1234);


Kui esineb tõrge, tagastab funktsioon SOCKET_ERROR.
Nüüd on pesa s seotud kaugmasinaga ja saab ainult sellelt andmeid saata/vastu võtta.

5. samm – saatke andmed.

Andmete saatmiseks kasutame saatmisfunktsiooni

int send(SOCKET s, // saatmine pesa
const char FAR *buf, // osutab andmetega puhvrile
int len, // andmete pikkus
);


Näide selle funktsiooni kasutamisest:

if (SOCKET_ERROR == (saata (s, (char*) & buff), 512, 0))
{
// Viga...
viga = WSAGetLastError();
// ...
}


Kui esineb tõrge, tagastab funktsioon SOCKET_ERROR.
Andmepaketi pikkust piirab protokoll ise. Kuidas andmepaketi maksimaalset pikkust teada saada, vaatame järgmisel korral. Funktsioon ei naase enne, kui andmed on saadetud.

6. samm – nõustuge andmetega.

Recv funktsioon võimaldab meil saada andmeid masinalt, millega oleme eelnevalt ühenduse loonud.

int recv(SOCKET s, // adressaadi pesa
char FAR *buf, // puhvri aadress andmete vastuvõtmiseks
int len, // andmete vastuvõtmise puhvri pikkus
int lipud // lipud (võib olla 0)
);


Kui te ei tea sissetulevate andmete suurust ette, ei tohiks vastuvõtupuhvri pikkus olla väiksem kui maksimaalne suurus pakend, vastasel juhul ei pruugi sõnum sinna mahtuda ja lõigatakse ära. Sel juhul tagastab funktsioon veateate.
Näide:

int tegelik_len = 0;

Kui (SOCKET_ERROR == (actual_len = recv (s, (char*) & buff), max_packet_size, 0))
{
// Viga...
viga = WSAGetLastError();
// ...
}


Kui andmed on vastu võetud, tagastab funktsioon vastuvõetud andmepaketi suuruse (näites - tegelik_leen) Vea korral tagastab funktsioon SOCKET_ERROR. Pange tähele, et saatmise/vastuvõtmise funktsioonid ootavad, kuni saabub ajalõpp või saadetakse/vastu võetakse andmepakett. See põhjustab programmi töös viivitust. Kuidas seda vältida, lugege järgmistest numbritest.

6. samm - sulgege ühendus.

Aktiivse ühenduse sulgemise protseduur tehakse väljalülitamise ja sulgemise funktsioonide abil. Ühenduse sulgemisi on kahte tüüpi: katkendlikud ja graatsilised. Esimene tüüp on pistikupesa (closesocket) hädaolukorra sulgemine. Sel juhul katkeb ühendus kohe. Closocketti helistamine mõjub koheselt. Pärast closesocketile helistamist pole pistikupesale enam ligipääsetav. Lugege tulevastes väljaannetes, kuidas pistikupesa sulgemise/sulgemise abil sulgeda, kuna see teema nõuab rohkem täielikud teadmised Winsock.

int shutdown(SOCKET s, // Suletav pesa
int kuidas // Sulgemismeetod
);


int closesocket(SOCKET s // Suletud pesa
);


Näide:

sulgemispesa(d);

Nagu näete, on Winsocki andmevahetusmehhanism väga lihtne. Programmeerija peab kaugmasinate vaheliseks suhtluseks välja töötama ainult oma "protokolli" ja rakendama seda nende funktsioonide abil. Muidugi ei kajasta vaadatud näited Winsocki kõiki võimalusi. Püüame oma artiklites kaaluda Winsockiga töötamise kõige olulisemaid meie arvates. Püsige lainel. :)
Loe järgmisest numbrist:

  • Kirjutame lihtsat winsocki rakendust.
  • UDP-pesad – garantiita pakettide vastuvõtt/tarnimine
  • Lahendame pistikupesade "blokeerimise" probleemi.

Aitab teooriast, andke mulle WinSock!

Niisiis, nüüd on WinSockil kaks versiooni: 1.1 ja 2. Soovitan kasutada teist versiooni. Mida me edasi teeme? Kirjutame mängust "Rock-Paper-Scissors" klient-server versiooni.Server saab olema mitme lõimega konsoolirakendus, klient kirjutatakse DirectX-is.

Alustuseks näitan, kuidas WinSock on üles ehitatud ja selgitan erinevaid viise pesa programmeerimine ja kirjeldage selleks kasutatavaid funktsioone. Pärast seda, kui oleme WinSockiga tegelenud, kasutame seda ülalmainitud mänguasja kirjutamiseks.

WinSocki kasutamiseks peate lisama vastava päisefaili ja lisama ws2_32.lib teie projektile. Nüüd on kõik WinSocki all programmeerimiseks valmis. Aga kuidas see töötab ja kust alustada?

Pistikupesade programmeerimiseks on mitu võimalust. Saate kasutada UNIX-i/Berkley põhifunktsioone või spetsiaalseid funktsioone Microsoft Windows või kasutage pistikupesade objektorienteeritud MFC versiooni. Alguses tahtsin kasutada soklite OO versiooni, sest... Arvan, et klassid muudavad API paremini seeditavaks. Kuid need pole lihtsalt klassid, need on MFC. “Tehtud nii raskeks kui võimalik” on nende loosung. Ei, MFC on suurepärane, aga tõsiasi, et tuleb luua Win32 rakendus ja pistikupesad läbi käia Windowsi sõnumid, mis saadetakse teie programmi, on häiriv, eriti serveri puhul. Miks me peame tegema serveri Win32 rakenduseks? See on mõttetu. Asjade lihtsustamiseks kasutame kõige elementaarsemaid UNIX/Berkley serveri funktsioone.

Andmetüübid.

Sockaddr_in(sockaddr)

Kirjeldus: tüüp sockaddr_in kasutatakse pistikupesade kaudu ühenduse kirjeldamiseks. See sisaldab ka IP-aadressi ja pordi numbrit. See on TCP-le orienteeritud versioon sockaddr. Me kasutame sockaddr_in pistikupesade loomiseks.

struct sockaddr_in (lühike sin_perekond; // protokolli tüüp (peab olema AF_INET) u_short sin_port; // Pistikupesa pordi number struct in_addr sin_addr; // IP-aadress char sin_zero[ 8 ] ; // pole kasutatud };

Kirjeldus: WSAData kasutatakse teegi laadimisel ja lähtestamisel ws2_32.dll. Seda tüüpi kasutatakse funktsiooni tagastusväärtusena WSAStartup(). Kasutage seda oma arvutis WinSocki versiooni määramiseks.

Kirjeldus: Seda andmetüüpi kasutatakse pesa deskriptori salvestamiseks. Neid deskriptoreid kasutatakse pistikupesa tuvastamiseks. Tegelikult on SOCKET lihtsalt allkirjastamata sisend.

Noh, alustame programmeerimist

Kõigepealt peate alla laadima ws2_32.dll:

// See kood peab olema kõigis programmides, mis kasutavad WinSocki WSADATA w; // kasutatakse pesa versiooni teabe salvestamiseks int error = WSAStartup (0x0202 , &w) ; // täitke w if (viga) ( // mingi viga tagasi ; ) if (w.wVersion != 0x0202 ) ( // pistikupesade vale versioon! WSACleanup(); // laadige maha ws2_32.dll tagasi ; )

Tõenäoliselt on teil küsimus – mida tähendab 0x0202? See tähendab versiooni 2.2. Kui on vaja versiooni 1.1, tuleb see number muuta 0x0101-ks. WSAStartup() täidab WSADATA muutuja ja laadib dünaamilise sokliteegi. WSACleanup() laadib selle vastavalt maha.

Loo pistikupesa

SOCKET s = pesa(AF_INET, SOCK_STREAM, 0); // Loo pesa

Üldiselt on see kõik, mis on pistikupesa loomiseks vajalik, kuid peate siiski siduma ( siduda) mõne pordiga, kui soovite sellega otse tööd alustada. Konstant AF_INET on defineeritud kuskil failis winsock2.h. Kui mõni funktsioon nõuab näiteks aadressipere ( pöörduge perekonna poole) või int af, määrake lihtsalt AF_INET. SOCK_STREAM konstant on vajalik voogesituse (TCP/IP) pesa loomiseks. Saate luua ka UPD-pesa, kuid nagu eespool öeldud, pole see nii usaldusväärne kui TCP. Viimase parameetri väärtus on null. See tähendab lihtsalt seda, et teie jaoks valitakse automaatselt õige protokoll (see peaks olema TCP/IP).

Määrake nõutav port pesa (bind socket) pordile ja pesale:

// Pea meeles! Peaksite siduma ainult serveri pistikupesasid, mitte kliente // WSAStartup funktsioon kutsutud sockaddr_in addr; // muutuja TCP-sokli jaoks addr.sin_family = AF_INET; // Aadressiperekond – Internet addr.sin_port = htons(5001); // Määrake pesale port 5001 addr.sin_addr.s_addr = htonl (INADDR_ANY) ; // Ilma konkreetse aadressita if (bind(s, (LPSOCKADDR) &addr, sizeof (addr) ) == SOCKET_ERROR) ( // WSACleanup error () ; // unload WinSocki tagastamine ; )

See kood võib tunduda segane, kuid see pole nii. Adr kirjeldab pistikupesa, täpsustades pordi. Kuid võib tekkida küsimus: "Aga IP-aadress?" Määrasime selle INADDR_ANY. See võimaldab meil mitte muretseda konkreetse aadressi pärast. Peame märkima ainult pordi numbri, mida tahame ühenduse loomiseks kasutada. Miks me kasutame htons() ja htonl()? Need funktsioonid teisendavad vastavalt lühike ja pikk tüüpi muutujad võrgule arusaadavasse vormingusse. Näiteks kui pordi number on 7134 (lühinumber), peate helistama funktsioonile htons(7134). IP-aadressi jaoks peame kasutama htonl(). Aga mis siis, kui tahame tegelikult IP-aadressi määrata? Peame kasutama funktsiooni inet_addr(). Näiteks inet_addr("129.42.12.241"). See funktsioon teisendab aadressi stringi, eemaldab sellest punktid ja teisendab selle pikaks tüübiks.

Seotud pordi kuulamine ( kuula port)

// WSAStartup() kutsus // SOCKET s osutab juba loodud soklile if (kuula(s,5 ) ==SOCKET_ERROR) ( // viga! Kuulamine pole võimalik WSACleanup(); tagasi ; ) //kuula:

Siin oleme vastu võtnud ühenduse kliendilt, kes soovib mänguga liituda. Reas on veel midagi huvitavat kuulake(SOCKET s, int mahajäämus). Mis on juhtunud mahajäämus? Mahajäämus on klientide arv, kes saavad ühenduse luua ajal, mil me pistikupesa kasutame, st. need kliendid peavad ootama, kuni server teiste klientidega ühenduse loomiseks läbi räägib. Näiteks kui määrate mahajäämusena 5 ja liituda proovib 7 inimest, saavad viimased 2 veateate ja on sunnitud hiljem ühenduse looma. Tavaliselt on see parameeter vahemikus 2 kuni 10, sõltuvalt serveri maksimaalsest võimsusest.

Püüab ühendada pistikupessa ( proovige pistikupesa ühendada)

// WSAStartup() kutsus // SOCKET s osutab juba loodud soklile // s on seotud pordiga sockaddr_in abil. sockaddr_in sihtmärk; sihtmärk.sin_perekond = AF_INET; // aadresside perekond - Internet sihtmärk.sin_port = htons(5001); // serveri port target.sin_addr.s_addr = inet_addr ("52 .123 .72 .251 ") ; // Serveri IP-aadress if (ühenda(d, sihtmärk, suurus (siht) ) == SOCKET_ERROR) ( // Ühenduse viga WSACleanup(); tagasi ; )

Põhimõtteliselt on see ühendusetaotluse jaoks kõik. Muutuv sihtmärk tähistab pesa, millega proovime ühendust luua (server). Funktsioon connect() nõuab pesa (kliendipool), ühenduspesa kirjeldust (serveri pool) ja kirjeldusmuutuja suurust. See funktsioon saadab lihtsalt ühenduse päringu ja ootab serverilt vastust, teatades samal ajal esinevatest vigadest.

Ühenduse vastuvõtmine ( ühenduse vastuvõtmine)

// WSAStartup() kutsus // SOCKET s osutab juba loodud soklile // s on seotud pordiga sockaddr_in abil. // socket s kuulab#define MAX_CLIENTS 5 ; // selguse huvides int klientide_arv = 0 ; SOCKET klient[ MAX_CLIENTS] ; // kliendi pesad sockaddr klient_sokk[ MAX_KLIENTE] ; // kliendi pesade kirjeldus while (klientide_arv< MAX_CLIENTS) // kas MAX_CLIENTS klienti on ühendatud?( klient[ klientide_arv] = // nõustu ühendustaotlusega aktsepteerima (s, kliendi_sokk[ klientide_arv] , &addr_suurus) ; if (klient[ klientide_arv] == INVALID_SOCKET) ( // Ühenduse viga WSACleanup(); tagasi ; ) muidu ( // klient liitus edukalt // alustage kliendiga suhtlemiseks lõime algusThread (klient[klientide_arv] ); klientide_arv++; ) )

Üldiselt on siin kõik selge. Klientide arvu ei pea määrama avaldisega MAX_CLIENTS. Seda kasutatakse siin ainult selle koodi selguse ja mõistmise hõlbustamiseks. klientide_arv- muutuja, mis sisaldab ühendatud klientide arvu. klient on SOCKET-tüüpi massiiv, mida kasutatakse ühendatud klientide soklikirjelduste salvestamiseks. client_sock - tüüpi massiiv sockaddr, mis sisaldab teavet ühenduse tüübi, pordi numbri jne kohta. Tavaliselt me ​​seda tegelikult ei vaja, kuigi mõned funktsioonid nõuavad ühenduse kirjeldust parameetrina. Põhisilmus lihtsalt ootab ühenduse taotlust, võtab selle vastu ja käivitab kliendiga suhtlemiseks lõime.

Andmete saatmine ( kirjutamine või saatmine)

// SOCKET s on lähtestatud söepuhver[11]; // 11 tähemärgi puhver sprintf(puhver, "Mis iganes:"); saatma (s, buffer, sizeof (buffer) , 0 ) ;

Funktsiooni send() teine ​​parameeter on tüüpi muutuja char FAR *buf , mis on kursor andmepuhvrile, mida tahame saata. Kolmas parameeter on saadetava puhvri suurus. Viimane parameeter on erinevate lippude seadistamiseks. Me ei kasuta seda ja jätame selle nulliks.

Andmete vastuvõtmine ( lugemist või vastuvõtmist)

// SOCKET s on lähtestatud söepuhver[80]; // 80 tähemärgi puhver recv(s, buffer, sizeof(buffer), 0);

recv(), on suures osas sarnane saada(), välja arvatud see, et me ei edasta andmeid, vaid võtame vastu.

Teisendab stringi aadressi (IP-aadress või hostinimi) numbriliseks aadressiks, mida kasutatakse ühenduse loomisel ( IP-aadressi lahendamine).

Õpetus WINSOCKil mängimiseks

Pistikupesad(pesad) on kõrgetasemeline ühtne liides sideprotokollidega suhtlemiseks. Tehnilises kirjanduses on selle sõna erinevaid tõlkeid - neid nimetatakse pistikupesadeks, pistikuteks, kassettideks, torudeks jne. Kindla venekeelse termini puudumise tõttu nimetatakse selles artiklis pesasid pistikupesadeks ja mitte millekski muuks.

Pistikupesade programmeerimine ei ole iseenesest keeruline, kuid olemasolevas kirjanduses on seda kirjeldatud üsna pealiskaudselt ning Windows Sockets SDK sisaldab palju vigu nii tehnilises dokumentatsioonis kui ka kaasasolevates demodes. Lisaks on UNIXis ja Windowsis pistikupesade rakendamisel olulisi erinevusi, mis tekitab ilmseid probleeme.

Autor püüdis anda kõige täielikuma ja sidusama kirjelduse, hõlmates mitte ainult põhipunkte, vaid ka mõningaid nüansse, mida tavalised programmeerijad ei tea. Ajakirja artikli piiratud ulatus ei võimalda meil kõigest rääkida, seetõttu tuli keskenduda ainult ühele pistikupesade teostusele - Winsock 2 raamatukogule, ühele programmeerimiskeelele - C/C++(kuigi öeldu on enamasti vastuvõetav Delphi, Perli jne jaoks) ja ühte tüüpi pistikupesad - sünkroonsete pistikupesade blokeerimine.

ALMA MATER

Peamine abi pistikupesade õppimisel on Windows Sockets 2 SDK. SDK on dokumentatsioon, päisefailide komplekt ja arendaja tööriistad. Dokumentatsioon pole kuigi hea - kuid see on siiski üsna asjatundlikult kirjutatud ja võimaldab, kuigi mitte ilma raskusteta, pistikupesasid meisterdada isegi ilma muu kirjanduse abita. Pealegi on enamik turul saadaolevaid raamatuid kirjelduse terviklikkuse ja läbimõeldusega selgelt alla Microsoftile. SDK ainus puudus on see, et see on täielikult inglise keeles (mõnede jaoks on see väga oluline).

SDK-s sisalduvatest tööriistadest tahaksin kõigepealt esile tõsta utiliiti sockeye.exe - see on arendaja jaoks tõeline "testistend". See võimaldab interaktiivselt helistada erinevatele pesafunktsioonidele ja neid oma äranägemise järgi manipuleerida.

Demonstratsiooniprogrammid pole kahjuks vigadeta, kohati üsna jämedad ja sugestiivsed – kas neid näiteid on üldse testitud? (Näiteks programmi simples.c lähtekoodis kasutatakse send- ja sendto-funktsioonide väljakutses strlen asemel sizeof) Samas sisaldavad kõik näited palju üksikasjalikke kommentaare ja paljastavad üsna huvitavaid võtteid ebatraditsiooniline programmeerimine, seega tasub nendega siiski tutvuda.

Programmeerimispesadele ja kõigele sellega seonduvale pühendatud WEB-ressurssidest tahaksin ennekõike märkida järgmist: sockaddr.com; www.winsock.com ja www.sockets.com.

Pistikupesade ülevaade

Winsocki teek toetab kahte tüüpi pistikupesasid - sünkroonne (blokeeritav) Ja asünkroonne (mitteblokeeriv). Sünkroonsed pistikupesad hoiavad kontrolli all toimingu ajal, asünkroonsed pesad aga tagastavad kohe juhtimise, jätkavad täitmist taustal ja teavitavad kutsumiskoodi, kui see lõpeb.

Windows 3.x toetab ainult asünkroonseid pistikupesasid, kuna ettevõtte multitegumtöötlusega keskkonnas ripub ühe ülesande kontrolli alla võtmine kõik teised, sealhulgas süsteemi enda. Windows 9x\NT toetab mõlemat tüüpi pistikupesasid, kuid kuna sünkroonseid pesasid on lihtsam programmeerida kui asünkroonseid, siis viimaseid laialdaselt ei kasutata. See artikkel on pühendatud eranditult sünkroonsetele pistikupesadele (asünkroonne on eraldi arutelu teema).

Pistikupesad võimaldavad töötada mitmesuguste protokollidega ja on mugav protsessoritevahelise suhtluse vahend, kuid selles artiklis räägime ainult TCP/IP-protokolliperekonna pesadest, mida kasutatakse andmete vahetamiseks Interneti-sõlmede vahel. Kõiki teisi protokolle, nagu IPX/SPX, NetBIOS, ajakirjaartikli piiratud ulatuse tõttu arvesse ei võeta.

Olenemata tüübist jagunevad pistikupesad kahte tüüpi - voogesitus Ja datagramm . Voopistikupesad töötavad ühenduspõhiselt, tagades mõlema osapoole tugeva tuvastamise ning garanteerides andmete edastamise terviklikkuse ja edu. Datagrammi pesad töötavad ilma ühendust loomata ega võimalda ei saatjat tuvastada ega andmeedastuse edukust kontrollida, kuid on märgatavalt kiiremad kui voogedastuspesad.

Ühte või teist tüüpi pistikupesa valiku määrab transpordiprotokoll, millel server töötab – klient ei saa suvaliselt luua voogedastusühendust datagrammiserveriga.

Kommenteeri : Datagrammi pesad põhinevad UDP-protokollil, voogesituse pesad aga TCP-l.

Esimene samm, teine, kolmas

Winsock 2.x teegiga töötamiseks peate lisama käsu " #kaasa " ja linkeri käsureal määrake "ws2_32.lib". Microsoft Visual Studio arenduskeskkonnas klõpsake lihtsalt<Alt-F7>, minge vahekaardile "Link" ja real "Objekt/teegi moodulid" loetletud teekide loendisse, lisage "ws2_32.lib", eraldades selle ülejäänud osast tühikuga.

Enne Winsocki teegi funktsioonide kasutamise alustamist peate selle tööks ette valmistama, kutsudes funktsiooni " intWSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData)" edastades sõna kõrge baidi wVersionRequested nõutava versiooni number ja molli puhul - alamversiooni number.

Argument lpWSAData peab osutama struktuurile WSADATA, mis eduka initsialiseerimise korral sisaldab teavet teegi tootja kohta. See ei paku erilist huvi ja rakendus võib seda ignoreerida. Kui lähtestamine ebaõnnestub, tagastab funktsioon nullist erineva väärtuse.

Teine samm– "pistikupesa" objekti loomine. Seda teeb funktsioon " PISKASTpistikupesa (int af, int tüüp, int protokoll)". Esimene argument vasakul näitab kasutatavate protokollide perekonda. Interneti-rakenduste puhul tuleks see seada väärtusele AF_INET.

Järgmine argument määrab loodava sokli tüübi - voogesitus (SOCK_STREAM) või datagramm (SOCK_DGRAM) (ka töötlemata pistikupesad on olemas, kuid Windows neid ei toeta – vaadake jaotist "Toorpesad").

Viimane argument määrab, millist transpordiprotokolli tuleks kasutada. Väärtus null vastab vaikevalikule: voopesade jaoks TCP ja datagrammi pesade jaoks UDP. Enamikul juhtudel pole mõtet protokolli käsitsi seadistada ja tavaliselt tuginetakse automaatsele vaikevalikule.

Kui funktsioon õnnestub, tagastab see pesa käepideme, vastasel juhul INVALID_SOCKET.

Järgmised sammud sõltuvad sellest, kas rakendus on server või klient. Allpool kirjeldatakse neid kahte juhtumit eraldi.

Klient: samm kolm - Kaughostiga ühenduse loomiseks peab voopesa funktsiooni kutsuma "intühendada (SOCKET s, const struct sockaddr FAR* nimi, int namelen)". Datagrammi pesad töötavad ilma ühenduseta, seega tavaliseltära kutsu ühenduse funktsiooni.

Märge: sõna “tavaliselt” taga peitub üks keeruline programmeerimisnipp - ühenduskõne võimaldab datagrammi pesal sõlmega andmeid vahetada mitte ainult funktsioonide sendto, recvfrom, vaid ka mugavama ja kompaktsema send- ja recv-ga. Seda peenust kirjeldatakse Winsocketi SDK-s ja seda kasutavad laialdaselt nii Microsoft ise kui ka kolmandate osapoolte arendajad. Seetõttu on selle kasutamine täiesti ohutu.

Esimene vasakul olev argument on soklifunktsiooni tagastatud sokli deskriptor; teine ​​on osutaja struktuurile " sockaddr", mis sisaldab kaugsõlme aadressi ja porti, millega ühendus luuakse. Sockaddr struktuuri kasutavad paljud funktsioonid, seega on selle kirjeldus lisatud eraldi jaotisesse "Aadress üks, aadress kaks". Viimane argument ütleb funktsioon sockaddr struktuuri suurus.

Pärast helista ühendada süsteem proovib luua ühendust määratud hostiga. Kui seda mingil põhjusel teha ei saa (aadress on valesti seatud, sõlm puudub või ripub, arvuti pole võrgus), tagastab funktsioon nullist erineva väärtuse.

Server: samm kolm– Enne kui server saab pesa kasutada, peab see siduma selle kohaliku aadressiga. Kohalik aadress, nagu iga teine ​​Interneti-aadress, koosneb hosti IP-aadressist ja pordi numbrist. Kui serveril on mitu IP-aadressi, saab sokli siduda kõigiga korraga (selleks tuleks IP-aadressi asemel määrata konstant INADDR_ANY, mis võrdub nulliga) või mis tahes konkreetsega.

Sidumine toimub funktsiooni "" kutsumisega intsiduda (SOCKET s, const struct sockaddr FAR* nimi, int namelen)". Esimene argument vasakul on socket-funktsiooni tagastatud sokli deskriptor, millele järgneb osuti struktuurile sockaddr ja selle pikkus (vt jaotist " Aadress üks, aadress kaks").

Rangelt võttes peab klient siduma pesa ka kohaliku aadressiga enne selle kasutamist, kuid ühendamise funktsioon teeb seda selle eest, seostades pesa ühe juhuslikult valitud pordiga vahemikus 1024-5000. Server peab "istuma" etteantud pordis, näiteks 21 FTP jaoks, 23 telneti jaoks, 25 SMTP jaoks, 80 WEB jaoks, 110 POP3 jaoks jne. Seetõttu peab ta köitmise "käsitsi" läbi viima.

Funktsioon tagastab õnnestumise korral nulli, muidu nullist erineva.

Server: neljas samm - Pärast sidumise lõpetamist läheb voogedastusserver ühenduste ootamise režiimi, kutsudes välja funktsiooni " intkuulake (SOCKET s, sisemine mahajäämus)", Kus s– pistikupesa deskriptor ja mahajäämus– sõnumijärjekorra maksimaalne lubatud suurus.

Järjekorra suurus piirab samaaegselt töödeldavate ühenduste arvu, seega peaksite selle valima targalt. Kui järjekord on täiesti täis, saab järgmine klient ühenduse loomisel keeldumise (RST-lipuga TCP-pakett). Samas määrab maksimaalse mõistliku ühenduste arvu serveri jõudlus, RAM-i hulk jne.

Datagrammi serverid ei kutsu kuulamisfunktsiooni, kuna töötab ilma ühendust loomata ja saab kohe pärast sidumist helistada recvfromile, et lugeda sissetulevaid sõnumeid kohe pärast sidumist, möödudes neljandast ja viiendast sammust.

Server: samm viis– ühenduse taotlused hangib järjekorrast funktsioon " PISKASTaktsepteerima (SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen)", mis loob automaatselt uue pesa, teostab sidumise ja tagastab selle käepideme struktuuri sockaddr salvestab teavet ühendatud kliendi kohta (IP-aadress ja port). Kui järjekord on aktsepteerimise kutsumisel tühi, ei tagasta funktsioon juhtimist enne, kui serveriga on loodud vähemalt üks ühendus. Kui ilmneb viga, tagastab funktsioon negatiivse väärtuse.

Sest paralleelne töö mitme kliendiga luua kohe peale päringu järjekorrast eemaldamist uus lõim (protsess), edastades sellele aktsepteerimisfunktsiooni poolt loodud sokli deskriptori, seejärel eemaldada järgmine päring uuesti järjekorrast jne. Vastasel juhul ei saa server kõiki teisi teenindada enne, kui üks klient on lõpetanud.

koos - Kui ühendus on loodud, saavad voopesad suhelda kaughostiga, helistades funktsioonidele " intsaada (SOCKET s, const char FAR * buf, int len,int lipud)"Ja" intrev (SOCKET s, char FAR* buf, int len, int lipud)" andmete saatmiseks ja vastuvõtmiseks.

Funktsioon saada tagastab kontrolli kohe pärast täitmist, olenemata sellest, kas vastuvõttev pool sai meie andmed või mitte. Eduka lõpetamise korral tagastab funktsioon koguse edastatud (ei edastata!) andmed - st edukas täitmine ei viita edukale kohaletoimetamisele! Üldjuhul tagab TCP (millele voogesituspesad toetuvad) andmete eduka edastamise adressaadile, kuid ainult siis, kui ühendust ei katkestata enneaegselt. Kui ühendus katkeb enne ülekande lõppu, jäävad andmed edastamata, kuid helistamiskood ei saa selle kohta teadet! Ja viga tagastatakse ainult siis, kui ühendus katkeb enne kutsudes funktsiooni send!

Funktsioon on rev tagastab kontrolli alles pärast vähemalt ühe baidi saamist. Täpsemalt ootab ta terviku saabumist datagrammid. Datagramm on saatmiskõnega saadetud ühe või mitme IP-paketi kogum. Lihtsamalt öeldes võtab iga recv-kõne korraga vastu nii palju baite, kui saatmisfunktsiooni saatis. See eeldab, et funktsioon recv on varustatud piisavalt suure puhvriga, vastasel juhul tuleb seda mitu korda kutsuda. Kõigi järgnevate kõnede puhul võetakse aga andmed kohalikust puhvrist, mitte aga võrgust, sest TCP pakkuja ei saa vastu võtta osa datagrammist, vaid ainult kogu see.

Mõlemat funktsiooni saab juhtida kasutades lipud, mis edastati ühes muutujas tüübiga in kolmanda argumendina vasakult. See muutuja võib võtta ühe kahest väärtusest: MSG _PEEK Ja MSG _OOB.

Lipp MSG_PEEK paneb funktsioon recv andmeid lugemise asemel vaatama. Vaatamine, erinevalt lugemisest, ei hävita vaadatavaid andmeid. Mõned allikad väidavad, et kui lipp MSG_PEEK on seatud, ei viivita funktsioon recv juhtimist, kui kohalikus puhvris pole andmeid koheseks vastuvõtmiseks. See ei ole tõsi! Samamoodi kohtate mõnikord otsest vale väidet, et MSG_PEEK lipuga saatmisfunktsioon tagastab juba edastatud baitide arvu (saatmiskõne ei blokeeri juhtimist). Tegelikult eirab saatmisfunktsioon seda lippu!

Lipp MSG_OOB on edastamiseks ja vastuvõtmiseks kiireloomuline (Bändist väljas) andmed. Kiireloomulised andmed ei oma võrgu kaudu saatmisel eelist teiste ees, vaid võimaldavad teil kliendi tavaandmete voo tavapärasest töötlemisest lahti rebida ja talle “kiireloomulist” teavet anda. Kui andmeid edastas saatmisfunktsioon, mille lipp on seatud MSG_OOB, tuleb nende lugemiseks määrata ka recv funktsiooni lipp MSG_OOB.

Kommenteeri: Soovitatav on hoiduda rakendustes ajatundlike andmete kasutamisest. Esiteks on need täiesti valikulised – palju lihtsam, töökindlam ja elegantsem on luua hoopis eraldi TCP-ühendus. Teiseks puudub nende rakendamise osas üksmeel ja erinevate tootjate tõlgendused on üksteisest väga erinevad. Seega pole arendajad veel jõudnud lõplikule kokkuleppele, kuhu kiireloomulisuse osuti peaks osutama: kas kiireloomuliste andmete viimasele baidile või kiireloomuliste andmete viimasele baidile järgnevale baidile. Selle tulemusena ei saa saatja kunagi kindel olla, et saaja suudab oma päringut õigesti tõlgendada.

Samuti on olemas lipp MSG_DONTROUTE, mis käsib andmeid edastada ilma marsruutimiseta, kuid Winsock seda ei toeta ja seetõttu seda siin ei käsitleta.

Datagrammi pesa saab kasutada ka saatmis- ja tagasivõtmisfunktsioone, kui see kutsub esmalt ühendust (vt " Klient: samm kolmandaks"), kuid sellel on ka oma isiklikud funktsioonid: " intsaada (SOCKET s, const char FAR * buf, int len,int lipud, const struct sockaddr FAR * to, int tolen)"Ja" intrecv from (SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen)".

Need on väga sarnased send- ja recv-ga, ainsaks erinevuseks on see, et sendto ja recvfrom nõuavad andmete vastuvõtmiseks või edastamiseks selgesõnalist sõlme aadressi näitamist. Recvfrom-kõne ei nõua saatva sõlme aadressi eelmääramist – funktsioon võtab vastu kõik määratud UDP-porti saabuvad paketid kõigilt IP-aadressidelt ja portidest. Vastupidi, saatja peaks vastama samale pordile, kust sõnum tuli. Kuna funktsioon recvfrom kirjutab kliendi IP-aadressi ja pordi numbri pärast temalt sõnumi saamist, ei pea programmeerija tegelikult tegema midagi muud, kui saatma sendto samale osutile sockaddr struktuurile, mis oli varem edastatud funktsioonile recvfrem. sai kliendilt sõnumi.

Üks detail veel– UDP transpordiprotokoll, millele datagrammi pesad toetuvad, ei garanteeri sõnumite edukat edastamist ja see ülesanne langeb arendaja enda õlule. Seda saab lahendada näiteks nii, et klient saadab kinnituse andmete eduka vastuvõtmise kohta. Tõsi, ka klient ei saa olla kindel, et kinnitus serverisse jõuab ja teel kuhugi ära ei eksi. Kinnituse saamise kinnitamine on mõttetu, sest see on rekursiivselt otsustamatu. Ebausaldusväärsetel kanalitel on parem üldse mitte kasutada datagrammi pesasid.

Muus osas on mõlemad funktsioonipaarid täiesti identsed ja töötavad samade lippudega – MSG_PEEK ja MSG_OOB.

Kõik neli funktsiooni tagastavad vea ilmnemisel SOCKET_ERROR (== -1).

Märge: UNIXis saab sokleid käsitleda täpselt nagu tavalisi faile, eelkõige saab neid kirjutada ja lugeda koos kirjutamis- ja lugemisfunktsioonidega. Windows 3.1 seda funktsiooni ei toetanud, seega tuli UNIX-i rakenduste Windowsi portimisel kõik kirjutamis- ja lugemiskõned asendada vastavalt sendi ja recv-ga. Windows 95-s, kus on installitud Windows 2.x, on see puudus parandatud – nüüd saab pesa deskriptorid edasi anda funktsioonidele ReadFil, WriteFile, DuplicateHandle jne.

Kuues samm, viimane– funktsioon " on mõeldud ühenduse sulgemiseks ja pistikupesa hävitamiseks intsulgemispesa (SOCKET s)", mis tagastab nullväärtuse, kui toiming on edukalt lõpule viidud.

Enne programmist väljumist peate kutsuma funktsiooni " intWSACpuhastus (tühine)" WINSOCKi teegi deinitialiseerimiseks ja selle rakenduse kasutatavate ressursside vabastamiseks. Tähelepanu : Protsessi lõpetamine ExitProcessi abil ei vabasta automaatselt sokliressursse!

Märkus. Ühenduse sulgemiseks on täiustatud tehnikad TCP-protokoll võimaldab teil valikuliselt sulgeda kummagi poole ühenduse, jättes teise poole aktiivseks. Näiteks saab klient serverile öelda, et ta ei saada talle enam andmeid ja sulgeb kliendi (serveri) ühenduse, kuid on valmis jätkama sellelt andmete vastuvõtmist seni, kuni server neid saadab, st soovib lahkuda. ühendus "klient (server") on avatud.

Selleks peate kutsuma funktsiooni "int Lülita välja(SOCKET s ,int how)", edastades argumendi kuidas ühe järgmistest väärtustest: SD_RECEIVE serveri (kliendi) kanali sulgemiseks, SD_SEND kliendi (serveri) kanali sulgemiseks ja lõpuks SD_BOTH mõlema kanali sulgemiseks.

Viimast varianti võrreldakse soodsalt closesocketiga ühenduse "pehme" sulgemise kaudu – kaugsõlmele saadetakse teade, et ta soovib ühenduse sulgeda, kuid seda soovi ei rakendata enne, kui see sõlm oma kinnituse tagastab. Nii ei pea te muretsema, et ühendus katkeb kõige ebasobivamal hetkel.

Tähelepanu: väljalülitamine ei vabasta vajadust sulgeda pistikupesa sulgemisfunktsiooniga!

Kõnepuu

Soklifunktsioonide omavaheliste seoste selgemaks demonstreerimiseks on allpool toodud kõnepuu, mis näitab funktsioonikutsete järjekorda, olenevalt sokli tüübist (voogesitus või datagramm) ja päringu töötlemise tüübist (klient või server).

kliendiserver

ühenda |-sendto TCP UDP

| |-recv from | |

|-saata kuula |

|-saata |-saata

|-recv |-recvform

Nimekiri 29 Socket funktsioonide kõnede jada erinevate toimingute jaoks

Aadress üks, aadress kaks

Aadressides on kõige suurem segadus ja natukene selgust ei teeks paha tuua. Esiteks on sockaddri struktuur määratletud järgmiselt:

u_short sa_family; // protokolli perekond

// (tavaliselt AF_INET)

char sa_data; // Hosti IP-aadress ja port

Nimekiri 30 Sockaddr-i struktuurimääratlus on aegunud

Nüüdseks aegunud Winsock 2.x asendas selle struktuuriga sockaddr_in, mis on määratletud järgmiselt:

struct sockaddr_in

lühike patu_perekond; // protokolli perekond

// (tavaliselt AF_INET)

u_short sin_port; // port

struct in_addr sin_addr; // IP-aadress

char sin_zero; // saba

Nimekiri 31 Struktuuri sockaddr_in kaasaegne määratlus

Üldiselt pole midagi muutunud (ja kas tasus aeda tarastada?), protokollide perekonna esindamiseks märgita lühikese täisarvu asendamine märgiga lühikese täisarvuga ei anna midagi. Kuid nüüd esitatakse sõlme aadress kolme välja kujul - sin_port (pordi numbrid), sin_addr (sõlme IP-aadressid) ja kaheksa nullbaidist "saba", mis jääb neljateistkümnest märgimassiivist sa_data . Milleks see mõeldud on? Fakt on see, et sockaddri struktuur ei ole spetsiaalselt Internetiga seotud ja võib töötada koos teiste võrkudega. Mõne võrgu aadressid nõuavad esituseks palju rohkem kui neli baiti, seega tuleb need varuga võtta!

In_addr struktuur on määratletud järgmiselt:

struct in_addr(

struct ( u_char s_b1,s_b2,s_b3,s_b4; ) S_un_b;

// IP-aadress

struct ( u_short s_w1,s_w2; ) S_un_w;

// IP-aadress

u_pikk S_addr; // IP-aadress

Nimekiri 32 Struktuuri in_addr määratlemine

Nagu näete, koosneb see ühest IP-aadressist, mis on kirjutatud kolmes "varjundis" - neljabaidine jada (S_un_b), kahebaidine sõnade paar (S_un_W) ja üks pikk täisarv (S_addr) - valige maitse järgi, kuid see on pole nii lihtne! Paljud Winsocki SDK-ga kaasas olevad programmid, tehnilised juhendid ja isegi demod viitavad s_addr struktuuri "salapärasele" liikmele, mida SDK-s otseselt ei kirjeldata! Näiteks siin on rida failist "Simples.h": "local.sin_addr. s_addr= (!liides)?INADDR_ANY:inet_addr(liides);"

Mis see on?! Vaadates faili "winsock2.h", leiate järgmise: "#define s_addr S_un.S_addr". Jah, aga see on s_addr ekvivalent, st. IP-aadress kirjutatud pika täisarvuna!

Praktikas saate võrdse eduga kasutada nii "aegunud" sockaddr kui ka "uue aja" sockaddr_in. Kuna aga teiste funktsioonide prototüübid ei ole muutunud, peate sockaddr_ini kasutamisel pidevalt tegema selgesõnalisi teisendusi, näiteks nii: " sockaddr_in siht_addr; ühenda (mysocket, (struct sockaddr* ) &dest_addr, sizeof(dest_addr)".

Märgijadana (nt "127.0.0.1") kirjutatud IP-aadressi teisendamiseks neljabaidiseks numbrijadaks kasutage " allkirjastamata pikkinet_addr (pidev char FAR * cp)". See võtab kursori märgistringile ja kui see õnnestub, teisendab selle neljabaidiseks IP-aadressiks või -1-ks, kui see pole võimalik. Funktsiooni tagastatud tulemuse saab määrata struktuuri sockaddr_in elemendile järgnevalt: " struktuur sockaddr_in dest_addr; dest_addr.sin_addr.S_addr=inet_addr("195.161.42.222");". Kasutades struktuuri sockaddr näeks see välja järgmine: " struktur sockaddr dest_addr; ((allkirjastamata int *)(&dest_addr.sa_data+2)) = inet_addr("195.161.42.222");"

Hosti domeeninime inet_addr edastamise katse nurjub. Sellise ja sellise domeeni IP-aadressi saate teada funktsiooni " struct hostent FAR *gethostbyname (const char FAR * nimi); Funktsioon pääseb juurde DNS-ile ja tagastab vastuse hostistruktuuris või nulli, kui DNS-server ei suutnud selle domeeni IP-aadressi määrata.

Hosti struktuur näeb välja selline:

char FAR * h_nimi; // sõlme ametlik nimi

char FAR * FAR * h_aliased; // alternatiivsed nimed

// sõlm (stringide massiiv)

lühike h_addrtype; // aadressi tüüp

lühike h_pikkus; // aadressi pikkus

// (tavaliselt AF_INET)

char FAR * KAUG * h_addr_list; // näpunäidete loend

//IP-aadressidele

// null on loendi lõpp

Nimekiri 33 Hostistruktuuri määratlemine

Nagu ka in_addr puhul, kasutavad paljud Winsocki SDK-ga kaasatud programmid ja näited ulatuslikult dokumenteerimata h_addr struktuurivälja. Näiteks siin on rida failist "simplec.c" "memcpy(&(server.sin_addr),hp->h_addr ,hp->h_length);" Lehte "winsock2.h" vaadates leiate, mida see tähendab: " #define h_addr h_addr_list".

Nüüd on see huvitav! Fakt on see, et mõned domeeninimed on seotud korraga mitme IP-aadressiga. Kui üks sõlm ebaõnnestub, võib klient proovida luua ühendust teisega või valida lihtsalt kõrgeima vahetuskursiga sõlme. Kuid ülaltoodud näites kasutab klient ainult loendi esimest IP-aadressi ja ignoreerib kõiki teisi! Muidugi pole see saatuslik, kuid siiski on parem, kui võtate oma programmides arvesse võimalust luua ühendus teiste IP-aadressidega, kui esimesega pole võimalik ühendust luua.

Funktsioon gethostbyname ootab sisendit ainult domeeninimed, kuid mitte digitaalsed IP-aadressid. Samas nõuavad “hea vormi” reeglid, et kliendile tuleb anda võimalus määrata nii domeeninimesid kui ka digitaalseid IP-aadresse.

Lahendus on järgmine - vaja on analüüsida kliendi saadetud stringi - kui tegemist on IP-aadressiga, siis edastada see funktsioonile inet_addr, muidu - gethostbyaddr, eeldades, et tegemist on domeeninimega. IP-aadresside eristamiseks domeeninimedest kasutavad paljud programmeerijad lihtsat nippi: kui rea esimene märk on number, on see IP-aadress, muidu on see domeeninimi. See trikk pole aga päris aus – domeeninimed võivad alata numbriga, näiteks “666.ru”, need võivad lõppeda ka numbriga, näiteks saavad alamdomeeni “666” liikmed pöörduda sõlme “666” poole. .ru" kui "666"". Naljakas on see, et (teoreetiliselt) võivad olla domeeninimed, mis on süntaktiliselt IP-aadressidest eristamatud! Seetõttu on selle artikli autori arvates kõige parem toimida nii: edastame kasutaja sisestatud stringi funktsioonile inet_addr, kui see tagastab vea, siis kutsume gethostbyaddr.

Pöördprobleemi lahendamiseks - domeeninime määramine IP-aadressi järgi, funktsioon " struct HOSTENT FAR *gethostbyaddr (const char FAR * addr, int len, int type)", mis on täpselt sama mis gethostbyname, välja arvatud see, et selle argument ei ole osuti nime sisaldavale stringile, vaid neljabaidilisele IP-aadressile. Veel kaks argumenti määravad selle pikkuse ja tüübi (vastavalt 4 ja AF_INET ).

Hostinime aadressi järgi määramine võib olla kasulik serveritele, mis soovivad oma kliente otse teada.

Võrguvormingus kirjutatud IP-aadressi teisendamiseks märgistringiks tuleb funktsioon " char FAR *inet _ ntoa (struct in_addr)", mis võtab sisendiks struktuuri in_addr ja tagastab stringile osuti, kui teisendamine õnnestub ja muul juhul null.

Võrgu baitide järjekord

Nimega torud sobivad protsessidevahelise suhtluse korraldamiseks nii samas süsteemis töötavate protsesside puhul kui ka lokaalse või globaalse võrgu kaudu omavahel ühendatud arvutites töötavate protsesside puhul. Neid võimalusi demonstreeriti peatükis 11 välja töötatud klient-server süsteemi abil, alustades programmist 11.2.

Kuid nii nimega torudel kui ka postkastidel (mille puhul kasutame lihtsuse huvides üldnimetust "nimega torud", kui nendevahelised erinevused ei ole olulised) on puuduseks, et need ei ole tööstusharu standard. See muudab peatükis 11 käsitletud programmide portimise mitte-Windowsi süsteemidesse keeruliseks, kuigi nimega torud on protokollist sõltumatud ja võivad töötada paljude tööstusharu standardprotokollidega, nagu TCP/IP.

See pakub võimalust suhelda teiste süsteemidega Windowsi tugi pesad Windows Sockets – Berkeley Socketsi ühilduv ja peaaegu täpne analoog, mis de facto täidab tööstusstandardi rolli. Selles peatükis API kasutamine Windows Sockets (või "Winsock") on illustreeritud, kasutades peatükis 11 toodud modifitseeritud klient/server süsteemi. Saadud süsteem on võimeline töötama ülemaailmsed võrgud, kasutades TCP/IP-protokolli, mis näiteks võimaldab serveril vastu võtta päringuid UNIX-i klientidelt või muudelt mitte-Windowsi süsteemidelt.

Berkeley Socketsi liidesega tuttavad lugejad võivad edasi minna näidete juurde, mis mitte ainult ei kasuta pistikupesasid, vaid tutvustavad ka uusi serverivõimalusi ja demonstreerivad täiendavaid tehnikaid lõimekindlat tuge pakkuvate teekide kasutamiseks.

Võimendades standarditel põhinevat koostalitlusvõimet heterogeensete süsteemide vahel, annab Winsocki liides programmeerijatele juurdepääsu kõrgetasemelistele protokollidele ja rakendustele, nagu ftp, http, RPC ja COM, mis koos pakuvad rikkalikku kõrgetasemeliste mudelite komplekti, mis toetavad protsessidevahelist võrku. süsteemid koos erinev arhitektuur.

See peatükk kasutab seda klient-serveri süsteemi mehhanismina Winsocki liidese demonstreerimiseks ning serveri muutmisel lisatakse uusi huvitavaid funktsioone. Eelkõige kasutame esimest korda DLL-i sisenemispunktid(5. peatükk) ja protsessisisesed DLL-serverid.(Need uued funktsioonid oleksid võinud sisalduda programmi algses versioonis peatükis 11, kuid see oleks viinud teie tähelepanu aluseks oleva süsteemiarhitektuuri arendamiselt.) Lõpuks näitavad täiendavad näited teile, kuidas luua turvalist, taassisenevat ja mitme lõimega raamatukogud.

Kuna Winsocki liides peab vastama tööstusstandarditele, on selle nimetamisviisid ja programmeerimisstiilid mõnevõrra erinevad nendest, mida kohtasime varem kirjeldatud Windowsi funktsioonide puhul. Rangelt võttes ei ole Winsocki API osa Win32/64-st. Lisaks pakub Winsock täiendavaid mittestandardseid funktsioone; Neid funktsioone kasutatakse ainult siis, kui see on hädavajalik. Muud Winsocki pakutavad eelised hõlmavad saadud programmide paremat teisaldatavust teistesse süsteemidesse.

Windowsi pistikupesad

Winsocki API loodi Berkley Sockets API laiendusena Windowsi keskkonna jaoks ja seetõttu toetatakse seda kõigis Windowsi süsteemides. Winsocki eeliste hulka kuuluvad järgmised:

Olemasolev kood, mis on kirjutatud Berkeley Socketsi API jaoks, porditakse otse.

Windowsi süsteeme saab hõlpsasti integreerida võrkudesse, mis kasutavad nii TCP/IP-protokolli IPv4 versiooni kui ka üha laiemalt levivat IPv6 versiooni. Lisaks kõigele muule võimaldab IPv6 pikemaid IP-aadresse, purustades IPv4 olemasoleva 4-baidise aadressibarjääri.

Pistikupesasid saab kasutada koos Windowsi kattuva I/O-ga (peatükk 14), mis muuhulgas võimaldab serveritel skaleerida, kui aktiivsete klientide arv kasvab.

Funktsioonide ReadFile ja WriteFile kasutamisel võib sokleid pidada failikäepidemena (tüüpi HANDLE) ning teatud piirangutega ka muude funktsioonide kasutamisel, nagu UNIXis kasutatakse sokleid failikäepidemena. See funktsioon on kasulik, kui peate kasutama asünkroonseid I/O ja I/O lõpetamise porte.

Samuti on täiendavaid, kaasaskantavaid laiendusi.

Winsocki lähtestamine

Winsocki API-d toetab DLL-teek (WS2_32.DLL), millele juurdepääsu saamiseks peate programmiga ühendama WS_232.LIB-teegi. See DLL tuleb lähtestada, kasutades mittestandardset, Winsocki-spetsiifilist funktsiooni WSAStartup, mis peab olema esimene Winsocki funktsioon, mille programm kutsub. Kui te ei vaja enam Winsocki funktsioone, peaksite helistama funktsioonile WSACleanup. Märge. WSA eesliide tähistab "Windows Sockets asynchronous...". Me ei kasuta siin Winsocki asünkroonse režiimi võimalusi, kuna kui tekib vajadus asünkroonsete toimingute tegemiseks, saame kasutada ja kasutame lõime.

Kuigi WSAStartup ja WSACleanup funktsioone tuleb kutsuda, on täiesti võimalik, et need on ainsad mittestandardsed funktsioonid, millega peate tegelema. Levinud tava on kasutada #ifdef eelprotsessori käske, et kontrollida sümboolse konstandi _WIN32 väärtust (tavaliselt määrab selle kompileerimise ajal Visual C++), mistõttu WSA funktsioone kutsutakse välja ainult siis, kui kasutate Windowsi). Muidugi eeldab see lähenemine, et ülejäänud kood on platvormist sõltumatu.

int WSAStartup(WORD wVersionRequired, LPWSADATA ipWSAData);
Valikud

wVersionRequired – määrab DLL-i peamise versiooninumbri, mida vajate ja saate kasutada. Tavaliselt piisab versioonist 1.1, et tagada mis tahes vajalik koostalitlusvõime teiste süsteemidega. Siiski on Winsock 2.0 saadaval kõigis Windowsi süsteemides, sealhulgas Windows 9x, ja seda kasutatakse allolevates näidetes. Versioon 1.1 loetakse aegunuks ja järk-järgult langeb kasutusest välja.

Funktsioon tagastab nullist erineva, kui selle DLL-i soovitud versiooni ei toetata.

Parameetri wVersionRequired madal bait näitab põhiversiooni numbrit ja kõrgem bait lisaversiooni. Tavaliselt kasutatakse makrot MAKEWORD; seega avaldis MAKEWORD(2,0) tähistab versiooni 2.0.

ipWSAData on kursor WSADATA struktuurile, mis tagastab DLL-i konfiguratsiooniteabe, sealhulgas peamise saadaoleva versiooni numbri. Selle sisu tõlgendamise kohta saate lugeda Visual Studio võrguabist.

Täpsema veateabe saamiseks saab kasutada funktsiooni WSAGetLastError, kuid selleks sobib ka GetLastError funktsioon, nagu ka 2. peatükis välja töötatud ReportError funktsioon.

Kui programm on töötamise lõpetanud või kui pole enam vaja pistikupesasid kasutada, peaksite kutsuma funktsiooni WSACleanup, et pistikupesasid hooldav WS_32.DLL teek saaks vabastada protsessile eraldatud ressursid.

Pistikupesa loomine

Kui Winsocki DLL on lähtestatud, saate kasutada standardseid (Berkeley Sockets) funktsioone, et luua pesasid ja ühendusi, mis võimaldavad serveritel suhelda klientidega või suhelda võrgus olevate kaaslaste vahel.

Winsocki andmetüüp SOCKET sarnaneb andmetüübiga Windows HANDLE ja seda saab kasutada isegi koos funktsiooniga ReadFile ja muude Windowsi funktsioonidega, mis nõuavad HANDLE-käepidemeid. Pistikupesa loomiseks (või avamiseks) kasutage pesa.

SOCKET socket(int af, int tüüp, int protokoll);
Valikud

Andmetüüp SOCKET on tegelikult määratletud kui int-andmetüüp, nii et UNIX-kood jääb teisaldatavaks, ilma et oleks vaja tüübikutset Windowsi andmed.

af - tähistab aadresside perekonda või protokolli; IP-protokolli (TCP/IP-protokolli komponent, mis vastutab Interneti-protokolli eest) määramiseks peaksite kasutama väärtust PF_INET (või AF_INET, millel on sama arvväärtus, kuid mida kasutatakse tavaliselt sidumisfunktsiooni kutsumisel). .

tüüp – näitab side tüüpi: ühendusele orienteeritud side ehk voogedastus (SOCK_STREAM) ja datagrammi side (SOCK_DGRAM), mis on mõneti võrreldav vastavalt nimega torude ja postkastidega.

protokoll – on üleliigne, kui af on seatud väärtusele AF_INET; kasuta väärtust 0.

Kui see ebaõnnestub, tagastab soklifunktsioon INVALID_SOCKET.

Winsocki saab kasutada ka muude protokollidega peale TCP/IP, määrates protokolli parameetri jaoks erinevad väärtused; kasutame ainult TCP/IP-protokolli.

Nagu kõigi teiste standardfunktsioonide puhul, ei tohi soklifunktsiooni nimi sisaldada suurtähti. See on Windowsi tavadest kõrvalekaldumine ja selle põhjuseks on vajadus järgida tööstusharu standardeid.

Serveri pesa funktsioonid

Järgnevas arutelus all server all mõistetakse protsessi, mis võtab vastu taotlusi ühenduse loomiseks antud pordi kaudu. Kuigi pistikupesasid, nagu ka nimelisi torusid, saab kasutada võrgus olevate kaaslaste vahel ühenduste loomiseks, on selline eristamine kaaslaste vahel mugav ja peegeldab erinevusi kahe süsteemi vahel üksteisega ühenduse loomiseks.

Kui pole teisiti märgitud, on meie näidetes pistikupesa tüüp alati SOCK_STREAM. SOCK_DGRAM-i pesasid käsitletakse selles peatükis hiljem.

Pistikupesa sidumine

Järgmine samm on pesa sidumine selle aadressiga ja lõpp-punkt(otspunkt) (sidekanali suund rakendusest teenusesse). Kutse pistikupesale, millele järgneb üleskutse siduda, on sama, mis nimega toru loomine. Siiski pole nimetusi, mille abil saaks pistikupesasid eristada sellest arvutist. Selle asemel on teenuse lõpp-punkt pordi number(pordi number). Igal serveril võib olla mitu lõpp-punkti. Sidumisfunktsiooni prototüüp on toodud allpool.

int bind(SOCKET s, const struct sockaddr *saddr, int namelen);
Valikud

s on pesafunktsiooni poolt tagastatud sidumata pesa.

saddr – Täidetakse enne kõnet ja täpsustatakse protokoll ja protokollispetsiifiline teave, nagu allpool kirjeldatud. Muuhulgas sisaldab see struktuur pordi numbrit.

namelen - määrake väärtusele sizeof(sockaddr).

Kui see õnnestub, tagastab funktsioon väärtuse 0, vastasel juhul SOCKET_ERROR. Sockaddri struktuur on määratletud järgmiselt:

typedef struct sockaddr SOCKADDR, *PSOCKADDR;

Selle struktuuri esimene liige sa_family tähistab protokolli. Teine liige sa_data on protokollist sõltuv. Struktuuri sa_data Interneti-versioon on sockaddr_in struktuur:

lühike patu_perekond; /* AF_INET */
struct in_addr sin_addr; /* 4-baidine IP-aadress */
typedef struct sockaddr_in SOCKADDR_IN, *PSOCKADDR IN;

Pange tähele tüübi kasutamist andmed lühikesed pordi numbri täisarv. Lisaks tuleb pordi number ja muu teave salvestada sobivas baitide järjekorras, kusjuures kõige olulisem bait on paigutatud big-endiani positsiooni, et tagada binaarne ühilduvus teiste süsteemidega. Struktuur sin_addr sisaldab alamstruktuuri s_addr, mis on täidetud tuttava 4-baidise IP-aadressiga, näiteks 127.0.0.1, mis näitab süsteemi, mille ühenduse taotlus tuleks vastu võtta. Üldjuhul rahuldatakse mis tahes süsteemi päringud, seega tuleks kasutada INADDR_ANY, kuigi see sümboolne parameeter tuleb teisendada õigesse vormingusse, nagu on näidatud allolevas koodilõigul.

Funktsiooni inet_addr saab kasutada IP-aadressi sisaldava tekstistringi teisendamiseks nõutavasse vormingusse, seega initsialiseeritakse muutuja sockaddr_in liige sin_addr.s_addr järgmiselt:

sa.sin_addr.s_addr = inet_addr("192 .13.12.1");

Seotud pesa, mille jaoks on määratletud protokoll, pordi number ja IP-aadress, nimetatakse mõnikord ka kui nimega pistikupesa(nimega pistikupesa).

Seotud pesa lülitamine kuulamisolekusse

Kuulamisfunktsioon muudab serveri kliendiga ühenduse loomiseks kättesaadavaks. Nimega torude puhul sarnast funktsiooni pole.

int kuula(SOCKET s, int nQueueSize);

Parameeter nQueueSize määrab ühenduse taotluste arvu, mille kavatsete soklisse järjekorda panna. Winsock 2.0 puhul ei ole selle parameetri väärtusel ülempiiri, kuid versioonis 1.1 piirab seda SOMAXCONi limiit (5).

Kliendi ühenduse taotluste vastuvõtmine

Lõpuks saab server oodata ühenduse loomist kliendiga, kasutades aktsepteerimisfunktsiooni, mis tagastab uue ühendatud pesa, mida kasutatakse I/O toimingute jaoks. Pange tähele, et algset pesa, mis on nüüd kuulamisolekus, kasutatakse ainult aktsepteerimisfunktsiooni parameetrina ja see ei ole otseselt seotud sisend- ja väljundtoimingutega.

Aktsepteerimisfunktsioon blokeerib seni, kuni kliendilt saabub ühenduse taotlus, misjärel tagastab see uue I/O-pesa. Kuigi see raamat ei hõlma, on võimalik luua mitteblokeeruvaid pesasid ja server (programm 12.2) kasutab päringu vastuvõtmiseks eraldi lõime, mis võimaldab teil luua ka mitteblokeerivaid servereid.

SOCKET aktsepteeri (SOCKET s, LPSOCKADDR lpAddr, LPINT lpAddrLen);
Valikud

s - kuulamispesa. Pistikupesa kuulamisolekusse panemiseks peate esmalt kutsuma pesa, sidumise ja kuulamise funktsioonid.

lpAddr on kursor sockaddr_in struktuurile, mis annab kliendisüsteemi aadressi.

lpAddrLen on osuti muutujale, mis sisaldab tagastatud sockaddr_in struktuuri suurust. Enne aktsepteerimist tuleb see muutuja lähtestada väärtusega sizeof(struct sockaddr_in).

Pistikupesade lahtiühendamine ja sulgemine

Pistikupesade väljalülitamiseks kasutage funktsiooni shutdown(s, how). Argumendil kuidas võib olla üks kahest väärtusest: 1, mis näitab, et ühenduse saab sulgeda ainult sõnumite saatmiseks, ja 2, mis näitab, et ühenduse saab sulgeda nii sõnumite saatmiseks kui ka vastuvõtmiseks. Sulgemisfunktsioon ei vabasta pesaga seotud ressursse, kuid tagab kõigi andmete saatmise ja vastuvõtmise enne pesa sulgemist. Kui aga väljalülitusfunktsioon on välja kutsutud, ei tohiks rakendus enam pistikupesa kasutada.

Kui olete pistikupesa kasutamise lõpetanud, peaksite selle sulgema, kutsudes välja funktsiooni closesocket(SOCKET s). Server sulgeb esmalt aktsepteerimisfunktsiooni loodud pesa, mitte socket funktsiooniga loodud kuulamispesa. Server peaks kuulamispesa sulgema ainult siis, kui see lülitub välja või lõpetab kliendiühenduse taotluste vastuvõtmise. Isegi kui käsitlete soklit HANDLE-käepidemena ja kasutate funktsioone ReadFile ja WriteFile, ei saa te soklit hävitada, kutsudes ainult funktsiooni CloseHandle; Selleks peaksite kasutama closesocket funktsiooni.

Näide: Kliendiühenduse taotluste ettevalmistamine ja vastuvõtmine

Järgmine koodilõik näitab, kuidas luua soklit ja aktsepteerida kliendiühenduse taotlusi.

See näide kasutab kahte standardfunktsioonid: htons ("host to network short") ja htonl ("host to network long"), mis teisendavad täisarvud IP-protokolli nõutavasse little-endiani vormi.

Serveri pordi number võib olla mis tahes number lühikeste täisarvude jaoks lubatud vahemikus, kuid tavaliselt kasutatakse kasutaja määratud teenuste puhul numbreid vahemikus 1025–5000. Madalamad pordinumbrid on reserveeritud tuntud teenustele, nagu telnet või ftp, samas kui suuremad pordinumbrid on reserveeritud muudele standardteenustele.

struct sockaddr_in SrvSAddr; /* Serveri aadressi struktuur. */
struct sockaddr_in ConnectAddr;
AddrLen = suurus(ConnectAddr);
sockio = aktsepteeri(SrvSock, (struct sockaddr *) &ConnectAddr, &AddrLen);
... Päringute vastuvõtmine ja vastuste saatmine ...

Socket kliendi funktsioonid

Kliendijaam, mis soovib serveriga ühendust luua, peab looma ka sokli, kutsudes välja soklifunktsiooni. Järgmise sammuna loob server ühenduse ning lisaks tuleb määrata pordi number, hosti aadress ja muu info. On ainult üks lisafunktsioon - ühendage.

Kliendiühenduse loomine serveriga

Kui kuulamisrežiimis on pesaga server, saab klient sellega ühenduse luua ühenduse funktsiooni kasutades.

int connect(SOCKET s, LPSOCKADDR lpName, int nNameLen);
Valikud

s on pesa, mis on loodud pesa funktsiooni abil.

lpName on kursor sockaddr_in struktuurile, mis on initsialiseeritud pordi numbri ja süsteemi IP-aadressi väärtustega, mille pistikupesa on seotud määratud pordiga, mis on kuulamisolekus.

Initsialiseerige nNameLen väärtusega sizeof (struct sockaddr_in).

Tagastusväärtus 0 näitab funktsiooni õnnestumist, samas kui tagastatav väärtus SOCKET_ERROR näitab viga, mis võib muu hulgas olla tingitud sellest, et määratud aadressil pole kuulamispesa.

Socket s ei pea olema pordiga seotud enne ühenduse funktsiooni kutsumist, kuigi see võib nii olla. Vajadusel eraldab süsteem pordi ja määrab protokolli.

Näide: kliendi ühendamine serveriga

Allpool näidatud koodilõik ühendab kliendi serveriga. Selleks on vaja ainult kahte funktsioonikutset, kuid aadressi struktuur tuleb enne ühenduse funktsiooni kutsumist initsialiseerida. Siin ei kontrollita võimalikke vigu, kuid see peaks olema reaalsetes programmides. Näide eeldab, et IP-aadress ( tekstistring nagu "192.76.33.4") on määratud argv käsurea argumendis.

ClientSAddr.sin_addr.s_addr = inet_addr(argv);
ConVal = connect(ClientSock, (struct sockaddr *)&ClientSAddr, sizeof(ClientSAddr));

Andmete saatmine ja vastuvõtmine

Socketid kasutavad programmid vahetavad andmeid kasutades send ja recv funktsioone, mille prototüübid on peaaegu samad (saatmisfunktsiooni puhvri osutile eelneb const modifikaator). Allpool on vaid saatmisfunktsiooni prototüüp.

int send(SOCKET s, const char * lpBuffer, int nBufferLen, int nFlags);

Tagastusväärtus on tegelikult edastatud baitide arv. SOCKET_ERROR näitab viga.

nLippude abil saab näidata sõnumite kiireloomulisust (nt hädaabisõnumid) ja MSG_PEEK väärtus võimaldab vaadata vastuvõetud andmeid ilma neid lugemata.

Kõige olulisem asi, mida peate meeles pidama, on saatmise ja vastuvõtu funktsioonid ei ole aatomilised(aatom) ja seetõttu ei ole mingit garantiid, et taotletud andmed tegelikult saadetakse või vastu võetakse. "Lühisõnumite" ("short sents") edastamine on äärmiselt haruldane, kuigi see on võimalik, mis kehtib ka "lühisõnumite" ("short Receives") vastuvõtmise kohta. Sõnumi mõiste selles mõttes, nagu see oli nimega torude puhul, siin puudub ja seetõttu tuleb kontrollida tagastusväärtust ja andmeid uuesti saata või vastu võtta, kuni kõik need on edastatud.

Funktsioone ReadFile ja WriteFile saab kasutada ka soklitega, ainult sellisel juhul tuleb funktsiooni välja kutsumisel socket heita HANDLE tüüpi.

Nimetatud torude ja pistikupesade võrdlus

Peatükis 11 kirjeldatud nimega torud on väga sarnased pistikupesadega, kuid nende kasutusviisis on olulisi erinevusi.

Nimega torud võivad olla sõnumipõhised, mis lihtsustab oluliselt programme.

Nimega torud nõuavad funktsioonide ReadFile ja WriteFile kasutamist, samas kui pistikupesad saavad kasutada ka saatmise ja taastamise funktsioone.

Erinevalt nimega torudest on pistikupesad nii paindlikud, et annavad kasutajatele võimaluse valida pesaga kasutatava protokolli, näiteks TCP või UDP. Lisaks on kasutajal võimalus valida protokoll pakutava teenuse iseloomu või muude tegurite alusel.

Pistikupesad põhinevad tööstusstandardil, mis muudab need ühilduvaks mitte-Windowsi süsteemidega.

Samuti on erinevusi serveri ja kliendi programmeerimismudelites.

Nimega toru- ja pesaserverite võrdlus

Ühenduse loomine mitme kliendiga pesade abil nõuab korduvaid kõnesid aktsepteerimisfunktsioonile. Iga kõne tagastab järgmise ühendatud pistikupesa. Nimetatud torudega võrreldes on järgmised erinevused:

Nimega torud nõuavad, et iga nimega toru eksemplar ja HANDLE-käepide luuakse funktsiooni CreateNamedPipe abil, samas kui sokli eksemplarid luuakse aktsepteerimisfunktsiooni abil.

Lubatud kliendisoklite arv on piiramatu (kuulamisfunktsioon piirab ainult järjekorda asetatavate klientide arvu), samas kui nimega torujuhtumite arv võib olla piiratud, sõltuvalt sellest, mis määrati CreateNamedPipe'i esmakordsel kutsumisel.

Funktsiooniga TransactNamedPipe sarnaseid pistikupesa abifunktsioone pole.

Nimega torudel ei ole selgelt määratletud pordinumbreid ja neid eristatakse nimede järgi.

Nimega toruserveri puhul nõuab kasutatava HANDLE tüüpi käepideme hankimine kahe funktsiooni (CreateNamedPipe ja ConnectNamedPipe) väljakutsumist, socket serveri puhul aga nelja funktsiooni (socket, bind, kuula ja aktsepteeri) kutsumist.

Nimetatud torude ja pistikupesade klientide võrdlus

Nimega torude puhul peate järjestikku kutsuma funktsioone WaitNamedPipe ja CreateFile. Kui kasutatakse pesasid, on kõnede järjekord vastupidine, kuna pesafunktsiooni võib käsitleda kui pesa loomist ja ühendamise funktsiooni blokeerimist.

Täiendav erinevus seisneb selles, et ühenduse funktsioon on socket-kliendi funktsioon, samas kui funktsiooni ConnectNamedPipe kasutab nimega toruserver.

Näide: sõnumi vastuvõtmise funktsioon pistikupesa korral

Sageli on mugav sõnumeid saata ja vastu võtta üksikute plokkidena. Nagu nägime 11. peatükis, võimaldavad kanalid seda teha. Pistikupesade puhul on aga nõutav kirja suurust sisaldav päis, millele järgneb teade ise. Funktsioon ReceiveMessage, mida näidetes kasutatakse, on mõeldud selliste sõnumite vastuvõtmiseks. Sama võib öelda ka funktsiooni SendMessage kohta, mis on mõeldud sõnumite saatmiseks.

Pange tähele, et sõnum võetakse vastu kahes osas: päis ja sisu. Allpool eeldame, et kasutajatüüp MESSAGE vastab 4-baidisele päisele. Kuid isegi 4-baidine päis nõuab korduvaid väljakutseid funktsioonile recv, et tagada selle täielik lugemine, kuna funktsioon recv ei ole aatom.

Win64 spetsiifiline märkus

Sõnumi suuruse salvestamiseks kasutatavate muutujate tüüp on fikseeritud täpsusega andmetüüp LONG32, mis on täiesti piisav sõnumites sisalduvate suurusparameetrite väärtuste mahutamiseks suhtlemisel muude süsteemidega kui Windows ja mis sobib programmi võimalik hilisem uuesti kompileerimine selle kasutamiseks Win64 platvormil (vt peatükk 16).

DWORD-vastuvõtusõnum (SÕNUM *pMsg, SOCKET sd) (
/* Sõnum koosneb 4-baidisest sõnumi suuruse väljast, millele järgneb tegelik sisu. */
/* Lugege sõnumit. */
/* Kõigepealt loetakse päis, seejärel sisu. */
nRemainRecv = 4; /* Päisevälja suurus. */
ppuhver = (LPBYTE)pMsg; /* recv ei pruugi kõiki nõutud baite üle kanda. */
while (nRemainRecv > 0 && !Katkesta ühendus) (
/* Lugege sõnumi sisu. */
while (nRemainRecv > 0 && !Katkesta ühendus) (
nXfer = recv(sd, pBuffer, nRemainRecv, 0);

Näide: soklipõhine klient

Programm 12.1 on nimega torude jaoks kasutatud clientNP klientprogrammi (programm 11.2) ümbertöötlemine. Programmi teisendamine toimub väga arusaadaval viisil ja nõuab vaid mõningaid selgitusi.

Selle asemel, et serverit postkastide abil avastada, sisestab kasutaja käsureale serveri IP-aadressi. Kui IP-aadressi pole määratud, kasutatakse vaikeaadressi 127.0.0.1, mis vastab kohalikule süsteemile.

Sõnumite saatmiseks ja vastuvõtmiseks kasutatakse selliseid funktsioone nagu ReceiveMessage, mida siin ei kuvata.

Pordi number SERVER_PORT on määratletud päisefailis ClntSrvr.h.

Kuigi kood on kirjutatud all jooksma Windowsi juhtimine, ainus Windowsi sõltuvus hõlmab funktsioonikutsete kasutamist, mille eesliite on WSA.

Programm 12.1. clientSK: pistikupesapõhine klient
/* Peatükk 12. clientSK.с */
/* Ühe lõimega käsurea klient. */
/* WINDOWS SOCKETSI PÕHINE VERSION. */
/* Loeb serveriprotsessile saatmiseks käskude jada*/
/* pistikupesa ühenduse kaudu. Ootab vastust ja kuvab selle. */

#define _NOEXCLUSIONS /* Nõutav pesade määratluste lubamiseks. */
#include "ClntSrvr.h" /* Määrab päringu ja vastuse kirje struktuurid. */

/* Sõnumifunktsioonid päringute ja vastuste teenindamiseks. */
/* Lisaks kuvab ReceiveResponseMessage vastuvõetud sõnumid. */
staatiline DWORD SendRequestMessage(REQUEST *, SOCKET);
staatiline DWORD ReceiveResponseMessage(RESPONSE *, SOCKET);
struct sockaddr_in ClientSAddr; /* Kliendi pistikupesa aadress. */
int _tmain(DWORD argc, LPTSTR argv) (
SOCKET ClientSock = INVALID_SOCKET;
REQUEST Taotlus; /* Vaata ClntSrvr.h. */
RESPONSE Response; /* Vaata ClntSrvr.h. */
TCHAR PromptMsg = _T("\nSisestage käsk> ");
TCHAR QuitMsg = _T("$Quit");
/* Taotlus: kliendi lõpetamine. */
TCHAR ShutMsg = _T("$ShutDownServer"); /* Peatage kõik lõimed. */
CHAR DefaultIPAddr = "127.0.0.1"; /* Kohalik süsteem. */
/* Ühendage serveriga. */
/* Jälgi standardprotseduur socket/connect funktsioonide jada kutsumine kliendi poolt. */
ClientSock = pesa(AF_INET, SOCK_STREAM, 0);
memset(&ClientSAddr, 0, sizeof(ClientSAddr));
ClientSAddr.sin_family = AF_INET;
if (argc >= 2) ClientSAddr.sin_addr.s_addr = inet_addr(argv);
else ClientSAddr.sin_addr.s_addr = inet_addr(VaikimisiIPAddr);
ClientSAddr.sin_port = htons(SERVER_PORT);
/* Pordi number on määratletud kui 1070. */
connect(ClientSock, (struct sockaddr *)&ClientSAddr, sizeof(ClientSAddr));
/* Põhisilmus käsuviiba kuvamiseks, päringu saatmiseks ja vastuse saamiseks. */
_tprintf(_T("%s"), PromptMsg);
/* Sisend on metamärgivormingus, kuid serverile suunatav käsk peab olema määratud ASCII-vormingus. */
_fgetts(Req, MAX_RQRS_LEN-1, stdin);
jaoks (j = 0; j<= _tcslen(Req) Request.Record[j] = Req[j];
/* Vabane sümbolist uus rida rea lõpus. */
Request.Record = "\0";
if (strcmp(Request.Record, QuitMsg) == 0 || strcmp(Request.Record, ShutMsg) == 0) Quit = TRUE;
SendRequestMessage(&Request, ClientSock);
ReceiveResponseMessage(&Response, ClientSock);
shutdown (ClientSock, 2); /* Keelake sõnumite saatmine ja vastuvõtmine. */
_tprintf(_T("\n****Välju klientprogrammist\n"));

Näide: Advanced Socket Server

ServerSK programm (programm 12.2) sarnaneb serverNP programmiga (programm 11.3), olles selle muudetud ja täiustatud versioon.

Programmi täiustatud versioonis luuakse serveri lõimed nõudlusel(nõudmisel), mitte fikseeritud suurusega niidibasseinina. Iga kord, kui server võtab vastu kliendi ühendustaotluse, luuakse serveritöötaja lõim ja kui klient lõpetab, lõpeb lõime täitmine.

Server loob eraldi voogu vastu võtma(accept thread), mis võimaldab põhilõimel küsida globaalset sulgemislippu, samal ajal kui vastuvõtukõne jääb blokeerituks. Kuigi pistikupesasid võib määratleda kui mitteblokeerivaid, pakuvad vood mugavat üldotstarbelist lahendust. Tuleb märkida, et suur osa Winsocki täiustatud funktsioonidest on loodud toetama asünkroonseid toiminguid, samas kui Windowsi lõimed võimaldavad kasutada ära sünkroonse pesarežiimi lihtsamat standardipõhist funktsionaalsust.

Programmi mõningase keerukuse tõttu on lõime haldust täiustatud, mis võimaldas pakkuda tuge iga lõime olekute jaoks.

See server toetab ka protsessisisesed serverid(protsessisisesed serverid), mis saavutatakse DLL-i laadimisega initsialiseerimise ajal. DLL-i nimi määratakse käsureal ja serveri lõim proovib esmalt määrata DLL-i sisenemispunkti. Kui see õnnestub, kutsub serveri lõim DLL-i sisenemispunkti; vastasel juhul loob server protsessi samamoodi nagu serverNP programmis. DLL-i näide on toodud programmis 12.3. Kuna DLL-i erandite loomine tapab kogu serveriprotsessi, on DLL-i funktsioonikõne kaitstud lihtsa erandikäsitlejaga.

Soovi korral saate serverNP programmi kaasata protsessisisesed serverid. Protsessiseste serverite suurim eelis on see, et nad ei vaja konteksti lülitumist teistele protsessidele, mis võib kaasa tuua märgatava jõudluse paranemise.

Kuna serveri kood kasutab Windowsi spetsiifilisi funktsioone, eriti lõime juhtimisvõimalusi ja mõnda muud, osutub see erinevalt kliendikoodist Windowsiga seotud olevat.

Programm 12.2. serverSK: pistikupesapõhine server protsessisiseste serveritega
/* Peatükk 12. Klient-server süsteem. SERVER PROGRAMM. SOCKET PÕHINE VERSION. */
/* Täidab päringus määratud käsu ja tagastab vastuse. */
/* Kui jagatud teegi sisenemispunkt on leitav, siis käsud */
/* täidetakse protsessi sees, muidu väljaspool protsessi. */
/* VALIKULINE FUNKTSIOON: argv võib sisaldada teegi nime */
/* DLL, mis toetab protsessisiseseid servereid. */

#include "ClntSrvr.h" /* Määrab päringu ja vastuse kirjete struktuuri. */

/* Serveri sokli aadressi struktuur. */
struct sockaddr_in ConnectSAddr; /* Ühendatud pistikupesa. */
WSADATA WSStartData; /* Sokliteegi andmestruktuur. */

typedef struct SERVER_ARG_TAG ( /* Serveri lõime argumendid. */
/* Selgitused sisalduvad põhilõime kommentaarides. */
HINSTANCE dlhandle; /* Käepide jagatud teegi jaoks. */

lenduv staatiline ShutFlag = VÄÄR;
staatiline SOCKET SrvSock, ConnectSock;
int _tmain(DWORD argc, LPCTSTR argv) (
/* Server kuulab ja ühendatud pistikupesad. */
SERVER_ARG srv_arg;
/* WSA teegi lähtestamine; versioon 2.0 on määratud, kuid töötab ka versioon 1.1. */
WSAStartup(MAKEWORD(2, 0), &WSSStartData);
/* Avab dünaamilise käsuteegi, kui selle nimi on käsureal määratud. */
if (argc > 1) hDll = Laadi raamatukogu(argv);
/* Käivitage lõime arg massiiv. */
jaoks (ith = 0; ith< MAXCLIENTS; ith++) {
srv_arg.dllhandle = hDll;
/* Järgige kliendi funktsioonide järjestuse socket/bind/len/accept kutsumiseks standardprotseduuri. */
SrvSock = pesa(AF_INET, SOCK_STREAM, 0);
SrvSAddr.sin_family = AF_INET;
SrvSAddr.sin_addr.s_addr = htonl(INADDR_ANY);
SrvSAddr.sin_port = htons(SERVER_PORT);
bind(SrvSock, (struct sockaddr *)&SrvSAddr, SrvSAddr suurus);
kuula(SrvSock, MAX_CLIENTS);

/* Põhilõimest saab kuulamis-/ühendus-/juhtlõng.*/
/* Leia serveri lõime arg-massiivist tühi lahter. */
/* olekuparameeter: 0 – lahter on vaba; 1 – vool seiskunud; 2 - niit töötab; 3 – kogu süsteem on peatatud. */
jaoks (ith = 0; ith< MAX_CLIENTS && !ShutFlag;) {
if (srv_arg.status==1 || srv_arg.status==3) ( /* Lõime täitmine on lõpetatud või tavalisel viisil, või peatamise taotlusel. */
WaitForSingleObject(srv_arg.srv_thd LÕPETTU);
CloseHandle(srv_arg.srv_tnd);
if (srv_arg.status == 3) ShutFlag = TRUE;
else srv_arg.status = 0;
/* Vabastage selle lõime lahter. */
if (srv_arg.status == 0 || ShutFlag) break;
ith = (ith + 1) % MAXCLIENTS;
/* Katkestage küsitlustsükkel. */
/* Teise võimalusena kasutage sündmust, et genereerida signaal, mis näitab, et lahter on vabastatud. */
/* Oodake selle pistikupesaga ühenduse loomise katset. */
/* Eraldi lõime küsitluseks ShutFlag. */
hAcceptTh = (HANDLE)_beginthreadex(NULL, 0, AcceptTh, &srv_arg, 0, &ThId);
tstatus = WaitForSingleObject(hAcceptTh, CS_TIMEOUT);
if (tstatus == WAIT_OBJECT_0) break; /* Ühendus loodud. */
hAcceptTh = NULL; /* Valmistuge järgmiseks ühenduseks. */
_tprintf(_T("Serveri peatamine. Ootan kõigi serveri lõimede lõpetamist\n"));
/* Lõpetage vastuvõttev lõim, kui see veel töötab. */
/* Veel detailne info kasutatud lõpetamisloogika kohta */
/* teosed on loetletud raamatu veebisaidil. */
if (hDll != NULL) FreeLibrary(hDll);
if (hAcceptTh != NULL) TerminateThread(hAcceptTh, 0);
/* Oodake, kuni kõik aktiivsed serverilõimed on lõpetatud. */
jaoks (ith = 0; ith< MAXCLIENTS; ith++) if (srv_arg .status != 0) {
WaitForSingleObject(srv_arg.srv_thd, LÕPETTU);
CloseHandle(srv_arg.srv_thd);

staatiline DWORD WINAPI AktseptTh(SERVER_ARG * pThArg) (
/* Vastuvõttev lõim, mis võimaldab põhilõimel lõpulippu pollida. Lisaks loob see lõim serverilõime. */
AddrLen = suurus(ConnectSAddr);
pThArg->sock = aktsepteeri(SrvSock, /* See on blokeeriv kõne. */
(struct sockaddr *)&ConnectSAddr, &AddrLen);
/* Uus ühendus. Looge serveri lõim. */
pThArg->srv_thd = (HANDLE)_beginthreadex(NULL, 0, server, pThArg, 0, & ThId);
tagasi 0; /* Serveri lõim jätkab töötamist. */

staatiline DWORD WINAPI server (SERVER_ARG * pThArg)
/* Serveri lõime funktsioon. Voog luuakse nõudmisel. */
/* Iga lõim säilitab pinus oma päringu, vastuse ja logi andmestruktuurid. */
/* ... ServerNP standarddeklaratsioonid on välja jäetud ... */
int (*dl_addr)(char *, char *);
char *ws = "\0\t\n"; /* Tühikud. */
GetStartupInfo(&StartInfoCh);
/* Looge ajutine failinimi. */
sprintf(TempFile, "%s%d%s", "ServerTemp", pThArg->number, ".tmp");
while (!Valmis && !ShutFlag) ( /* Peamine käsutsükkel. */
Katkesta ühendus = ReceiveRequestMessage(&Request, ConnectSock);
Valmis = Katkesta ühendus || (strcmp(Request.Record, "$Quit") == 0) || (strcmp(Request.Record, "$ShutDownServer") == 0);
/* Peatage see lõim, kui see saab käsu "$Quit" või "$ShutDownServer". */
hTrapFile = CreateFail(TempFail, ÜLDINE_LOE | ÜLDINE_KIRJUTAMINE, FAIL_JAGAMIS_LUGEMINE | FAIL_JAGA_KIRJUTAMINE, &TempSA, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
/* Kontrollige selle käsu olemasolu DLL-is. Käsu lihtsustamiseks */
/* jagatud raamatukogudes on rohkem kõrge prioriteet võrreldes */
/* protsessikäskudega. Kõigepealt peate välja võtma käsu nime.*/
i = strcspn(Request.Record, ws); /* Märgi suurus. */
memcpy(sys_command, Request.Record, i) ;
dl_addr = NULL; /* Määratakse, kui funktsioon GetProcAddress on edukas. */
if (pThArg->dlhandle != NULL) (/* "Protsessisisese" serveri toe kontrollimine. */
dl_addr = (int (*)(char *, char *))GetProcAddress(pThArg->dlhandle, sys_command);
/* Kaitske serveriprotsessi DLL-idesse visatud erandite eest*/
(*dl_addr)(Request.Record, TempFile);
) __välja arvatud (EXCEPTION_EXECUTE_HANDLER) (
ReportError(_T("Erand DLL-is"), 0, FALSE);
if (dl_addr == NULL) ( /* Protsessisisese serveri tugi puudub. */
/* Käsu täitmiseks protsessi loomine. */
/* ... Sama mis serverNP-s ... */
) /* Peamise käsutsükli lõpp. Hankige järgmine käsk. */
/* Käsutsükli lõpp. Vabastage ressursse; voolust välja tulla. */
_tprintf(_T("Serveri seiskamine# %d\n"), pThArg->number);
if (strcmp(Request.Record, "$ShutDownServer") == 0) (

Ohutusmärkused

Nagu siin on esitatud, on see klient-server süsteem Mitte on ohutu. Kui teie süsteemis töötab server ja keegi teab teie kasutatava pordi numbrit ja arvuti nime, võivad nad teie süsteemi rünnata. Teine kasutaja, kes oma arvutis klientprogrammi käivitab, saab teie süsteemis täita käske, mis võimaldavad neil näiteks faile kustutada või muuta.

Ehitusmeetodite täielik arutelu turvalised süsteemid jääb selle raamatu raamidest välja. Peatükk 15 näitab aga, kuidas turvata Windowsi objektid, ja harjutus 12.14 soovitab kasutada SSL-protokolli.

Protsessisisesed serverid

Nagu eelnevalt märgitud, on serverSK programmi peamine täiustus seotud protsessisiseste serverite kaasamisega. Programm 12.3 näitab, kuidas kirjutada seda tüüpi teenust pakkuvat DLL-i. Programm sisaldab kahte funktsiooni, mida te juba teate – sõnalugemisfunktsiooni ja toupperi funktsiooni.

Kokkuleppeliselt on esimene parameeter käsurida ja teine ​​väljundfaili nimi. Lisaks peaksite alati meeles pidama, et funktsioon käivitatakse serveriga samas lõimes ja see nõuab rangeid lõime ohutusnõudeid, sealhulgas, kuid mitte ainult, järgmist.

Funktsioonid ei tohi kuidagi protsessikeskkonda muuta. Näiteks kui üks funktsioonidest muudab töökataloogi, mõjutab see kogu protsessi.

Samuti ei tohiks funktsioonid standardset sisendit ja väljundit ümber suunata.

Programmeerimisvead, nagu indeksi või osuti piiridest väljumine või virna ületäitumine, võivad rikkuda mõne teise lõime või protsessi enda mälu.

Ressursilekked, näiteks need, mis tulenevad vabanenud mälu õigeaegsest süsteemi tagastamisest või käepidemete sulgemisest, avaldavad lõppkokkuvõttes negatiivset mõju kogu serverisüsteemile.

Selliseid rangeid nõudeid protsessidele ei kehtestata põhjusel, et üks protsess ei saa reeglina teisi protsesse kahjustada ning pärast protsessi lõpuleviimist vabastatakse selle hõivatud ressursid automaatselt. Seetõttu arendatakse ja silutakse teenust tavaliselt lõimena ning alles pärast seda, kui ollakse veendunud, et see töötab usaldusväärselt, teisendatakse see DLL-iks.

Programm 12.3 pakub väikest DLL-i, mis sisaldab kahte funktsiooni.

Programm 12.3. käsk: näide protsessiserverites
/* Peatükk 12. käsud.lk. */
/* Protsessisisesed serverikäsud kasutamiseks serverSK-s ja nii edasi. */
/* DLL-idena on realiseeritud mitu käsku. */
/* Iga käsufunktsioon võtab kaks parameetrit ja annab */
/* turvaline täitmine mitme lõimega režiimis. Esimene parameeter */
/* on string: käsk arg1 arg2 ... argn */
/* (st tavaline käsurida) ja teine ​​on väljundfaili nimi. … */

static void extract_token(int, char *, char *);

int wcip(char * käsk, char * väljundfail)
/* Sõnaloendur; protsessis. */
/* MÄRKUS: lihtsustatud versioon; tulemused võivad erineda wc-utiliidi pakutavatest. */
while ((c = fgetc(fin)) != EOF) (
/* … Standardkood – pole selle näite puhul oluline … */
/* Kirjutage tulemused. */
fprintf(fout, " %9d %9d %9d %s\n", nl, nw, nc, sisendfail);

int toupperip(char * käsk, char * väljundfail)
/* Teisendab sisendi suurtähtedeks; teostatakse protsessi raames. */
/* Teine märk määrab sisendfaili (esimene märk on "toupperip"). */
ekstrakti_märk(1, käsk, sisendfail);
fin = fopen(sisend_fail, "r");
fout = fopen(väljund_fail, "w");
while ((c = fgetc (fin)) != EOF) (
if (isalpha(c)) c = toupper(c);

static void extract_token(int it, char * käsk, char * token) (
/* Eraldab käsklusest (esimese märgi number */) märgi numbri "it"
/* on "0"). Tulemus läheb "märgiks" */
/* Tühikuid kasutatakse märgi eraldajatena. … */

Reapõhised sõnumid , DLL ja TLS reisipunktid

ServerSK ja clientSK programmid suhtlevad omavahel sõnumite vahetamise teel, millest igaüks koosneb 4-baidisest päisest, mis sisaldab sõnumi suurust ja sisu ennast. Selle lähenemisviisi tavaline alternatiiv on see, et sõnumid eraldatakse üksteisest rea lõpu (või reavahetuse) tähemärkidega.

Selliste sõnumitega töötamise raskus seisneb selles, et sõnumi pikkus pole ette teada ja seetõttu tuleb iga sissetulevat tähemärki kontrollida. Korraga ühe märgi toomine on aga äärmiselt ebaefektiivne ja seetõttu hoitakse märgid puhvris, mille sisu võib sisaldada ühte või mitut realõpumärki ja ühe või mitme sõnumi komponente. Sel juhul tuleb sõnumi vastuvõtmise funktsiooni kõnede vahelisel ajal hoida puhvri sisu ja olek muutumatuna.Ühe keermega keskkonnas saab selleks kasutada staatilisi mälurakke, kuid jagamine Mitu lõime, mis kasutavad sama staatilist muutujat, pole võimalik.

Üldisemas sõnastuses seisame siin silmitsi pikaajaliste olekute salvestamise probleem mitme keermega keskkonnas(mitme lõimega püsiv oleku probleem). See probleem tekib alati, kui lõimekindel funktsioon peab toetama teatud teabe püsivust ühest funktsioonikutsest järgmiseni. Sama probleem esineb standardse C teegi funktsiooniga strtook, mis on loodud stringi läbimiseks, et leida konkreetse märgi järjestikuseid eksemplare.

Pikaajaliste olekute probleemi lahendamine mitme lõimega keskkonnas

Soovitud lahendus ühendab mitu komponenti:

DLL, mis sisaldab funktsioone sõnumite saatmiseks ja vastuvõtmiseks.

Funktsioon, mis tähistab DLL-i sisenemispunkti.

Stream Local Storage Area (TLS, 7. peatükk). Protsessi ühendamisega teegiga kaasneb DLL-i indeksi loomine ja lahtiühendamisega kaasneb hävitamine. Indeksi väärtus salvestatakse staatilisse salvestusruumi, millele pääsevad juurde kõik lõimed.

Struktuur, milles puhver ja selle praegune olek on salvestatud. Struktuur levitatakse iga kord, kui teegiga liitub uus lõim, ja selle aadress salvestatakse selle lõime TLS-kirjesse. Kui lõime teegist eraldatakse, vabastatakse selle struktuuriga hõivatud mälu.

Seega toimib TLS staatilise poena ja igal lõimel on selle poe unikaalne koopia.

Näide: lõimekindel DLL pesasõnumite jaoks

Programm 12.4 on DLL, mis sisaldab kahte funktsiooni märgistringide töötlemiseks (millel on antud juhul nimedes "CS", alates märgistring- märgistring) või sokli voogedastusfunktsioonid: SendCSMessage ja ReceiveCSMessage, samuti DllMain sisendpunkt (vt peatükk 5). Need kaks funktsiooni täidavad sama rolli nagu funktsioon ReceiveMessage, samuti funktsioonid, mida kasutatakse programmides 12.1 ja 12.2, ning tegelikult asendavad need.

Funktsioon DllMain on tüüpiline näide pikaajalise olekuprobleemi lahendamisest mitme lõimega keskkonnas ning ühendab TLS-i ja DLL-e.

Ressursside vabastamine lõimede eraldamisel (juhtum DLL_THREAD_DETACH) on eriti oluline serverikeskkonnas; Kui te seda ei tee, ammenduvad lõpuks serveri ressursid, mis võib põhjustada selle krahhi, halva jõudluse või mõlemat.

Märge

Mõned allpool illustreeritud mõisted ei ole otseselt pesadega seotud, kuid neid käsitletakse siiski siin, mitte eelmistes peatükkides, sest näide annab kasuliku võimaluse illustreerida niidikindlate DLL-ide loomise tehnikaid realistlikus keskkonnas.

Seda DLL-i kasutav kliendi- ja serverikood, mida on veidi muudetud programmidest 12.1 ja 12.2, on saadaval raamatu veebisaidil.

Programm 12.4. SendReceiveSKST: Thread Safe DLL
/* SendReceiveSKST.с - Mitmelõimeline DLL voolu pesa. */
/* Sõnumi eraldajatena kasutatakse lõpumärke */
/* stringid ("\0"), seega pole kirja suurus ette teada. */
/* Sissetulevad andmed puhverdatakse ja salvestatakse vahepeal */
/* funktsioonikutsed. */
/* Sel eesmärgil kasutatakse lõime kohalikke salvestusalasid */
/* (Thread Local Storage, TLS), mis pakub iga lõime */
/* enda privaatne "staatiline salvestusruum". */

#include "ClntSrvr.h" /* Määrab päringu ja vastuse kirjed. */

/* "static_buf" sisaldab jääkandmete "static_buf_len" baite. */
/* Rea lõpu tähemärke (nullmärke) võib esineda või mitte */
/* ja ei osale. */
char static_buf ;

staatiline DWORD TlsIx = 0; /* TLS-i indeks – IGAL PROTSESSIL ON OMA INDEKS.*/
/* Ühe lõimega teegi puhul kasutatakse järgmisi määratlusi:
static char static_buf ;
staatiline LONG32 static_buf_len; */
/* DLL-i põhifunktsioon. */

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) (
/* Pealõnga jaoks pole ühendust, seega tuleb lõime ühendamise toimingud teha ka protsessi ühendamise ajal. */
/* Näitab, et mälu pole eraldatud. */
tagasta TRUE; /* Seda väärtust tegelikult ignoreeritakse. */
/* Eemalda ka põhilõng. */

BOOL ReceiveCSMessage(REQUEST *pRequest, SOCKET sd) (
/* Tagastusväärtus TRUE näitab viga või ühenduse katkemist. */
LONG32 nRemainRecv = 0, nXfer, k; /* Peavad olema märgiga täisarvud. */
CHAR TempBuf;
p = (STATIC_BUF *)TlsGetValue(TlsIx);
if (p == NULL) ( /* Initsialiseeri esimesel kõnel. */
/* Seda salvestusruumi levitavad ainult lõimed, mis seda vajavad */
/* vajalik. Muud tüüpi vood võivad TLS-i kasutada muudel eesmärkidel. */
p = malloc(suurus(STATIC_BUF));
if (p == NULL) tagastab TRUE; /* Viga. */
p->staatiline_buf_len = 0; /* Initsialiseeri olek. */
/* Loendab kuni reavahetuse märgini, jättes jääkandmed staatilisse puhvrisse. */
jaoks (k = 0; k< p->static_buf_len && p->static_buf[k] != "\0"; k++) (
teade[k] = p->staatiline_buf[k];
) /* k – edastatud märkide arv. */
if(k< p->static_buf_len) ( /* Staatilises puhvris tuvastati nullmärk. */
p->static_buf_len –= (k + 1); /* Reguleerige staatilise puhvri olekut. */
memcpy(p->staatiline_buf, &(p->staatiline_buf), p->staatiline_buf_len);
return FALSE; /* Socket-sisendit pole vaja. */

/* Kogu staatiline puhver on üle kantud. Rea lõpetajat ei tuvastatud.*/
nRemainRecv = suurus(TempBuf) – 1 – p->static_buf_len;
pPuffer = sõnum + p->staatiline_puhver;
while (nRemainRecv > 0 && !Katkesta ühendus) (
nXfer = recv(sd, TempBuf, nRemainRecv, 0);
/* Edastab kõik märgid kuni nullmärgini, kui neid on, sihtsõnumisse. */
jaoks (k = 0; k< nXfer && TempBuf[k] != "\0"; k++) {
if (k >= nXfer) ( /*Rea lõpetajat ei tuvastatud, loe edasi*/
) else ( /* Tuvastati rea lõpetaja. */
memcpy(p->staatiline_buf, &TempBuf, nXfer – k – 1);
p->static_buf_len = nXfer – k – 1;

BOOL SendCSMessage(RESPONSE *pResponse, SOCKET sd) (
/* Saada päring sd-pesas asuvasse serverisse. */
nRemainSend = strlen(pBuffer) + 1;
while (nRemainSend > 0 && !Katkesta ühendus) (
/* Saatmine ei garanteeri, et kogu kiri saadetakse. */
nXfer = saada(sd, pBuffer, nRemainSend, 0);
fprintf(stderr, "\nServeri ühenduse katkestamine enne lõpetamistaotluse saatmist");