Programování socketů v C

Programování socketů v C

Programování zásuvek je způsob propojení dvou uzlů v síti za účelem vzájemné komunikace. Jeden soket (uzel) naslouchá na konkrétním portu na IP, zatímco druhý soket zasahuje do druhého, aby vytvořil spojení. Server tvoří soket posluchače, zatímco klient oslovuje server.
Programování soketů je široce používáno v aplikacích pro rychlé zasílání zpráv, binární streamování a spolupráce na dokumentech, online streamovací platformy atd.

Příklad

V tomto programu C si vyměňujeme jednu pozdravnou zprávu mezi serverem a klientem, abychom demonstrovali model klient/server.

server.c

C
   #include         #include         #include         #include         #include         #include         #define PORT 8080   int     main  (  int     argc       char     const  *     argv  [])   {      int     server_fd       new_socket  ;      ssize_t     valread  ;      struct     sockaddr_in     address  ;      int     opt     =     1  ;      socklen_t     addrlen     =     sizeof  (  address  );      char     buffer  [  1024  ]     =     {     0     };      char  *     hello     =     'Hello from server'  ;      // Creating socket file descriptor      if     ((  server_fd     =     socket  (  AF_INET       SOCK_STREAM       0  ))      <     0  )     {      perror  (  'socket failed'  );      exit  (  EXIT_FAILURE  );      }      // Forcefully attaching socket to the port 8080      if     (  setsockopt  (  server_fd       SOL_SOCKET        SO_REUSEADDR     |     SO_REUSEPORT       &  opt        sizeof  (  opt  )))     {      perror  (  'setsockopt'  );      exit  (  EXIT_FAILURE  );      }      address  .  sin_family     =     AF_INET  ;      address  .  sin_addr  .  s_addr     =     INADDR_ANY  ;      address  .  sin_port     =     htons  (  PORT  );      // Forcefully attaching socket to the port 8080      if     (  bind  (  server_fd       (  struct     sockaddr  *  )  &  address        sizeof  (  address  ))       <     0  )     {      perror  (  'bind failed'  );      exit  (  EXIT_FAILURE  );      }      if     (  listen  (  server_fd       3  )      <     0  )     {      perror  (  'listen'  );      exit  (  EXIT_FAILURE  );      }      if     ((  new_socket      =     accept  (  server_fd       (  struct     sockaddr  *  )  &  address        &  addrlen  ))       <     0  )     {      perror  (  'accept'  );      exit  (  EXIT_FAILURE  );      }          // subtract 1 for the null      // terminator at the end      valread     =     read  (  new_socket       buffer        1024     -     1  );         printf  (  '%s  n  '       buffer  );      send  (  new_socket       hello       strlen  (  hello  )     0  );      printf  (  'Hello message sent  n  '  );      // closing the connected socket      close  (  new_socket  );          // closing the listening socket      close  (  server_fd  );      return     0  ;   }   

klient.c

C
   #include          #include         #include         #include         #include         #define PORT 8080   int     main  (  int     argc       char     const  *     argv  [])   {      int     status       valread       client_fd  ;      struct     sockaddr_in     serv_addr  ;      char  *     hello     =     'Hello from client'  ;      char     buffer  [  1024  ]     =     {     0     };      if     ((  client_fd     =     socket  (  AF_INET       SOCK_STREAM       0  ))      <     0  )     {      printf  (  '  n   Socket creation error   n  '  );      return     -1  ;      }      serv_addr  .  sin_family     =     AF_INET  ;      serv_addr  .  sin_port     =     htons  (  PORT  );      // Convert IPv4 and IPv6 addresses from text to binary      // form      if     (  inet_pton  (  AF_INET       '127.0.0.1'       &  serv_addr  .  sin_addr  )       <=     0  )     {      printf  (      '  n  Invalid address/ Address not supported   n  '  );      return     -1  ;      }      if     ((  status      =     connect  (  client_fd       (  struct     sockaddr  *  )  &  serv_addr        sizeof  (  serv_addr  )))       <     0  )     {      printf  (  '  n  Connection Failed   n  '  );      return     -1  ;      }          // subtract 1 for the null      // terminator at the end      send  (  client_fd       hello       strlen  (  hello  )     0  );      printf  (  'Hello message sent  n  '  );      valread     =     read  (  client_fd       buffer        1024     -     1  );         printf  (  '%s  n  '       buffer  );      // closing the connected socket      close  (  client_fd  );      return     0  ;   }   


Kompilace

 gcc client.c -o clientgcc server.c -o server  


Výstup

 Client:Hello message sentHello from serverServer:Hello from clientHello message sent  

Komponenty soketového programování

1. Zásuvky

Zásuvky jsou jednou ze základních komponent používaných programem pro přístup k síti pro komunikaci s jinými procesy/uzly v síti. Je to prostě kombinace IP adresy a čísla portu, který funguje jako koncový bod pro komunikaci.
Příklad: 192.168.1.1:8080 kde dvě části oddělené dvojtečkou představují IP adresa (192.168.1.1) a číslo portu (8080).

Typy zásuvek:

  • TCP Socket (Stream Socket): Poskytuje spolehlivou komunikaci založenou na připojení (t.j. TCP protokol ).
  • Zásuvka UDP (zásuvka pro datagram): Poskytuje rychlejší, ale nespolehlivou komunikaci bez připojení (tj. protokol UDP ).

2. Model klient-server

The model klient-server odkazuje na architekturu používanou v programování soketů, kde klient a server vzájemně spolupracují za účelem výměny informací nebo služeb. Tato architektura umožňuje klientovi odesílat požadavky na služby a serveru zpracovávat a odesílat odpovědi na tyto požadavky na služby.

Stavový diagram pro model serveru a klienta

Programování socketů v CStavový diagram pro serverový a klientský model Socketu

Socket programování v C je výkonný způsob, jak zvládnout síťovou komunikaci.

Vytvoření procesu na straně serveru

Server se vytvoří pomocí následujících kroků:

1. Vytvoření zásuvky

Tento krok zahrnuje vytvoření soketu pomocí funkce socket().

Parametry:

  • sockfd: deskriptor soketu celé číslo (jako popisovač souboru)
  • doména: celé číslo určuje komunikační doménu. Pro komunikaci mezi procesy na stejném hostiteli používáme AF_ LOCAL, jak je definováno ve standardu POSIX. Pro komunikaci mezi procesy na různých hostitelích připojených přes IPV4 používáme AF_INET a AF_I NET 6 pro procesy spojené přes IPV6.
  • typ: typ komunikace
    SOCK_STREAM: TCP (orientované na spolehlivé připojení)
    SOCK_DGRAM: UDP (nespolehlivý bez připojení)
  • protokol: Hodnota protokolu pro internetový protokol (IP), která je 0. Toto je stejné číslo, které se zobrazuje v poli protokolu v hlavičce IP paketu. (další podrobnosti naleznete v protokolech man)
C
   sockfd     =     socket  (  domain       type       protocol  )   

2. Nastavte zásuvku opt

To pomáhá při manipulaci s volbami pro soket, na který odkazuje deskriptor souboru sockfd. Toto je zcela volitelné, ale pomáhá při opětovném použití adresy a portu. Zabraňuje chybám jako: adresa se již používá.

C
   setsockopt  (  sockfd       level       optname       optval       socklen_t     optlen  );   

3. Svázat

Po vytvoření soketu funkce bind() naváže soket na adresu a číslo portu zadané v addr (vlastní datová struktura). V ukázkovém kódu vážeme server k localhost, proto používáme INADDR_ANY k určení IP adresy.

C++
   bind  (  sockfd       sockaddr     *  addr       socklen_t     addrlen  );   

Parametry:

  • sockfd : deskriptor souboru socket vytvořený pomocí funkce socket().
  • adr : ukazatel na strukturu sockaddr, která obsahuje IP adresu a číslo portu pro vazbu soketu.
  • addrlen : délka struktury addr.

4. Poslouchejte

V tomto kroku server používá funkci listen(), která uvede serverový soket do pasivního režimu, kde čeká, až se klient přiblíží k serveru a naváže spojení. Backlog definuje maximální délku, na kterou může narůst fronta čekajících připojení pro sockfd. Pokud požadavek na připojení dorazí, když je fronta plná, klient může obdržet chybu s označením ECONNREFUSED.

C
   listen  (  sockfd       backlog  );   

Parametry :

  • sockfd : deskriptor souboru socket vytvořený pomocí funkce socket().
  • nevyřízených : číslo představující velikost fronty, která drží nevyřízená připojení, zatímco server čeká na přijetí připojení.

5. Přijměte

V tomto kroku server extrahuje první požadavek na připojení z fronty čekajících připojení pro naslouchající soket sockfd vytvoří nový připojený soket pomocí přijmout() a vrátí nový deskriptor souboru odkazující na daný soket. V tomto okamžiku je navázáno spojení mezi klientem a serverem a jsou připraveni k přenosu dat.

C
   new_socket  =     accept  (  sockfd       sockaddr     *  addr       socklen_t     *  addrlen  );   

Parametry:

  • sockfd : deskriptor souboru soketu vrácený funkcemi socket() a bind().
  • adr : ukazatel na strukturu sockaddr, která bude obsahovat IP adresu klienta a číslo portu.
  • addrlen : ukazatel na proměnnou, která určuje délku struktury adresy.

6. Odeslat/Přijmout

V tomto kroku může server odesílat nebo přijímat data od klienta.

Poslat(): k odeslání dat klientovi

C
   send  (  sockfd       *  buf       len       flags  );   

Parametry:

  • sockfd : deskriptor souboru socket vrácený funkcí socket().
  • buf : ukazatel na vyrovnávací paměť obsahující data, která mají být odeslána.
  • pouze : počet bajtů dat k odeslání.
  • vlajky : celé číslo určující různé možnosti, jak jsou data odesílána, obvykle se pro výchozí chování používá 0.

Přijmout(): získat data od klienta.

C
   recv  (     sockfd       *  buf       len       flags  );   

Parametry:

  • sockfd : deskriptor souboru socket vrácený funkcí socket().
  • buf : ukazatel na vyrovnávací paměť obsahující data, která mají být uložena.
  • pouze : počet bajtů dat k odeslání.
  • vlajky : celé číslo určující různé možnosti, jak jsou data odesílána, obvykle se pro výchozí chování používá 0.

6. Zavřete

Po dokončení výměny informací server uzavře soket pomocí funkce close() a uvolní systémové prostředky.

C
   close  (  fd  );   

Parametry:

  • fd: deskriptor souboru soketu.

Vytvoření procesu na straně klienta

Při vytváření procesu na straně klienta postupujte podle následujících kroků:

1. Zapojení do zásuvky

Tento krok zahrnuje vytvoření soketu, který se provádí stejným způsobem jako vytváření soketu serveru

2. Připojte

Systémové volání connect() připojí soket, na který odkazuje deskriptor souboru sockfd, na adresu zadanou addr. Adresa a port serveru jsou uvedeny v addr.

C++
   connect  (  sockfd       sockaddr     *  addr       socklen_t     addrlen  );   

Parametry

  • sockfd : deskriptor souboru socket vrácený funkcí socket().
  • adr : ukazatel na strukturu sockaddr obsahující IP adresu serveru a číslo portu.
  • addrlen : velikost adr.

3. Odeslat/Přijmout

V tomto kroku může klient odesílat nebo přijímat data ze serveru, což se provádí pomocí funkcí send() a recieve() podobně jako server odesílá/přijímá data od klienta.

4. Zavřete

Jakmile je výměna informací dokončena, klient také musí zavřít vytvořený soket a uvolnit systémové prostředky pomocí funkce close() stejným způsobem jako server.

Běžné problémy a jejich opravy v programování soketů

  • Selhání připojení: Abychom se vyhnuli selhání připojení, měli bychom zajistit, že se klient pokouší připojit ke správnému IP adresa a port .
  • Chyby vázání portu: K těmto chybám dochází, když je port již používán jinou aplikací. V tomto scénáři vazba na tento port selže. Zkuste použít jiný port nebo zavřete předchozí aplikaci používající port.
  • Blokovací zásuvky: Ve výchozím nastavení jsou zásuvky blokovány. To znamená, že volání jako accept() nebo recv() budou čekat neomezeně dlouho, pokud není k dispozici žádné připojení klienta nebo data. V případě potřeby můžete zásuvku nastavit do neblokovacího režimu.
Vytvořit kvíz