Stekkeraansluiting. Netwerktheorie en lage niveaus. Voorbeeld: Thread-safe DLL voor socketberichten

Laatste update: 31.10.2015

In de kern internetwerken sockets zijn gebaseerd op TCP- en UDP-protocollen. In .NET worden sockets weergegeven door de klasse System.NET.Sockets.Socket, die een interface op laag niveau biedt voor het ontvangen en verzenden van berichten via het netwerk.

Laten we eens kijken naar de belangrijkste eigenschappen van deze klasse:

    AddressFamily: retourneert alle adressen die door de socket worden gebruikt. Deze eigenschap vertegenwoordigt een van de waarden die zijn gedefinieerd in de AdresFamily-opsomming met dezelfde naam. De opsomming bevat 18 verschillende waarden, de meest gebruikte zijn:

    • InterNetwork: IPv4-adres

      InterNetworkV6: IPv6-adres

      Ipx: IPX- of SPX-adres

      NetBios: NetBios-adres

    Beschikbaar: Retourneert de hoeveelheid gegevens die beschikbaar zijn om te lezen

    Verbonden: retourneert waar als de socket is verbonden met de externe host

    LocalEndPoint: retourneert lokaal punt, waarop de socket wordt gelanceerd en waarop deze gegevens ontvangt

    ProtocolType: Retourneert een van de ProtocolType-opsommingswaarden die het protocol vertegenwoordigt dat door de socket wordt gebruikt. Er zijn de volgende mogelijke waarden:

    • IPSecAuthenticationHeader (IPv6 AH-header)

      IPSecEncapsulatedSecurityPayload (IPv6 ESP-header)

      IPv6DestinationOptions (header IPv6-bestemmingsopties)

      IPv6FragmentHeader (IPv6-fragmentkop)

      IPv6HopByHopOptions (header IPv6 Hop by Hop-opties)

      IPv6NoNextHeader (IPv6 Geen volgende header)

      IPv6RoutingHeader (IPv6-routeringsheader)

      Onbekend (onbekend protocol)

      Niet gespecificeerd (niet gespecificeerd protocol)

    Elke waarde vertegenwoordigt een corresponderend protocol, maar de meest gebruikte zijn Tcp en Udp.

    RemoteEndPoint: retourneert het adres van de externe host waarmee de socket is verbonden

    SocketType: retourneert het sockettype. Vertegenwoordigt een van de waarden uit de SocketType-opsomming:

    • Dgram: De socket zal datagrammen ontvangen en verzenden Udp-protocol. Dit type socket werkt in combinatie met het protocoltype Udp en de waarde AddressFamily.InterNetwork

      Raw: de socket heeft toegang tot het onderliggende protocol transport laag en kan protocollen zoals ICMP en IGMP gebruiken om berichten te verzenden

      Rdm: waarmee de socket kan communiceren externe gastheren zonder installatie permanente verbinding. Indien door de socket verzonden berichten niet kunnen worden afgeleverd, ontvangt de socket hierover een melding

      Seqpacket: Biedt betrouwbare gegevensoverdracht in twee richtingen met een permanente verbinding

      Stream: zorgt voor betrouwbare gegevensoverdracht in twee richtingen met een permanente verbinding. Communicatie maakt gebruik van het TCP-protocol, dus dit sockettype wordt gebruikt in combinatie met het Tcp-protocoltype en de waarde AddressFamily.InterNetwork

      Onbekend: NetBios-adres

U kunt een van de constructors gebruiken om een ​​socketobject te maken. Een socket die het TCP-protocol gebruikt:

Socket socket = nieuwe Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Of een socket die het Udp-protocol gebruikt:

Socket socket = nieuwe Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

Bij het maken van een socket kunnen we dus specificeren verschillende combinaties protocollen, sockettypen, waarden uit de AddressFamily-opsomming. Tegelijkertijd zijn echter niet alle combinaties correct. Om via het Tcp-protocol te werken, moeten we dus de volgende parameters opgeven: AddressFamily.InterNetwork, SocketType.Stream en ProtocolType.Tcp. Voor Udp zal de set parameters anders zijn: AddressFamily.InterNetwork, SocketType.Dgram en ProtocolType.Udp. Voor andere protocollen zal de reeks waarden anders zijn. Daarom kan het gebruik van sockets enige kennis vereisen van hoe individuele protocollen werken. Hoewel met betrekking tot Tcp en Udp alles relatief eenvoudig is.

Algemeen principe van socketbediening

Bij het werken met sockets zullen we, ongeacht de geselecteerde protocollen, vertrouwen op de methoden van de Socket-klasse:

    Accepteren() : creëert nieuw voorwerp Socket voor het verwerken van inkomende verbindingen

    Bind() : Bindt een Socket-object aan een lokaal eindpunt

    Close() : sluit de socket

    Connect(): brengt een verbinding tot stand met een externe host

    Listen() : Begint te luisteren naar inkomende verzoeken

    Poll(): bepaalt de status van de socket

    Ontvangen() : Ontvangt gegevens

    Send() : Verzendt gegevens

    Shutdown(): blokkeert de socket voor het ontvangen en verzenden van gegevens

Afhankelijk van het gebruikte protocol (TCP, UDP, etc.) algemeen principe Het werken met stopcontacten zal enigszins variëren.

Wanneer u een protocol gebruikt dat het tot stand brengen van een verbinding vereist, zoals TCP, moet de server de Bind-methode aanroepen om een ​​punt in te stellen waar naar inkomende verbindingen wordt geluisterd en vervolgens beginnen met luisteren naar verbindingen met behulp van de Listen-methode. Vervolgens kunt u met behulp van de Accept-methode inkomende verbindingsverzoeken ontvangen in de vorm van een Socket-object, dat wordt gebruikt om te communiceren met het externe knooppunt. Op het ontvangen Socket-object worden respectievelijk de methoden Verzenden en Ontvangen aangeroepen om gegevens te verzenden en te ontvangen. Als het nodig is om verbinding te maken met de server, wordt de Connect-methode aangeroepen. De verzend- of ontvangstmethoden worden ook gebruikt om gegevens met de server uit te wisselen.

Als u een verbindingsloos protocol gebruikt, zoals UDP, hoeft u de methode Listen niet aan te roepen nadat u de methode Bind hebt aangeroepen. En in dit geval wordt de methode ReceiverFrom gebruikt om gegevens te ontvangen, en wordt de methode SendTo gebruikt om gegevens te verzenden.

Stopcontacten

Stopcontact is het ene uiteinde van een tweerichtingscommunicatiekanaal tussen twee programma's die op het netwerk draaien. Door twee stopcontacten met elkaar te verbinden, kunt u gegevens uitwisselen verschillende processen(lokaal of op afstand). De socketimplementatie biedt inkapseling van netwerk- en transportlaagprotocollen.

Sockets zijn oorspronkelijk ontwikkeld voor UNIX aan de University of California, Berkeley. In UNIX volgt de communicatie-I/O-methode het open/lezen/schrijven/sluiten-algoritme. Voordat een bron kan worden gebruikt, moet deze worden geopend met de juiste machtigingen en andere instellingen. Zodra een bron open is, kunnen gegevens worden gelezen of geschreven. Na gebruik van een bron moet de gebruiker de methode Close() aanroepen om het besturingssysteem te laten weten dat het klaar is met de bron.

Wanneer naar de operatiekamer UNIX-systeem fondsen zijn toegevoegd communicatie tussen processen(Communicatie tussen processen, IPC) en netwerkuitwisseling werd het bekende input-output-patroon overgenomen. Alle bronnen die beschikbaar zijn voor communicatie in UNIX en Windows worden geïdentificeerd door handvatten. Deze descriptoren, of handvatten, kan verwijzen naar een bestand, geheugen of een ander communicatiekanaal, maar verwijst in feite naar interne structuur gegevens die door het besturingssysteem worden gebruikt. De socket, die dezelfde bron is, wordt ook weergegeven door een descriptor. Daarom kan voor stopcontacten de levensduur van een handvat in drie fasen worden verdeeld: het openen (creëren) van het stopcontact, ontvangen van of verzenden naar het stopcontact, en ten slotte het sluiten van het stopcontact.

De IPC-interface voor communicatie tussen verschillende processen is bovenop I/O-methoden gebouwd. Ze maken het voor sockets gemakkelijker om gegevens te verzenden en te ontvangen. Elk doel wordt gespecificeerd door een socketadres, zodat dit adres in de client kan worden opgegeven om een ​​verbinding met het doel tot stand te brengen.

Socket-typen

Er zijn twee hoofdtypen sockets: stream-sockets en datagram-sockets.

Stream-aansluitingen

Een stream-aansluiting is een aansluiting met verbinding tot stand gebracht, bestaande uit een stroom bytes die bidirectioneel kan zijn, wat betekent dat een applicatie zowel gegevens kan verzenden als ontvangen via dit eindpunt.

Een streamsocket zorgt voor foutcorrectie, zorgt voor de levering en handhaaft de gegevensconsistentie. U kunt erop vertrouwen dat het geordende, gedupliceerde gegevens levert. De streamaansluiting is ook geschikt voor zenden grote volumes gegevens, aangezien de overhead die gepaard gaat met het opzetten ervan aparte aansluiting voor elk verzonden bericht is mogelijk niet acceptabel kleine volumes gegevens. Stream-sockets bereiken dit kwaliteitsniveau door gebruik te maken van een protocol Transmissiecontroleprotocol (TCP). TCP zorgt ervoor dat gegevens in de juiste volgorde en zonder fouten de andere kant bereiken.

Bij dit type socket wordt het pad gevormd voordat berichten worden verzonden. Dit zorgt ervoor dat beide partijen die bij de interactie betrokken zijn, dit accepteren en erop reageren. Als een applicatie twee berichten naar een ontvanger verzendt, is gegarandeerd dat de berichten in dezelfde volgorde worden ontvangen.

Echter, individuele berichten kunnen in pakketten worden opgesplitst en er is geen manier om de grenzen van records te bepalen. Bij gebruik van TCP zorgt dit protocol ervoor dat de verzonden gegevens in pakketten van de juiste grootte worden opgedeeld, naar het netwerk worden verzonden en aan de andere kant weer worden samengevoegd. De applicatie weet alleen wat hij naar de TCP-laag verzendt bepaald aantal bytes en de andere kant ontvangt deze bytes. Op zijn beurt verdeelt TCP deze gegevens effectief in pakketten geschikte maat, ontvangt deze pakketten aan de andere kant, haalt de gegevens eruit en voegt ze samen.

Streams zijn gebaseerd op expliciete verbindingen: socket A vraagt ​​om een ​​verbinding met socket B, en socket B accepteert of wijst het verbindingsverzoek af.

Als de gegevens gegarandeerd aan de andere kant moeten worden afgeleverd of als de omvang van de gegevens groot is, verdienen streamsockets de voorkeur boven datagramsockets. Als betrouwbare communicatie tussen twee applicaties van het allergrootste belang is, kies dan voor stream-sockets.

Server E-mail presenteert een voorbeeld van een applicatie die inhoud in de juiste volgorde moet aanleveren, zonder doublures of weglatingen. De streamsocket is afhankelijk van TCP om ervoor te zorgen dat berichten op hun bestemming worden afgeleverd.

Datagram-aansluitingen

Datagramsockets worden soms verbindingsloze sockets genoemd, dat wil zeggen dat er geen expliciete verbinding tussen de sockets tot stand wordt gebracht - het bericht wordt naar de gespecificeerde socket verzonden en kan dienovereenkomstig worden ontvangen van de gespecificeerde socket.

Vergeleken met datagramsockets bieden streamsockets eigenlijk meer betrouwbare methode, maar voor sommige toepassingen is de overhead die gepaard gaat met het tot stand brengen van een expliciete verbinding onaanvaardbaar (bijvoorbeeld een tijdserver die tijdsynchronisatie biedt aan zijn clients). Het tot stand brengen van een betrouwbare verbinding met de server kost immers tijd, wat alleen maar vertragingen in de dienstverlening met zich meebrengt en de taak van de serverapplicatie mislukt. Om overhead te verminderen, moet u datagramsockets gebruiken.

Het gebruik van datagramsockets vereist dat de overdracht van gegevens van de client naar de server wordt afgehandeld Gebruikersdatagramprotocol (UDP). In dit protocol worden enkele beperkingen opgelegd aan de grootte van berichten, en in tegenstelling tot streamsockets, die op betrouwbare wijze berichten naar de doelserver kunnen sturen, bieden datagramsockets geen betrouwbaarheid. Als de gegevens ergens op het netwerk verloren gaan, zal de server geen fouten melden.

Naast de twee besproken typen bestaat er ook een algemene vorm van sockets, die onbewerkt of raw wordt genoemd.

Ruwe stopcontacten

Het belangrijkste doel van het gebruik van raw sockets is het omzeilen van het mechanisme waarmee de computer TCP/IP verwerkt. Dit wordt bereikt door een speciale implementatie van de TCP/IP-stack te bieden die het mechanisme van de TCP/IP-stack in de kernel overschrijft - het pakket wordt rechtstreeks aan de applicatie doorgegeven en wordt daarom veel efficiënter verwerkt dan wanneer het door de client gaat. hoofdprotocolstack.

Per definitie is een raw socket een socket die pakketten accepteert, de TCP- en UDP-lagen in de TCP/IP-stack omzeilt en deze rechtstreeks naar de applicatie verzendt.

Wanneer dergelijke sockets worden gebruikt, passeert het pakket niet het TCP/IP-filter, d.w.z. wordt op geen enkele manier verwerkt en verschijnt in zijn ruwe vorm. In dit geval is het de verantwoordelijkheid van de ontvangende applicatie om alle gegevens correct te verwerken en acties uit te voeren, zoals het strippen van headers en het parseren van velden, zoals het opnemen van een kleine TCP/IP-stack in de applicatie.

Het komt echter niet vaak voor dat u een programma nodig heeft dat zich bezighoudt met onbewerkte sockets. Als u geen systeem schrijft software of een programma dat lijkt op een pakketanalysator, u hoeft niet op dergelijke details in te gaan. Raw-sockets worden voornamelijk gebruikt bij de ontwikkeling van gespecialiseerde protocoltoepassingen op laag niveau. Verschillende TCP/IP-hulpprogramma's, zoals traceroute, ping of arp, gebruiken bijvoorbeeld onbewerkte sockets.

Werken met raw sockets vereist gedegen kennis basisprotocollen TCP/UDP/IP.

Poorten

De poort is gedefinieerd om het probleem van gelijktijdige interactie met meerdere applicaties mogelijk te maken. In wezen breidt het het concept van een IP-adres uit. Een computer waarop meerdere applicaties tegelijkertijd draaien en een pakket van het netwerk ontvangt, kan het doelproces identificeren met behulp van uniek nummer poort die is opgegeven bij het tot stand brengen van een verbinding.

Een socket bestaat uit het IP-adres van de machine en het gebruikte poortnummer TCP-toepassing. Omdat een IP-adres uniek is op internet en poortnummers uniek zijn op een individuele machine, zijn socketnummers ook uniek op het hele internet. Dankzij deze eigenschap kan een proces via het netwerk communiceren met een ander proces, uitsluitend op basis van het socketnummer.

Poortnummers zijn gereserveerd voor bepaalde diensten. Dit zijn bekende poortnummers, zoals poort 21, die in FTP worden gebruikt. Uw applicatie kan elk poortnummer gebruiken dat niet is gereserveerd en nog niet in gebruik is. Bureau Internet Toegewezen Nummers Autoriteit (IANA) houdt een lijst bij van algemeen bekende poortnummers.

Normaal gesproken bestaat een client-servertoepassing die gebruikmaakt van sockets uit twee verschillende toepassingen- een client die een verbinding initieert met het doel (server), en een server die wacht op een verbinding van de client.

Aan de clientzijde moet de applicatie bijvoorbeeld het doeladres en poortnummer kennen. Door een verbindingsverzoek te verzenden, probeert de client verbinding te maken met de server:

Als gebeurtenissen zich succesvol ontwikkelen, op voorwaarde dat de server wordt gestart voordat de client verbinding probeert te maken, gaat de server akkoord met de verbinding. Na toestemming te hebben gegeven, creëert de serverapplicatie een nieuwe socket om specifiek te communiceren met de client die de verbinding tot stand heeft gebracht:

Nu kunnen de client en de server met elkaar communiceren, berichten lezen vanuit hun eigen socket en dienovereenkomstig berichten schrijven.

Werken met sockets in .NET

Socketondersteuning in .NET wordt geleverd door klassen in de naamruimte Systeem.Net.Sockets- laten we beginnen met hun korte beschrijving.

Klassen voor het werken met stopcontacten
Klas Beschrijving
Multicast-optie De klasse MulticastOption stelt de IP-adreswaarde in voor deelname aan of verlaten van een IP-groep.
NetwerkStream De klasse NetworkStream wordt geïmplementeerd basisklasse de stroom waaruit gegevens worden verzonden en waarin deze worden ontvangen. Dit is een abstractie op hoog niveau die een verbinding met een TCP/IP-communicatiekanaal vertegenwoordigt.
TcpClient De TcpClient-klasse bouwt voort op de Socket-klasse om TCP-service voor meer te bieden hoog niveau. TcpClient biedt verschillende methoden voor het verzenden en ontvangen van gegevens via het netwerk.
TcpListener Deze klasse bouwt ook voort op de Socket-klasse op laag niveau. Het hoofddoel zijn servertoepassingen. Het luistert naar inkomende verbindingsverzoeken van clients en informeert de toepassing over eventuele verbindingen.
UdpClient UDP is een verbindingsloos protocol, daarom is er andere functionaliteit vereist om de UDP-service in .NET te implementeren.
SocketException Deze uitzondering wordt gegenereerd wanneer er een fout optreedt op de socket.
Stopcontact De laatste klasse in de naamruimte System.Net.Sockets is de Socket-klasse zelf. Het biedt de basisfunctionaliteit van een sockettoepassing.

Socket-klasse

De Socket-klasse wordt afgespeeld belangrijke rol in netwerkprogrammering, waarbij de werking van zowel de client als de server wordt gegarandeerd. Aanroepen naar methoden van deze klasse presteren voornamelijk noodzakelijke controles beveiligingsgerelateerde methoden, inclusief het controleren van beveiligingsmachtigingen, waarna ze worden doorgestuurd naar de tegenhangers van deze methoden in de Windows Sockets API.

Voordat we naar een voorbeeld gaan van het gebruik van de klasse Socket, kijken we eerst naar enkele belangrijke eigenschappen en methoden van deze klasse:

Eigenschappen en methoden van de klasse Socket
Eigenschap of methode Beschrijving
AdresFamilie Geeft de socketadresfamilie: een waarde uit de Socket.AddressFamily-opsomming.
Beschikbaar Retourneert de hoeveelheid gegevens die beschikbaar zijn om te lezen.
Blokkeren Haalt of stelt een waarde in die aangeeft of de socket zich in de blokkeermodus bevindt.
Verbonden Retourneert een waarde die aangeeft of de socket is verbonden met de externe host.
Lokaal Eindpunt Geeft het lokale eindpunt.
Protocoltype Geeft het protocoltype van de socket.
RemoteEndPoint Geeft het externe socketeindpunt.
SocketType Geeft het sockettype.
Aanvaarden() Creëert een nieuwe socket om een ​​inkomend verbindingsverzoek af te handelen.
Binden() Bindt een socket aan een lokaal eindpunt om te luisteren naar binnenkomende verbindingsaanvragen.
Dichtbij() Forceert het sluiten van de socket.
Aansluiten() Brengt een verbinding tot stand met een externe host.
GetSocketOption() Retourneert de SocketOption-waarde.
IOControl() Stelt bedrijfsmodi op laag niveau in voor het stopcontact. Deze methode biedt toegang op laag niveau tot de onderliggende Socket-klasse.
Luisteren() Zet het stopcontact in de luister- (wacht)modus. Deze methode is alleen bedoeld voor servertoepassingen.
Ontvangen() Ontvangt gegevens van een aangesloten stopcontact.
Enquête() Bepaalt de status van de socket.
Selecteer() Controleert de status van een of meer stopcontacten.
Versturen() Verzendt gegevens naar het aangesloten stopcontact.
SetSocketOption() Stelt de socketoptie in.
Afsluiten() Schakelt verzend- en ontvangstbewerkingen op de socket uit.

VSEVOLOD STAKHOV

Socket-programmering

Het overgrote deel van het netwerk serverprogramma's georganiseerd met behulp van stopcontacten. In wezen zijn sockets vergelijkbaar met bestandsdescriptors met één zeer belangrijk verschil- Sockets worden gebruikt voor communicatie tussen applicaties op het netwerk of op lokaal apparaat. De programmeur heeft dus geen probleem met de gegevenslevering; sockets doen het voor hem. U hoeft er alleen maar voor te zorgen dat de socketparameters van de twee applicaties overeenkomen.

Netwerkaansluitingen zijn dus gepaarde structuren die strikt met elkaar zijn gesynchroniseerd. Om sockets te maken op elk besturingssysteem dat ze ondersteunt, gebruikt u de socketfunctie (gelukkig zijn sockets voldoende gestandaardiseerd zodat ze kunnen worden gebruikt om gegevens over te dragen tussen applicaties die draaien op verschillende platforms). Het functieformaat is:

int socket(int-domein, int-type, int-protocol);

De domeinparameter specificeert het type transportprotocol, d.w.z. . De volgende protocollen worden momenteel ondersteund (maar houd er rekening mee dat voor verschillende soorten protocoltype van de adresstructuur zal anders zijn):

  • PF_UNIX of PF_LOCALlokale communicatie voor UNIX-besturingssysteem (en vergelijkbaar).
  • PF_INET – IPv4, IP-Internetprotocol, momenteel het meest voorkomende (32-bits adres).
  • PF_INET6– IPv6, de volgende generatie van het IP-protocol (IPng) – 128-bits adres.
  • PF_IPX – IPX– Novell-protocollen.

Andere protocollen worden ondersteund, maar deze 4 zijn het populairst.

De parameter type betekent het sockettype, d.w.z. hoe de gegevens worden overgedragen: meestal wordt de SOCK_STREAM-constante gebruikt, de gebruiksmiddelen ervan veilige overdracht bidirectionele gegevensstroom met foutcontrole. Met deze manier van dataoverdracht hoeft de programmeur zich geen zorgen te maken over het afhandelen van netwerkfouten, al biedt dit geen bescherming tegen logische fouten, wat wel belangrijk is voor een netwerkserver.

De protocolparameter specificeert specifiek type protocol voor een bepaald domein, bijvoorbeeld IPPROTO_TCP of IPPROTO_UDP (de parameter type moet in in dit geval SOCK_DGRAM zijn).

De socketfunctie creëert eenvoudigweg een eindpunt en retourneert een sockethandle; Totdat het stopcontact via de connect-functie met het externe adres is verbonden, kunnen er geen gegevens doorheen worden verzonden! Als er pakketten verloren gaan op het netwerk, b.v. Als er een communicatiefout optreedt, wordt er een Broken Pipe - SIGPIPE-signaal verzonden naar de applicatie die de socket heeft gemaakt. Het is dus raadzaam om een ​​handler toe te wijzen dit signaal functie signaal. Nadat een socket met een andere is verbonden met de connect-functie, kunnen er gegevens overheen worden verzonden met behulp van standaard lees-schrijffuncties of gespecialiseerde recv-zendfuncties. Na beëindiging van de werkzaamheden moet het stopcontact worden gesloten met behulp van de sluitfunctie. Voor het creëren klant applicatie Het is voldoende om een ​​lokaal stopcontact aan te sluiten met een externe (server) verbindingsfunctie. Het formaat van deze functie is:

int connect(int sock_fd, const struct *sockaddr serv_addr, socketlen_t addr_len);

Als er een fout is, retourneert de functie -1; de foutstatus kan worden verkregen met besturingssysteem. Bij succesvol werk retourneert 0. Een socket die eenmaal is aangesloten, kan meestal niet opnieuw worden aangesloten, zoals bijvoorbeeld gebeurt in het IP-protocol. De parameter sock_fd specificeert de socketdescriptor, de structuur serv_addr wijst het adres van het externe eindpunt toe, addr_len bevat de lengte van serv_addr (het type socketlen_t heeft een historische oorsprong, het is meestal hetzelfde als typ int). Meest belangrijke parameter in deze functie – het adres van het externe stopcontact. Uiteraard is dit niet hetzelfde voor verschillende protocollen, dus ik zal hier de adresstructuur alleen voor het IP (v4)-protocol beschrijven. Hiervoor wordt een gespecialiseerde structuur sockaddr_in gebruikt (deze moet direct naar het type sockaddr worden gecast als bel verbinden). De velden van deze structuur zien er als volgt uit:

struct sockaddr_in(

Sa_family_t sin_family; – definieert de adresfamilie, moet altijd AF_INET zijn

U_int16_t sin_port; – socketpoort in netwerkbytevolgorde

Structuur in_addr sin_adr; – structuur die het IP-adres bevat

Structuur die een IP-adres beschrijft:

struct in_addr(

U_int32_t s_adr; – socket-IP-adres in netwerkbytevolgorde

Let op de speciale bytevolgorde in alle integer-velden. Om het poortnummer naar de netwerkbytevolgorde te converteren, kunt u de htons-macro ( niet-ondertekende korte haven). Het is erg belangrijk om dit specifieke type geheel getal te gebruiken: een kort geheel getal zonder teken.

IPv4-adressen zijn onderverdeeld in single, broadcast (broadcast) en groep (multicast). Elk afzonderlijk adres verwijst naar één hostinterface, broadcastadressen verwijzen naar alle hosts in het netwerk en multicastadressen verwijzen naar alle hosts in een multicastgroep. De in_addr-structuur kan aan elk van deze adressen worden toegewezen. Maar voor socket-clients wordt in de overgrote meerderheid van de gevallen één enkel adres toegewezen. De uitzondering hierop is wanneer het nodig is om het geheel te scannen lokaal netwerk bij het zoeken naar een server, dan kunt u het uitzendadres gebruiken. Vervolgens moet de server hoogstwaarschijnlijk zijn echte IP-adres melden en moet de socket voor verdere gegevensoverdracht erop worden aangesloten. Er vindt geen gegevensoverdracht via broadcastadressen plaats goed idee, omdat niet bekend is welke server het verzoek verwerkt. Daarom kunnen momenteel verbindingsgerichte sockets slechts enkele adressen gebruiken. Voor socket-servers die naar adressen luisteren is de situatie anders: hier is het toegestaan ​​om broadcast-adressen te gebruiken om onmiddellijk te reageren op het verzoek van de client om de locatie van de server. Maar eerst dingen eerst. Zoals je hebt gemerkt, wordt het IP-adresveld in de sockaddr_in-structuur weergegeven als een lang geheel getal zonder teken, en zijn we gewend aan adressen in x.x.x.x-indeling (172.16.163.89) of in tekenindeling (myhost.com). Om de eerste te converteren, gebruikt u de functie inet_addr (const char *ip_addr), en voor de tweede de functie gethostbyname (const char *host). Laten we ze allebei bekijken:

u_int32_t inet_addr(const char *ip_addr)

– retourneert een onmiddellijk geheel getal dat geschikt is voor gebruik in de sockaddr_in-structuur op het IP-adres dat eraan wordt doorgegeven in het x.x.x.x-formaat. Als er een fout optreedt, wordt INADDR_NONE geretourneerd.

struct HOSTENT* gethostbyname(const char *hostnaam)

– retourneert de structuur van informatie over de host op basis van zijn naam. Indien dit niet lukt, wordt NULL geretourneerd. Het zoeken naar een naam vindt eerst plaats in hosts-bestand en vervolgens naar DNS. De HOSTENT-structuur biedt informatie over de vereiste host. Van al zijn velden is het veld char **h_addr_list het meest significant, dat een lijst met IP-adressen voor een bepaalde host vertegenwoordigt. Meestal wordt h_addr_list gebruikt, dat het eerste IP-adres van de host vertegenwoordigt, maar u kunt hiervoor ook de uitdrukking h_addr gebruiken. Na het uitvoeren van de functie gethostbyname bevat de lijst h_addr_list van de HOSTENT-structuur eenvoudige symbolische IP-adressen, dus u moet bovendien de functie inet_addr gebruiken om naar het sockaddr_in-formaat te converteren.

We hebben dus de client-socket met de server-socket verbonden met behulp van de connect-functie. U kunt dan de functies voor gegevensoverdracht gebruiken. Hiervoor kunt u beide gebruiken standaard kenmerken low-level I/O voor bestanden, aangezien het in essentie om een ​​socket gaat bestandsbeschrijving. Helaas kunnen de functies van werken op laag niveau met bestanden variëren voor verschillende besturingssystemen, dus u moet de handleiding van uw besturingssysteem raadplegen. Houd er rekening mee dat de netwerkcommunicatie kan eindigen met een SIGPIPE-signaal en dat de lees-/schrijffuncties een fout retourneren. U moet er altijd aan denken om op fouten te controleren. Bovendien mag u niet vergeten dat de gegevensoverdracht via het netwerk erg traag kan zijn en dat de invoer-/uitvoerfuncties synchroon zijn, wat aanzienlijke vertragingen in het programma kan veroorzaken.

Om gegevens tussen sockets over te dragen, zijn er speciale functies, gemeenschappelijk voor alle besturingssystemen, zijn de functies van de recv- en send-familie. Hun formaat lijkt erg op elkaar:

int send(int sockfd, void *data, size_t len, int flags);

– verzendt de databuffer.

int recv(int sockfd, void *data, size_t len, int flags);

– accepteert een databuffer.

Het eerste argument is de socketdescriptor, het tweede is een verwijzing naar de gegevens die moeten worden overgedragen, het derde is de bufferlengte en het vierde zijn de vlaggen. Als dit lukt, wordt het aantal overgedragen bytes geretourneerd; als dit niet lukt, wordt er een negatieve foutcode geretourneerd. Met vlaggen kunt u de transmissieparameters wijzigen (bijvoorbeeld de asynchrone bedrijfsmodus inschakelen), maar voor de meeste taken is het voldoende om het vlaggenveld nul te laten staan. normale modus overdrachten. Bij het verzenden of ontvangen van gegevens blokkeren functies de uitvoering van het programma voordat de gehele buffer is verzonden. En wanneer het tcp/ip-protocol wordt gebruikt, moet er een reactie komen van de externe socket die aangeeft dat de gegevens succesvol zijn verzonden of ontvangen, anders wordt het pakket opnieuw verzonden. Houd bij het verzenden van gegevens rekening met de netwerk-MTU ( maximumgrootte frame dat tegelijk wordt verzonden). Voor verschillende netwerken het kan bijvoorbeeld anders zijn Ethernet-netwerken het is gelijk aan 1500.

Voor de volledigheid zal ik dus het eenvoudigste voorbeeld geven van een C-programma dat een socketclient implementeert:

#erbij betrekken /* Standaardbibliotheken sockets voor Linux */

#erbij betrekken /* Gebruik voor Windows OS #include */

#erbij betrekken

int hoofd())(

Int sockfd = -1;

/* Socket-descriptor */

Char-buf;

Char s = "Klant klaar";

HOSTENT *h = NULL;

Sockaddr_in adres;

Niet-ondertekende korte poort = 80;

Adres.sin_family = AF_INET;

/* Maak een socket */

Als(sockfd == -1)

/* Of de socket is aangemaakt */

Retour -1;

H = gethostbyname("www.mijnhost.com");

/* Haal het hostadres op */

Als(h == NULL)

/* Bestaat er zo'n adres? */

Retour -1;

Addr.sin_addr.s_addr = inet_addr(h->h_addr_lijst);

/* Converteer het IP-adres naar een getal */

If(connect(sockfd, (sockaddr*) &adr, groottevan(adr)))

/* Probeert verbinding te maken met een extern stopcontact */

Retour -1;

/* Verbinding was succesvol - ga verder */

Als(verzenden(sockfd, s, groottevan(s), 0)< 0)

Retour -1;

If(recv(sockfd, buf, groottevan(buf), 0)< 0)

Retour -1;

Sluiten(sockfd);

/* Sluit de socket */

/* Voor Windows wordt de closesocket(s)-functie gebruikt */

Retour 0;

Zie je, het gebruik van stopcontacten is niet zo moeilijk. IN servertoepassingen Er worden totaal verschillende principes van het werken met stopcontacten gebruikt. Eerst wordt een socket gemaakt en vervolgens toegewezen lokaal adres Met behulp van de bindfunctie kunt u een broadcastadres aan de socket toewijzen. Vervolgens begint de luisterfunctie naar het adres te luisteren, verbindingsverzoeken worden in een wachtrij geplaatst. Dat wil zeggen dat de luisterfunctie de socket initialiseert om berichten te ontvangen. Hierna moet u de accept-functie gebruiken, die een nieuwe socket retourneert die al aan de client is gekoppeld. Het is gebruikelijk dat servers met korte tussenpozen veel verbindingen accepteren. Daarom moet u voortdurend de wachtrij met inkomende verbindingen controleren met behulp van de acceptfunctie. Om dit gedrag te organiseren, nemen ze meestal hun toevlucht tot de mogelijkheden van het besturingssysteem. Voor Windows OS wordt vaker een multi-threaded versie van de serverbediening gebruikt; bij het accepteren van de verbinding wordt er een nieuwe thread in het programma gemaakt, die de socket verwerkt. In *nix-systemen wordt vaker gebruik gemaakt van het creëren van een onderliggend proces door de fork-functie. Tegelijkertijd worden de overheadkosten verlaagd doordat er daadwerkelijk een kopie van het proces plaatsvindt bestandssysteem proc. In dit geval zijn alle variabelen van het onderliggende proces hetzelfde als die van het bovenliggende proces. En het kindproces kan de inkomende verbinding direct afhandelen. Het ouderproces blijft luisteren. Houd er rekening mee dat de poorten met de nummers 1 tot en met 1024 bevoorrecht zijn en dat het niet altijd mogelijk is om ernaar te luisteren. Nog één punt: je kunt niet twee verschillende stopcontacten hebben die naar dezelfde poort op hetzelfde adres luisteren! Laten we eerst eens kijken naar de formaten van de bovenstaande functies voor het maken van een serversocket:

int bind(int sockfd, const struct *sockaddr, socklen_t addr_len);

– wijst een lokaal adres toe aan de socket zodat deze inkomende verbindingen kan accepteren. Voor een adres kunt u de constante INADDR_ANY gebruiken, waarmee u inkomende verbindingen van alle adressen in een bepaald subnet kunt accepteren. Het functieformaat is vergelijkbaar met connect. Retourneert een negatieve waarde in geval van een fout.

int luister(int sockfd, int achterstand);

– de functie creëert een wachtrij van inkomende sockets (het aantal verbindingen wordt bepaald door de backlog-parameter, dit mag het aantal SOMAXCONN, dat afhankelijk is van het besturingssysteem, niet overschrijden). Nadat u de wachtrij heeft aangemaakt, kunt u via de acceptatiefunctie wachten op een verbinding. Sockets blokkeren meestal, dus de uitvoering van het programma wordt opgeschort totdat de verbinding is geaccepteerd. In geval van een fout wordt -1 geretourneerd.

int accept(int sockfd, struct *sockaddr, socklen_t addr_len)

– functie wacht inkomende verbinding(of haalt het op uit de verbindingswachtrij) en retourneert een nieuwe socket die al aan de externe client is gekoppeld. In dit geval blijft de originele socket sockfd ongewijzigd. De sockaddr-structuur is gevuld met waarden uit de externe socket. In geval van een fout wordt -1 geretourneerd.

Hier is een voorbeeld van een eenvoudige socketserver die de fork-functie gebruikt om een ​​onderliggend proces te maken dat de verbinding afhandelt:

int hoofd())(

Pid_t pid;

/* Onderliggend proces-ID */

Int sockfd = -1;

/* Beweeg naar het stopcontact om mee te luisteren */

Ints = -1;

/* Socketdescriptor ontvangen */

Char-buf;

/* Verwijzing naar de buffer om te ontvangen */

Char str = "Server gereed";

/* Tekenreeks die naar de server moet worden verzonden */

HOSTENT *h = NULL;

/* Structuur voor het verkrijgen van een IP-adres */

Sockaddr_in adres;

/* Tcp/ip-protocolstructuur */

Sockaddr_in raddr;

Niet-ondertekende korte poort = 80;

/* Vul de velden van de structuur in: */

Adres.sin_family = AF_INET;

Addr.sin_port = htons(poort);

sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

/* Maak een socket */

Als(sockfd == -1)

/* Of de socket is aangemaakt */

Retour -1;

Addr.sin_addr.s_addr = INADDR_ANY;

/* Luister op alle adressen */

If(bind(sockfd, (sockaddr*) &adr, groottevan(adr)))

/* Wijs een lokaal adres toe aan de socket */

Retour -1;

Als(luister(sockfd, 1))

/* Begin te luisteren */

Retour -1;

S = accepteren(sockfd, (sockaddr *) &raddr, groottevan(raddr));

/* Verbinding accepteren */

Pid = vork();

/* breng een onderliggend proces voort */

Als(pid == 0)(

/* Dit is een onderliggend proces */

Als(recv(s, buf, groottevan(buf), 0)< 0)

/* Stuur de string s naar de externe socket */

Retour -1;

Als(verzenden(s, str, groottevan(str), 0)< 0)

/* Ontvang een antwoord van de externe server */

Retour -1;

Printf("Ontvangen tekenreeks was: %s", buf);

/* Afdrukbuffer naar standaarduitvoer */

Sluit(en);

/* Sluit de socket */

Retour 0;

/* Het onderliggende proces afsluiten */

Sluiten(sockfd);

/* Sluit de luisteraansluiting */

Retour 0;

Wanneer u een thread maakt om een ​​socket te verwerken, raadpleeg dan de handleiding van het besturingssysteem verschillende systemen De aanroep van de functie voor het maken van threads kan aanzienlijk variëren. Maar de verwerkingsprincipes voor de draad blijven hetzelfde. De verwerkingsfunctie hoeft alleen een socket-pointer als argument door te geven (meestal kan elk gegevenstype in void *-formaat worden doorgegeven aan de stream-functie, waarvoor het gebruik van een type cast vereist is).

Belangrijke opmerking voor Windows-systemen. Ik heb gemerkt dat het socketsysteem niet werkt zonder de WSAStartup-functie te gebruiken om de socketbibliotheek te initialiseren. Een Windows-socketprogramma zou als volgt moeten starten:

WSADATA wsaData;

WSAStartup(0x0101, &wsaData);

En schrijf het volgende wanneer u het programma afsluit:

WSAOpschonen();

Omdat de meeste socketbewerkingen blokkeren, moet de uitvoering van taken vaak worden onderbroken door te wachten op synchronisatie. Daarom vermijden *nix-achtige systemen vaak het blokkeren van de console door een speciaal type programma te maken: een daemon. De daemon hoort niet bij virtuele consoles en treedt op wanneer een onderliggend proces een fork aanroept, en het bovenliggende proces eindigt vóór het tweede kind (en dit gebeurt altijd op deze manier). Hierna wordt het tweede onderliggende proces het hoofdproces en blokkeert het de console niet. Hier is een voorbeeld van dit programmagedrag:

pid = vork();

/* Maak het eerste onderliggende proces */

als (pid<0){

/* Fout bij aanroepen van vork */

Printf("Forkingsfout:) ");

Afsluiten(-1);

)anders als (pid !=0)(

/* Dit is de eerste ouder! */

Printf("Dit is een vader 1 ");

)anders(

Pid = vork();

/* Het werk van de 1e ouder eindigt */

/* En we noemen een ander kindproces */

Als (pid<0){

Printf("Forking-fout:) ");

Afsluiten(-1);

)anders als (pid !=0)(

/* Dit is de tweede ouder */

Printf("Dit is een vader 2 ");

)anders(

/* En dit is hetzelfde tweede onderliggende proces */

/* Schakel over naar de "standaard" daemon-modus */

Setid();

/* Voer de daemon uit in superuser-modus */

Umasker(0); /* Standaard bestandsmasker */

Chdir("/"); /* Ga naar de hoofdmap */

Daemoncode(); /* Eigenlijk de daemoncode zelf */

/* Wanneer u een daemon afsplitst, verschijnt er een onderliggende daemon */

Dat is alles. Ik denk dat dit voldoende is om een ​​eenvoudige socketserver te maken.

netwerksysteemoproepinterface ( stopcontact(), binden(), recvvan(), verzenden naar() enz.) in het UNIX-besturingssysteem kunnen worden gebruikt voor andere protocolstapels (en voor de protocollen die daaronder liggen transport laag).

Wanneer u een socket maakt, moet u het type ervan nauwkeurig opgeven. Deze specificatie wordt gemaakt met behulp van drie oproepparameters stopcontact(). De eerste parameter specificeert tot welke protocolfamilie de gemaakte socket behoort, en de tweede en derde parameter definiëren het specifieke protocol binnen die familie.

De tweede parameter wordt gebruikt om het type interface te specificeren voor het werken met de socket: zal het een stream-socket zijn, een socket voor het werken met datagrammen, of een andere. De derde parameter specificeert het protocol voor het gegeven interfacetype. IN TCP/IP-protocolstack Er is slechts één protocol voor streamsockets - TCP en slechts één protocol voor datagramsockets - UDP, dus voor TCP/IP-transportprotocollen wordt de derde parameter genegeerd.

In andere protocolstapels kunnen er meerdere protocollen zijn met hetzelfde type interface, bijvoorbeeld datagram-interfaces, die verschillen in de mate van betrouwbaarheid.

Voor TCP/IP-transportprotocollen zullen we altijd de vooraf gedefinieerde constante AF_INET (Adresfamilie - Internet) of het synoniem PF_INET (Protocolfamilie - Internet) als eerste parameter specificeren.

De tweede parameter heeft de vooraf gedefinieerde waarden SOCK_STREAM voor streamsockets en SOCK_DGRAM voor datagramsockets.

Omdat in ons geval geen rekening wordt gehouden met de derde parameter, zullen we de waarde 0 daarin vervangen.

Er wordt een link geplaatst naar informatie over de aangemaakte socket tabel met geopende procesbestanden vergelijkbaar met hoe het werd gedaan voor pitten en FIFO's (zie workshop 5). De systeemaanroep retourneert aan de gebruiker een bestandsdescriptor die overeenkomt met het ingevulde tabelelement, dat we voortaan een socketdescriptor zullen noemen. Deze methode voor het opslaan van informatie over een socket maakt het ten eerste mogelijk dat onderliggende processen deze overerven van bovenliggende processen, en ten tweede dat voor sockets enkele van de systeemaanroepen kunnen worden gebruikt die ons al bekend zijn door het werken met pips en FIFO's: close( ) , lezen schrijven() .

Systeemoproep om een ​​socket te maken

Prototype van systeemoproep

#erbij betrekken #erbij betrekken int socket(int-domein, int-type, int-protocol);

Beschrijving van de systeemoproep

Systeemoproep stopcontact dient om een ​​virtueel communicatieknooppunt in het besturingssysteem te creëren. Deze beschrijving is geen volledige beschrijving van de systeemaanroep, maar is alleen bedoeld voor gebruik in onze cursus. Zie de UNIX-handleiding voor volledige informatie.

De domeinparameter definieert de protocolfamilie waarbinnen informatie wordt overgedragen. We zullen slechts twee van deze families beschouwen uit een aantal bestaande families. Er zijn vooraf gedefinieerde parameterwaarden voor:

  • PF_INET – voor TCP/IP-protocolfamilie ;
  • PF_UNIX – voor de familie van interne UNIX-protocollen, ook wel bekend als het UNIX-domein.

De parameter type bepaalt de semantiek van informatie-uitwisseling: of communicatie zal plaatsvinden via berichten (datagrammen), door het tot stand brengen van virtuele verbinding of op een andere manier. We zullen slechts twee manieren gebruiken om informatie uit te wisselen met vooraf gedefinieerde waarden voor de parameter type:

  • SOCK_STREAM – voor communicatie via vestiging virtuele verbinding ;
  • SOCK_DGRAM – voor het uitwisselen van informatie via berichten.

De protocolparameter specificeert het specifieke protocol voor de geselecteerde protocolfamilie en communicatiemethode. Het maakt alleen uit als er meerdere van dergelijke protocollen zijn. In ons geval bepalen de protocolfamilie en het type informatie-uitwisseling op unieke wijze het protocol. Daarom nemen we aan dat deze parameter gelijk is aan 0.

Winstwaarde

Indien succesvol, retourneert de systeemaanroep een bestandsdescriptor (een waarde groter dan of gelijk aan 0), die bij alle verdere netwerkoproepen zal worden gebruikt als verwijzing naar het aangemaakte communicatieknooppunt. Als er een fout optreedt, wordt er een negatieve waarde geretourneerd.

Socket-adressen. Het socketadres instellen. bind() systeemaanroep

Nadat de socket is gemaakt, moet u het adres ervan configureren. Hiervoor wordt de systeemoproep gebruikt binden(). De eerste parameter van de oproep moet een socketdescriptor bevatten waarvoor het adres wordt geconfigureerd. De tweede en derde parameters specificeren dit adres.

De tweede parameter moet een verwijzing zijn naar een struct sockaddr-structuur die de externe en lokale delen van het volledige adres bevat.

Pointers van het type struct sockaddr * zijn te vinden in veel netwerksysteemaanroepen; ze worden gebruikt om informatie over te brengen over aan welk adres een socket gebonden is of zou moeten zijn. Laten we dit gegevenstype eens nader bekijken. De structuurstructuur sockaddr wordt beschreven in het bestand op de volgende manier:

struct sockaddr (korte sa_family; char sa_data; );

Deze samenstelling van de structuur is te wijten aan het feit dat netwerksysteemoproepen kunnen worden gebruikt voor verschillende families van protocollen, die adresruimten verschillend definiëren voor externe en lokale socketadressen. In wezen is dit gegevenstype slechts een algemeen sjabloon voor het doorgeven van gegevensstructuren die specifiek zijn voor elke protocolfamilie aan systeemaanroepen. Het enige gemeenschappelijke element van deze structuren is het korte sa_family-veld (dat uiteraard verschillende namen kan hebben in verschillende structuren; het enige belangrijke is dat ze allemaal van hetzelfde type zijn en de eerste elementen van hun structuren zijn) om de protocol familie. De systeemoproep analyseert de inhoud van dit veld om de samenstelling van de binnenkomende informatie nauwkeurig te bepalen.

Werken met familie van TCP/IP-protocollen we gebruiken het socketadres van het volgende type, beschreven in het bestand :

struct sockaddr _in( short sin_family; /* Geselecteerde protocolfamilie is altijd AF_INET */ unsigned short sin_port; /* 16-bits poortnummer in netwerkbytevolgorde */ struct in_addr sin_addr; /* Netwerkinterfaceadres */ char sin_zero; /* Dit veld wordt niet gebruikt, maar moet altijd gevuld zijn met nullen */ );

Het eerste element van de structuur - sin_family specificeert protocol familie. We zullen daarin de vooraf gedefinieerde constante AF_INET invoeren, die ons al bekend is (zie de vorige sectie).

Het afgelegen deel van het volledige adres - het IP-adres - is opgenomen in een structuur van het type struct in_addr, die we tegenkwamen in de sectie "IP-adresvertaalfuncties" inet_ntoa(), inet_aton() " .

Om het poortnummer aan te geven wordt gebruik gemaakt van het structuurelement sin_port, waarin het poortnummer moet worden opgeslagen in netwerkbytevolgorde. Er zijn twee mogelijkheden om het poortnummer op te geven: een vaste poort naar wens van de gebruiker en een poort die willekeurig wordt toegewezen besturingssysteem. De eerste optie vereist het specificeren van een positief, vooraf bekend nummer als poortnummer en wordt voor het UDP-protocol meestal gebruikt bij het instellen van socketadressen en bij het verzenden van informatie met behulp van een systeemaanroep. verzenden naar()(zie volgende sectie). Voor de tweede optie moet in dit geval 0 als poortnummer worden opgegeven besturingssysteem zelf bindt de socket aan een vrij poortnummer. Deze methode wordt meestal gebruikt bij het instellen van sockets voor clientprogramma's, waarbij de programmeur het exacte poortnummer niet vooraf hoeft te weten.

Welk poortnummer kan een gebruiker gebruiken in een vaste configuratie? Poortnummers 1 tot en met 1023 kunnen alleen aan sockets worden toegewezen door processen die worden uitgevoerd met systeembeheerdersrechten. Normaal gesproken worden deze nummers toegewezen aan systeemnetwerkservices, ongeacht het gebruikte type besturingssysteem, zodat clientprogramma's van gebruikers altijd service kunnen aanvragen vanaf dezelfde lokale adressen. Er zijn ook een aantal veelgebruikte netwerkprogramma's die processen uitvoeren met normale gebruikersrechten (bijvoorbeeld X-Windows). Voor dergelijke programma's van de Internet Corporation voor het toekennen van namen en nummers (

Wilt u een netwerkprogramma in Java ontwikkelen - een speeltje, een chatroom of beide... U heeft het juiste artikel gevonden - hier kunt u kennis maken met de spannende wereld van stopcontacten in Java. Na het lezen van dit artikel zul je het licht aan het einde van de tunnel zien: het doel van sockets en hoe je een eenvoudig programma kunt ontwikkelen met behulp van sockets in de programmeertaal Java zal duidelijk worden.

Wat is een stopcontact?

Tegenwoordig is het gebruik van instant messenger-clients een onmisbaar hulpmiddel geworden voor alle internetgebruikers. Er zijn veel verschillende protocollen en clients (MSN, ICQ, Skype, etc.) waar iedereen van heeft gehoord en die we allemaal dagelijks gebruiken. Maar niet iedereen weet wat de basis is van hun robots - daar is dit artikel voor geschreven. Stel dat u een van de instant messaging-clients op uw computer hebt geïnstalleerd. Nadat u het hebt gestart en uw gebruikersnaam en wachtwoord hebt ingevoerd, probeert het programma verbinding te maken met de server. Wat betekent het woord ‘verbinden’ precies?

Elke computer in het netwerk heeft een IP-adres. Dit adres is vergelijkbaar met uw thuisadres: het identificeert uw computer op unieke wijze en zorgt ervoor dat anderen met uw computer kunnen communiceren. We zullen niet ingaan op de details van IP-adressen, aangezien dit artikel er niet over gaat. Ik wil alleen opmerken dat een IP-adres een reeks cijfers is, gescheiden door punten (bijvoorbeeld 64.104.137.158). Hoewel er een andere manier is om computers op het netwerk te identificeren: een domeinnaam, die handiger is en een computer duidelijker identificeert dan een eenvoudige reeks cijfers (bijvoorbeeld www.site). Er zijn speciale computers op internet die een domeinnaam omzetten in een IP-adres en omgekeerd.

Op één computer kunnen meerdere programma's parallel worden uitgevoerd. Laten we zeggen dat er 10 programma's op uw computer draaien en dat ze allemaal wachten tot andere computers met hen communiceren. Je kunt het je zo voorstellen: jullie zitten met z'n tienen in een groot kantoor met 1 telefoon, en iedereen wacht op telefoontjes van zijn eigen klanten. Hoe ga je dit oplossen? Je kunt uiteraard een verantwoordelijke medewerker aanstellen, en hij brengt de telefoon naar de corresponderende persoon die gebeld wordt, maar dan kunnen andere klanten andere mensen niet bereiken. Bovendien is het erg moeilijk en lastig om iemand te hebben die verantwoordelijk is voor het doorsturen van oproepen naar de juiste mensen. Je raadt misschien al waar ik hiermee naartoe wil: als al deze programma's die op dezelfde computer draaien hun klanten trots vragen om contact met hen op te nemen op een specifiek IP-adres, dan zullen hun klanten niet blij zijn. Het idee is... om voor elk programma een apart IP-adres te hebben, toch? NIET WAAR! De essentie van de vraag klopt niet: het is hetzelfde als vragen naar een apart kantoor voor ieder van jullie. Nou, dan... zijn aparte telefoonnummers misschien genoeg? JA! In netwerkjargon worden "individuele telefoonnummers" poorten genoemd. Een poort is slechts een nummer, en elk programma dat op een bepaalde computer draait, kan een uniek poortnummer kiezen om zichzelf voor de buitenwereld te identificeren. ONTHOUD: u kunt deze poorten niet vinden tussen de computerhardware (probeer er niet eens naar te zoeken). Deze cijfers zijn logisch. Nu is alles duidelijk: er is een IP-adres waarmee andere computers een specifieke computer op het netwerk kunnen herkennen, en een poortnummer dat een bepaald programma identificeert dat op de computer draait. Ook wordt duidelijk dat twee programma's op verschillende computers dezelfde poort kunnen gebruiken (twee huizen in verschillende straten kunnen ook hetzelfde nummer hebben, of niet?). Nou, we zijn er bijna, om je een beetje bang te maken, laten we de formule afleiden:

IP-adres = identificeert op unieke wijze een computer in een netwerk.
Poortnummer = identificeert op unieke wijze het programma dat op de betreffende computer draait.

Als we de bovenstaande vergelijkingen bij elkaar optellen, krijgen we:

IP-adres + poortnummer = _____

Met andere woorden:

Identificeert op unieke wijze een programma op een netwerk. Als je dit zelf hebt bedacht, zijn mijn inspanningen niet tevergeefs geweest. Zo niet, lees dan het hele artikel opnieuw vanaf het begin of gebruik Google om een ​​beter artikel te vinden. _____ – dit is... STOPCONTACT!

Samenvattend is een socket een combinatie van een IP-adres en een poort. Met een socketadres kunnen andere computers in het netwerk een specifiek programma vinden dat op een specifieke computer wordt uitgevoerd. U kunt het socketadres als volgt weergeven: 64.104.137.58:80, waarbij 64.104.137.58 het IP-adres is en 80 de poort.

Hoe programmeren met sockets?

Genoeg over de theorie, laten we tot actie overgaan. Laten we een zeer eenvoudige en visuele code in Java ontwikkelen die de mogelijkheden van het gebruik van sockets zal demonstreren. Laten we proberen de volgende lijst met acties te implementeren:

1) Het ene Java-programma zal proberen te communiceren met een ander Java-programma (dat wanhopig wacht tot iemand er contact mee opneemt). Laten we het eerste programma de Client noemen, en het tweede de Server.

2) Nadat er succesvol verbinding is gemaakt met de server, wacht de client op gegevensinvoer van u en stuurt de tekst naar de server.

3) Het serverprogramma stuurt die tekst terug naar de client (om aan te tonen dat het zelfs zo'n nuttige actie kan uitvoeren).

4) De client toont de tekst die van de server is ontvangen om u de mening van de server over u te laten zien. Klaar om te beginnen met ontwikkelen? Laten we beginnen. Ik wil u er wel op wijzen dat ik u niet helemaal opnieuw Java-programmeren zal leren, maar alleen de code zal uitleggen die betrekking heeft op sockets. Maak 2 nieuwe Java-programma's en noem deze Server.java en Client.java. Ik heb de onderstaande code verstrekt, wees niet ongerust, ik zal alles uitleggen.


importeer java.net.*;
java.io.* importeren;
openbare klasse Server (
int-poort = 6666; // willekeurige poort (kan elk nummer zijn van 1025 tot 65535)
poging (
ServerSocket ss = nieuwe ServerSocket(poort); // maak een server socket en bind deze aan de bovenstaande poort
System.out.println("Wachten op een client...");

Socket-aansluiting = ss.accept(); // dwing de server om te wachten op verbindingen en een bericht weer te geven wanneer iemand contact opneemt met de server
System.out.println("Ik heb een klant :) ... Eindelijk heeft iemand me door de hele omslag heen!");
Systeem.out.println();

// We nemen de invoer- en uitvoerstromen van de socket, nu kunnen we gegevens ontvangen en verzenden naar de klant.


Stringlijn = nul;
terwijl(waar) (
regel = in.readUTF(); // wacht tot de client een reeks tekst verzendt.
System.out.println("De domme client stuurde me zojuist deze regel: " + regel);
System.out.println("Ik stuur het terug...");
uit.writeUTF(regel); // stuur dezelfde tekstreeks terug naar de client.
System.out.println("Wachten op de volgende regel...");
Systeem.out.println();
}
) catch(Uitzondering x) ( x.printStackTrace(); )
}
}

Importeer java.net.*;
java.io.* importeren;

openbare klasse Klant (
openbare statische leegte hoofd(String ar) (
int serverpoort = 6666; // hier moet u de poort opgeven waaraan de server is gebonden.
Stringadres = "127.0.0.1"; // dit is het IP-adres van de computer waarop ons serverprogramma draait.
// Hier is het adres van dezelfde computer waarop de client zal worden uitgevoerd.

Poging (
InetAddress ipAddress = InetAddress.getByName(adres); // maak een object dat het hierboven beschreven IP-adres weergeeft.
System.out.println("Heeft iemand van jullie gehoord van een socket met IP-adres " + adres + " en poort " + serverPort + "?");
Socket socket = nieuwe Socket(ipAddress, serverPort); // maak een socket met behulp van het IP-adres en de poort van de server.
System.out.println("Ja! Ik heb het programma net te pakken gekregen.");

// We nemen de invoer- en uitvoerstromen van de socket, nu kunnen we gegevens ontvangen en verzenden door de klant.
InputStream sin = socket.getInputStream();
OutputStream zuid = socket.getOutputStream();

// Converteer streams naar een ander type om het verwerken van sms-berichten gemakkelijker te maken.
DataInputStream in = nieuwe DataInputStream(sin);
DataOutputStream uit = nieuwe DataOutputStream(sout);

// Maak een stream om vanaf het toetsenbord te lezen.
BufferedReader-toetsenbord = nieuwe BufferedReader (nieuwe InputStreamReader (System.in));
Stringlijn = nul;
System.out.println("Typ iets in en druk op enter. Zal het naar de server sturen en je vertellen wat het denkt.");
Systeem.out.println();

Terwijl (waar) (
line = toetsenbord.readLine(); // wacht tot de gebruiker iets invoert en druk op de Enter-knop.
System.out.println("Deze regel naar de server verzenden...");
uit.writeUTF(regel); // stuur de ingevoerde tekststring naar de server.
uit.flush(); // forceer de thread om het verzenden van gegevens te voltooien.
regel = in.readUTF(); // wacht tot de server een tekstreeks verzendt.
System.out.println("De server was erg beleefd. Hij stuurde me dit: " + line);
System.out.println("Het lijkt erop dat de server tevreden over ons is. Ga je gang en voer meer regels in.");
Systeem.out.println();
}
) vangen (Uitzondering x) (
x.printStackTrace();
}
}
}

Laten we nu de code compileren:

Javac Server.java Client.java

Open twee opdrachtvensters (DOS). In één venster voeren we in:

Java-server

en in de andere:

Java-client

Zeker in die volgorde.

Voer nu een regel tekst in het venster waarin de client draait en druk op de Enter-knop. Bekijk de twee vensters en zie wat er gebeurt. Druk ten slotte op Ctrl-C om het programma te stoppen.

Uitleg van socketcode

Laten we nu wat dieper in de code duiken. U krijgt al enkele ideeën door de opmerkingen te lezen, maar laten we een paar cruciale coderegels analyseren.

Beschouw het volgende stukje servercode:

ServerSocket ss = nieuwe ServerSocket(poort);
Socket-aansluiting = ss.accept();

De klasse ServerSocket verschilt enigszins van de klasse Socket. De Socket-klasse is een socket. Het belangrijkste verschil tussen ServerSocket is dat het het programma kan dwingen te wachten op verbindingen van clients. Wanneer u het maakt, moet u de poort opgeven waarmee het zal werken en de accept() -methode aanroepen. Deze methode zorgt ervoor dat het programma wacht op verbindingen op de opgegeven poort. De programma-uitvoering blijft op dit punt hangen totdat de client verbinding maakt. Na een succesvolle verbinding door de client wordt een normaal Socket-object aangemaakt, waarmee u alle bestaande bewerkingen op de socket kunt uitvoeren. Houd er ook rekening mee dat dit Socket-object het andere uiteinde van de verbinding vertegenwoordigt. Als je data naar een client wilt sturen, kun je hiervoor geen eigen socket gebruiken.

Vervolgens kijken we naar de Socket-klasse. U kunt een Socket-object maken door een IP-adres en poort op te geven. U kunt de klasse InetAddress gebruiken om het IP-adres weer te geven (dit heeft de voorkeur). Gebruik de volgende methode om een ​​InetAddress-object te maken:

InetAddress ipAddress = InetAddress.getByName(adres);

Merk op dat we in ons programma het adres 127.0.0.1 gebruikten. Dit speciale adres wordt een loopback-adres genoemd; het verwijst eenvoudigweg naar de lokale computer. Als u de client en server op verschillende computers wilt gebruiken, moet u het overeenkomstige server-IP-adres gebruiken.

Nadat we het InetAddress hebben aangemaakt, kunnen we de Socket maken:

Socket socket = nieuwe Socket(ipAddress, serverPort);

Nadat u een Socket-object hebt gemaakt, kunt u de invoer- en uitvoerstromen van de socket gebruiken. Met de invoerstroom kunt u vanuit de socket lezen, en met de uitvoerstroom kunt u naar de socket schrijven.

InputStream sin = socket.getInputStream();
OutputStream zuid = socket.getOutputStream();

Met de volgende regels worden de streams eenvoudigweg omgezet naar andere streamtypen. Hierna zal het voor ons gemakkelijker zijn om met String-objecten te werken. Deze code doet niets met het netwerk.

DataInputStream in = nieuwe DataInputStream(sin);
DataOutputStream uit = nieuwe DataOutputStream(sout);

Al het andere is heel eenvoudig: eenvoudige manipulaties met draadobjecten (geen stopcontacten). U kunt uw favoriete streams gebruiken, uw favoriete methoden aanroepen en ervoor zorgen dat de gegevens naar de andere kant worden doorgegeven. Als je niet veel kennis hebt over het gebruik van threads, raad ik je aan een artikel hierover te zoeken en te lezen.