Berkeley-uttag

Berkeley Sockets  är ett applikationsprogrammeringsgränssnitt (API) som är ett bibliotek för att utveckla applikationer i C-språket med stöd för inter-process communication (IPC), som ofta används i datornätverk .

Berkeley -sockets (även kända som BSD- socket API ) dök först upp som ett API i 4.1BSD Unix - operativsystemet (släppt 1982) [1] . Det var dock inte förrän 1989 som UC Berkeley kunde börja släppa versioner av operativsystemet och nätverksbiblioteket utan AT&T -licensrestriktioner för upphovsrättsskyddade Unix.

Berkeley Sockets API har bildat de facto abstraktionsstandarden för nätverkssockets. De flesta andra programmeringsspråk använder ett gränssnitt som liknar C API.

Det STREAMS-baserade Transport Layer Interface (TLI) API är ett alternativ till socket API. Berkeley Sockets API är dock starkt dominerat i popularitet och antal implementeringar.

Berkeley socket interface

Berkeley-socket-gränssnittet  är ett API som tillåter kommunikation mellan datorer eller mellan processer på samma dator. Denna teknik kan fungera med många olika I/O-enheter och drivrutiner , även om deras stöd beror på implementeringen av operativsystemet . Denna implementering av gränssnittet är grunden för TCP/IP , på grund av vilket det anses vara en av de grundläggande teknologierna som Internet är baserat på . Socket-teknologin utvecklades först vid UC Berkeley för användning på UNIX- system. Alla moderna operativsystem har en viss implementering av Berkeley-socket-gränssnittet, eftersom detta har blivit standardgränssnittet för att ansluta till Internet.

Programmerare kan komma åt socket-gränssnittet på tre olika nivåer, varav den mest kraftfulla och grundläggande är råsockets- nivån . Ett ganska litet antal applikationer behöver begränsa kontrollen över de utgående anslutningarna de implementerar, så stöd för rå socket var tänkt att endast vara tillgängligt på datorer som används för utveckling baserade på Internet-relaterad teknik. Därefter har de flesta operativsystem implementerat stöd för dem, inklusive Windows .

Rubrikfiler

Berkeley Sockets Software Library innehåller många relaterade rubrikfiler.

<sys/socket.h> Grundläggande BSD-uttagsfunktioner och datastrukturer. <netinet/in.h> Adress/protokollfamiljerna PF_INET och PF_INET6. De används ofta på Internet och inkluderar IP-adresser samt TCP- och UDP-portnummer. <sys/un.h> PF_UNIX/PF_LOCAL adressfamilj. Används för lokal kommunikation mellan program som körs på samma dator. Gäller inte datornätverk. <arpa/inet.h> Funktioner för att arbeta med numeriska IP-adresser. <netdb.h> Funktioner för att konvertera protokollnamn och värdnamn till numeriska adresser. Lokal data används på samma sätt som DNS.

Strukturer

struct sockaddr_in stSockAddr ; ... bind ( SocketFD ,( const struct sockaddr * ) & stSockAddr , sizeof ( struct sockaddr_in ));
  • sockaddr_in
  • sockaddr_in6
  • in_addr
  • in6_addr

Funktioner

socket()

socket()skapar en anslutningsändpunkt och returnerar ett handtag till . socket()tar tre argument:

  • domän , som anger protokollfamiljen för den socket som skapas. Denna parameter specificerar namnkonventioner och adressformat. Till exempel:
    • PF_INETför IPv4 -nätverksprotokoll eller
    • PF_INET6för IPv6 .
    • PF_UNIXför lokala uttag (med en fil).
  • typ (typ) en av:
    • SOCK_STREAMpålitlig strömorienterad tjänst (TCP) (tjänst) eller strömuttag
    • SOCK_DGRAMdatagramtjänst (UDP) eller datagramuttag
    • SOCK_SEQPACKETpålitlig seriell pakettjänst
    • SOCK_RAW En råsocket  är ett råprotokoll ovanpå nätverkslagret.
  • protokoll anger vilket transportprotokoll som ska användas. De vanligaste är IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP. Dessa protokoll är specificerade i <netinet/in.h>. Värdet " 0" kan användas för att välja ett standardprotokoll från den angivna familjen ( domain) och typen ( type).

Funktionen återkommer −1vid fel. Annars returnerar den ett heltal som representerar det tilldelade handtaget.

Prototyp #include <sys/types.h> #include <sys/socket.h> int - socket ( int -domän , int -typ , int - protokoll );

gethostbyname() och gethostbyaddr()

Funktionerna gethostbyname()och gethostbyaddr()returnerar en pekare till ett objekt av typen struct hostent som beskriver en internetvärd med namn respektive adress. Denna struktur innehåller antingen information mottagen från namnservern eller godtyckliga fält från en rad i /etc/hosts. Om den lokala namnservern inte körs, ser dessa rutiner i /etc/hosts. Funktionerna tar följande argument:

  • name , som anger namnet på värden. Till exempel: www.wikipedia.org
  • addr , som definierar en pekare till en struct in_addr som innehåller adressen till värden.
  • len , som anger längden i byte av addr .
  • typ , som anger typen av värdens adressområde. Till exempel: PF_INET

Funktionerna returnerar en NULL-pekare vid fel. I det här fallet kan ytterligare ett heltal h_errno kontrolleras för att upptäcka ett fel eller en ogiltig eller okänd värd. Annars returneras en giltig struct hostent * .

Prototyper struct hostent * gethostbyname ( const char * name ); struct hostent * gethostbyaddr ( const void * addr , int len ​​, int type );

connect()

connect() Upprättar en anslutning till servern. Returnerar ett heltal som representerar felkoden: 0 indikerar framgång och -1 indikerar ett fel.

Vissa typer av uttag är anslutningslösa, framför allt UDP-uttag. För dem får anslutningen en speciell betydelse: standarddestinationen för att skicka och ta emot data är tilldelad adressen som skickas, vilket gör att sådana funktioner kan användas som send()på recv()anslutningslösa uttag.

En upptagen server kan avvisa anslutningsförsöket, så vissa typer av program måste konfigureras för att försöka ansluta igen.

Prototyp #include <sys/types.h> #include <sys/socket.h> int connect ( int sockfd , const struct sockaddr * serv_addr , socklen_t addrlen );

bind()

bind()binder en socket till en specifik adress. När en socket skapas med socket()associeras den med någon adressfamilj, men inte med en specifik adress. Innan ett uttag kan ta emot inkommande anslutningar måste det bindas till en adress. bind()tar tre argument:

  • sockfd - ett handtag som representerar hylsan när den är bunden
  • serv_addr är en pekare till en struktur som sockaddrrepresenterar adressen som vi binder till.
  • addrlen är ett fält som socklen_trepresenterar strukturens längd sockaddr.

Returnerar 0 vid framgång och -1 vid fel.

Prototyp #include <sys/types.h> #include <sys/socket.h> int bind ( int sockfd , const struct sockaddr * my_addr , socklen_t addrlen );

lyssna()

listen()förbereder den bundna uttaget för att acceptera inkommande anslutningar (kallas "lyssna"). Denna funktion gäller endast för uttagstyper SOCK_STREAMoch SOCK_SEQPACKET. Tar två argument:

  • sockfd är en giltig socket-beskrivning.
  • backlog är ett heltal som anger antalet etablerade anslutningar som kan bearbetas vid varje given tidpunkt. Operativsystemet ställer vanligtvis in det på maximalt värde.

När en anslutning väl har accepterats tas den ur kö. Vid framgång returneras 0, vid fel returneras −1.

Prototyp #include <sys/socket.h> int lyssna ( int sockfd , int backlog );

accept()

accept()används för att acceptera en anslutningsbegäran från en fjärrvärd. Accepterar följande argument:

  • sockfd — Beskrivning av lyssningsuttaget för att acceptera anslutningen.
  • cliaddr — en pekare till strukturen sockaddrför att ta emot information om kundens adress.
  • addrlen — pekare till socklen_t, som definierar storleken på strukturen som innehåller klientadressen och skickas till accept(). När accept()det returnerar något värde, socklen_tindikerar det hur många byte av strukturen cliaddrsom används för närvarande.

Funktionen returnerar socket-beskrivningen som är associerad med den accepterade anslutningen, eller -1 vid fel.

Prototyp #include <sys/types.h> #include <sys/socket.h> int accept ( int sockfd , struct sockaddr * cliaddr , socklen_t * addrlen );

Ytterligare alternativ för sockets

Efter att ha skapat en socket kan du ställa in ytterligare parametrar för den. Här är några av dem:

  • TCP_NODELAYinaktiverar Nagles algoritm ;
  • SO_KEEPALIVEinkluderar periodiska kontroller för "tecken på liv" om det stöds av operativsystemet.

Blockerande och icke-blockerande uttag

Berkeley-uttag kan fungera i ett av två lägen: blockerande eller icke-blockerande. En blockerande socket returnerar inte kontroll förrän den har skickat (eller tagit emot) all data specificerad för operationen. Detta gäller bara för Linux-system. På andra system, som FreeBSD, är det naturligt att en blockerande socket inte skickar all data (men du kan ställa in flaggan send() eller recv() MSG_WAITALL). Applikationen bör kontrollera returvärdet för att hålla reda på hur många byte som skickades/mottogs och skicka om den för närvarande obearbetade informationen i enlighet därmed [2] . Detta kan leda till problem om uttaget fortsätter att lyssna: programmet kan hänga sig eftersom uttaget väntar på data som kanske aldrig kommer fram.

Ett uttag anges vanligtvis som blockerande eller icke-blockerande med hjälp av funktionerna fcntl()eller ioctl().

Dataöverföring

För att överföra data kan du använda standardfunktionerna för att läsa/skriva filer readoch write, men det finns speciella funktioner för att överföra data via uttag:

  • skicka
  • recv
  • skicka till
  • recvfrom
  • sendmsg
  • recvmsg

Det bör noteras att när du använder TCP-protokollet (sockets av typen SOCK_STREAM), finns det en chans att ta emot mindre data än vad som överfördes, eftersom inte all data har tagits emot ännu, så du måste antingen vänta tills funktionen recvreturnerar 0 byte, eller ställ in en flagga MSG_WAITALLför funktionen recv, vilket tvingar den att vänta till slutet av överföringen. För andra typer av sockets ändrar flaggan MSG_WAITALLingenting (till exempel i UDP, hela paketet = hela meddelandet). Se även Blockerande och icke-blockerande uttag.

Frigör resurser

Systemet frigör inte de resurser som tilldelats av samtalet socket()förrän samtalet inträffar close(). Detta är särskilt viktigt om samtalet connect()misslyckades och kan försökas igen. Varje anrop socket()måste ha ett motsvarande anrop close()i alla möjliga exekveringsvägar. <unistd.h>-huvudfilen måste läggas till för att stödja stängningsfunktionen.

Resultatet av att utföra ett systemanrop close()är bara att anropa gränssnittet för att stänga uttaget, inte att stänga själva uttaget. Detta är ett kommando för kärnan för att stänga sockeln. Ibland på serversidan kan uttaget gå i viloläge TIME_WAITi upp till 4 minuter. [ett]

Ett exempel på en klient och server som använder TCP

TCP implementerar konceptet med en anslutning. Processen skapar en TCP-socket genom att anropa en funktion socket()med parametrarna PF_INETeller PF_INET6, samt SOCK_STREAM(Stream-socket) och IPPROTO_TCP.

Server

Att skapa en enkel TCP-server består av följande steg:

  • Skapa TCP-sockets genom att anropa socket().
  • Bindning av ett uttag till en lyssningsport genom att anropa bind(). Innan du anropar bind()måste programmeraren deklarera strukturen sockaddr_in, rensa den (med memset()), sedan sin_family( PF_INETeller PF_INET6) och fylla i fälten sin_port(lyssningsport, ange som en sekvens av bytes ). Omvandlingen short inttill endianness kan göras med ett funktionsanrop htons()(förkortning för värd-till-nätverk).
  • Förbered ett uttag för att lyssna efter anslutningar (skapa ett lyssningsuttag) genom att ringa listen().
  • Acceptera inkommande anslutningar genom ett samtal accept(). Detta blockerar uttaget tills en inkommande anslutning tas emot, varefter det returnerar en uttagsbeskrivning för den mottagna anslutningen. Originalhandtaget förblir ett avlyssningsbart handtag och accept()kan när som helst anropas igen på det uttaget (så länge det är öppet).
  • En anslutning till en fjärrvärd som kan skapas med send()och recv()eller write()och read().
  • Den slutliga stängningen av varje öppet uttag som inte längre behövs görs med close(). Det bör noteras att om det fanns några anrop fork()måste varje process stänga de sockets som den känner till (kärnan håller reda på antalet processer som har ett öppet handtag), och dessutom får två processer inte använda samma socket på samma gång.
/* Serverkod på C-språk */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define port 1100 int main ( void ) { struct sockaddr_in stSockAddr ; int i32SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( i32SocketFD == -1 ) { perror ( "fel att skapa socket" ); avsluta ( EXIT_FAILURE ); } memset ( & stSockAddr , 0 , sizeof ( stSockAddr )); stSockAddr . sin_familj = PF_INET ; stSockAddr . sin_port = htons ( port ); stSockAddr . sin_addr . s_addr = htonl ( INADDR_ANY ); if ( bind ( i32SocketFD , ( struct sockaddr * ) & stSockAddr , sizeof ( stSockAddr )) == -1 ) { perror ( "Fel: bindningar" ); stäng ( i32SocketFD ); avsluta ( EXIT_FAILURE ); } if ( lyssna ( i32SocketFD , 10 ) == -1 ) { perror ( "Fel: lyssnar" ); stäng ( i32SocketFD ); avsluta ( EXIT_FAILURE ); } för (;;) { int i32ConnectFD = acceptera ( i32SocketFD , 0 , 0 ); if ( i32ConnectFD < 0 ) { perror ( "Fel: acceptera" ); stäng ( i32SocketFD ); avsluta ( EXIT_FAILURE ); } /* utför läs- och skrivoperationer ... */ avstängning ( i32ConnectFD , SHUT_RDWR ); stäng ( i32ConnectFD ); } returnera 0 ; }

Klient

Skapandet av en TCP-klient är som följer:

  • Skapa en TCP-socket genom att anropa socket().
  • Anslut till en server med connect(), passera en struktur sockaddr_inmed eller sin_familyspecificerad , för att ange lyssningsporten (i byteordning) och för att ange IPv4- eller IPv6-adressen för servern att lyssna på (även i byteordning).PF_INETPF_INET6sin_portsin_addr
  • Interaktion med servern med hjälp av send()och recv()eller write()och read().
  • Avsluta anslutningen och återställa information vid samtal close(). På samma sätt, om det fanns några anrop fork()måste varje process stänga ( close()) sockeln.
/* C klientkod */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> int main ( void ) { struct sockaddr_in stSockAddr ; int i32Res ; int i32SocketFD = socket ( PF_INET , SOCK_STREAM , IPPROTO_TCP ); if ( i32SocketFD == -1 ) { perror ( "Fel: Det gick inte att skapa socket" ); returnera EXIT_FAILURE ; } memset ( & stSockAddr , 0 , sizeof ( stSockAddr )); stSockAddr . sin_familj = PF_INET ; stSockAddr . sin_port = htons ( 1100 ); i32Res = inet_pton ( PF_INET , "192.168.1.3" , & stSockAddr . sin_addr ); if ( i32Res < 0 ) { perror ( "Fel: den första parametern är inte en giltig adress" ); stäng ( i32SocketFD ); returnera EXIT_FAILURE ; } else if ( ! i32Res ) { perror ( "Fel: Den andra parametern innehåller inte en giltig IP-adress" ); stäng ( i32SocketFD ); returnera EXIT_FAILURE ; } if ( connect ( i32SocketFD , ( struct sockaddr * ) & stSockAddr , sizeof ( stSockAddr )) == -1 ) { perror ( "Fel: anslutningar" ); stäng ( i32SocketFD ); returnera EXIT_FAILURE ; } /* utför läs- och skrivoperationer ... */ avstängning ( i32SocketFD , SHUT_RDWR ); stäng ( i32SocketFD ); returnera 0 ; }

Ett exempel på en klient och server som använder UDP

UDP bygger på ett anslutningslöst protokoll, det vill säga ett protokoll som inte garanterar leverans av information. UDP-paket kan komma ur funktion, dupliceras och anlända mer än en gång, eller till och med inte nå destinationen alls. På grund av dessa minimigarantier är UDP betydligt sämre än TCP. Ingen anslutningsetablering innebär inga strömmar eller anslutningar mellan två värdar, eftersom data anländer i datagram istället ( Datagram Socket ).

UDP-adressutrymmet, området för UDP-portnummer (TSAP i ISO-terminologi), är helt separat från TCP-portar.

Server

Koden kan skapa en UDP-server på port 7654 så här:

int sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); struct sockaddr_insa ; _ int bunden ; ssize_t recsize ; socklen_t * address_len = NULL ; sa . sin_addr . s_addr = htonl ( INADDR_ANY ); sa . sin_port = htons ( 7654 ); bunden = binda ( socka , ( struktur sockaddr * ) & sa , sizeof ( struktur sockaddr ) ); if ( bundet < 0 ) fprintf ( stderr , "bind(): fel %s \n " , strerror ( errno ) );

bind() binder en socket till ett adress/portpar.

medan ( 1 ) { printf ( "recv test... \n " ); recsize = recvfrom ( sock , ( void * ) Hz , 100 , 0 , ( struct sockaddr * ) & sa , adress_len ); if ( ändra storlek < 0 ) fprintf ( stderr , "Fel %s \n " , strerror ( felnr ) ); printf ( "recsize: %d \n " , recsize ); sömn ( 1 ); printf ( "datagram: %s \n " , hz ); }

En sådan oändlig slinga tar emot alla UDP-datagram som anländer till port 7654 med recvfrom() . Funktionen använder parametrar:

  • uttag,
  • pekare till databuffert,
  • buffertstorlek,
  • flaggor (på samma sätt vid läsning eller andra uttagsmottagningsfunktioner),
  • avsändarens adressstruktur,
  • längden på avsändarens adressstruktur.

Klient

En enkel demonstration av att skicka ett UDP-paket som innehåller "Hej!" till adressen 127.0.0.1, port 7654, ser ut ungefär så här:

#include <stdio.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <unistd.h> /* för att anropa close() på en socket */ int main ( void ) { int strumpa ; struct sockaddr_insa ; _ int bytes_sent ; const char * buffer = "Hej!" ; int buffer_length ; buffer_length = strlen ( buffert ) + 1 ; sock = socket ( PF_INET , SOCK_DGRAM , IPPROTO_UDP ); if ( strumpa == -1 ) { printf ( "Fel vid skapande av socket" ); returnera 0 ; } sa . sin_familj = PF_INET ; sa . sin_addr . s_addr = htonl ( 0x7F000001 ); sa . sin_port = htons ( 7654 ); bytes skickade = skickat till ( strumpa , buffert , strlen ( buffert ) + 1 , 0 , ( struct sockaddr * ) & sa , sizeof ( struct sockaddr_in ) ); if ( bytes_sent < 0 ) printf ( "Fel vid sändning av paket: %s \n " , strerror ( errno ) ); nära ( strumpa ); returnera 0 ; }

Se även

Anteckningar

  1. Uresh Vahalia. UNIX interna: de nya gränserna. - Upper Saddle River, New Jersey 07458: Prentice Hall PTR, 2003. - 844 sid. — ISBN 0-13-101908-2 .
  2. Beejs guide till nätverksprogrammering . Hämtad 12 december 2008. Arkiverad från originalet 10 april 2011.

Länkar

"De jure" definitionen av socket-gränssnittet i POSIX- standarden , mer känd som: