Programowanie gniazd w C

Programowanie gniazd w C

Programowanie gniazd to sposób łączenia dwóch węzłów w sieci w celu wzajemnej komunikacji. Jedno gniazdo (węzeł) nasłuchuje na określonym porcie pod adresem IP, podczas gdy drugie gniazdo łączy się z drugim, tworząc połączenie. Serwer tworzy gniazdo nasłuchiwania, podczas gdy klient łączy się z serwerem.
Programowanie gniazd jest szeroko stosowane w aplikacjach do przesyłania wiadomości błyskawicznych, strumieniowaniu binarnym i współpracy nad dokumentami, platformach strumieniowego przesyłania danych online itp.

Przykład

W tym programie w języku C wymieniamy jedną wiadomość powitalną pomiędzy serwerem a klientem, aby zademonstrować model klient/serwer.

serwer.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  ;   }   


Kompilowanie

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


Wyjście

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

Składniki programowania gniazdowego

1. Gniazda

Gniazda to jeden z podstawowych komponentów używanych przez program do uzyskiwania dostępu do sieci w celu komunikowania się z innymi procesami/węzłami za pośrednictwem sieci. Jest to po prostu kombinacja adresu IP i numeru portu, który działa jako punkt końcowy komunikacji.
Przykład: 192.168.1.1:8080 gdzie dwie części oddzielone dwukropkiem reprezentują Adres IP (192.168.1.1) i numer portu (8080).

Typy gniazd:

  • Gniazdo TCP (gniazdo strumieniowe): Zapewnia niezawodną komunikację opartą na połączeniach (tj. Protokół TCP ).
  • Gniazdo UDP (gniazdo datagramu): Zapewnia komunikację bezpołączeniową szybszą, ale zawodną (tj. Protokół UDP ).

2. Model klient-serwer

The modelu klient-serwer odnosi się do architektury używanej w programowaniu gniazd, w której klient i serwer współdziałają ze sobą w celu wymiany informacji lub usług. Architektura ta umożliwia klientowi wysyłanie żądań usług, a serwerowi przetwarzanie i wysyłanie odpowiedzi na te żądania usług.

Diagram stanu dla modelu serwera i klienta

Programowanie gniazd w CDiagram stanu dla modelu serwera i klienta Socket

Programowanie gniazd w języku C to skuteczny sposób obsługi komunikacji sieciowej.

Tworzenie procesu po stronie serwera

Serwer jest tworzony w następujący sposób:

1. Tworzenie gniazda

Ten krok polega na utworzeniu gniazda za pomocą funkcji Socket().

Parametry:

  • skarpetka: deskryptor gniazda liczba całkowita (jak uchwyt pliku)
  • domena: liczba całkowita określa domenę komunikacyjną. Używamy AF_LOCAL zgodnie z definicją w standardzie POSIX do komunikacji między procesami na tym samym hoście. Do komunikacji pomiędzy procesami na różnych hostach połączonych przez IPV4 używamy AF_INET i AF_I NET 6 dla procesów połączonych przez IPV6.
  • typ: typ komunikacji
    SOCK_STREAM: TCP (niezawodny zorientowany na połączenie)
    SOCK_DGRAM: UDP (niewiarygodny brak połączenia)
  • protokół: Wartość protokołu dla protokołu internetowego (IP), która wynosi 0. Jest to ten sam numer, który pojawia się w polu protokołu w nagłówku IP pakietu. (więcej szczegółów można znaleźć w protokołach man)
C
   sockfd     =     socket  (  domain       type       protocol  )   

2. Ustaw opcję gniazda

Pomaga to w manipulowaniu opcjami gniazda, do którego odnosi się deskryptor pliku sockfd. Jest to całkowicie opcjonalne, ale pomaga w ponownym wykorzystaniu adresu i portu. Zapobiega błędom takim jak: adres już używany.

C
   setsockopt  (  sockfd       level       optname       optval       socklen_t     optlen  );   

3. Zwiąż

Po utworzeniu gniazda funkcja bind() wiąże gniazdo z adresem i numerem portu określonymi w adresie (niestandardowa struktura danych). W przykładowym kodzie łączymy serwer z hostem lokalnym, dlatego używamy INADDR_ANY do określenia adresu IP.

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

Parametry:

  • skarpetka : deskryptor pliku gniazda utworzony przy użyciu funkcji Socket().
  • adres : wskaźnik do struktury sockaddr zawierającej adres IP i numer portu do powiązania z gniazdem.
  • adres : długość struktury adresu.

4. Słuchaj

Na tym etapie serwer używa funkcji Listen(), która ustawia gniazdo serwera w tryb pasywny, w którym oczekuje, aż klient zbliży się do serwera w celu nawiązania połączenia. Backlog określa maksymalną długość, do której może urosnąć kolejka oczekujących połączeń dla sockfd. Jeśli żądanie połączenia nadejdzie, gdy kolejka jest pełna, klient może otrzymać błąd ze wskazaniem ECONNREFUSED.

C
   listen  (  sockfd       backlog  );   

Parametry :

  • skarpetka : deskryptor pliku gniazda utworzony przy użyciu funkcji Socket().
  • zaległości : liczba reprezentująca rozmiar kolejki oczekujących połączeń, podczas gdy serwer czeka na przyjęcie połączenia.

5. Zaakceptuj

W tym kroku serwer wyodrębnia pierwsze żądanie połączenia z kolejki oczekujących połączeń dla gniazda nasłuchującego. sockfd tworzy nowe połączone gniazdo za pomocą przyjąć() funkcję i zwraca nowy deskryptor pliku odnoszący się do tego gniazda. W tym momencie zostaje nawiązane połączenie pomiędzy klientem a serwerem i serwer jest gotowy do przesyłania danych.

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

Parametry:

  • skarpetka : deskryptor pliku gniazda zwracany przez gniazdo() i bind().
  • adres : wskaźnik do struktury sockaddr, w której będzie przechowywany adres IP i numer portu klienta.
  • adres : wskaźnik do zmiennej określającej długość struktury adresu.

6. Wyślij/Odbierz

Na tym etapie serwer może wysyłać lub odbierać dane od klienta.

Wysłać(): do przesłania danych do klienta

C
   send  (  sockfd       *  buf       len       flags  );   

Parametry:

  • skarpetka : deskryptor pliku gniazda zwrócony przez funkcję Socket().
  • buf : wskaźnik do bufora zawierającego dane do wysłania.
  • tylko : liczba bajtów danych do wysłania.
  • flagi : liczba całkowita określająca różne opcje sposobu wysyłania danych. Zazwyczaj 0 jest używane jako zachowanie domyślne.

Odbierać() : do odbioru danych od klienta.

C
   recv  (     sockfd       *  buf       len       flags  );   

Parametry:

  • skarpetka : deskryptor pliku gniazda zwrócony przez funkcję Socket().
  • buf : wskaźnik do bufora zawierającego dane, które mają być przechowywane.
  • tylko : liczba bajtów danych do wysłania.
  • flagi : liczba całkowita określająca różne opcje sposobu wysyłania danych. Zazwyczaj 0 jest używane jako zachowanie domyślne.

6. Zamknij

Po zakończeniu wymiany informacji serwer zamyka gniazdo przy pomocy funkcji close() i zwalnia zasoby systemowe.

C
   close  (  fd  );   

Parametry:

  • fd: deskryptor pliku gniazda.

Tworzenie procesu po stronie klienta

Wykonaj poniższe kroki, aby utworzyć proces po stronie klienta:

1. Podłączenie do gniazda

Ten krok polega na utworzeniu gniazda, które odbywa się w taki sam sposób, jak tworzenie gniazda serwera

2. Połącz

Wywołanie systemowe connect() łączy gniazdo określone przez deskryptor pliku sockfd z adresem określonym przez adres. Adres i port serwera podano w adresie.

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

Parametry

  • skarpetka : deskryptor pliku gniazda zwrócony przez funkcję Socket().
  • adres : wskaźnik do struktury sockaddr zawierającej adres IP serwera i numer portu.
  • adres : wielkość adresu

3. Wyślij/Odbierz

Na tym etapie klient może wysyłać lub odbierać dane z serwera, co odbywa się za pomocą funkcji send() i recieve() podobnych do sposobu, w jaki serwer wysyła/odbiera dane od klienta.

4. Zamknij

Po zakończeniu wymiany informacji klient musi również zamknąć utworzone gniazdo i zwolnić zasoby systemowe za pomocą funkcji close() w taki sam sposób, jak robi to serwer.

Typowe problemy i ich poprawki w programowaniu gniazd

  • Błędy połączenia: Aby uniknąć awarii połączenia powinniśmy upewnić się, że klient próbuje połączyć się z właściwym Adres IP i port .
  • Błędy wiązania portów: Te błędy występują, gdy port jest już używany przez inną aplikację. W tym scenariuszu powiązanie z tym portem nie powiedzie się. Spróbuj użyć innego portu lub zamknij poprzednią aplikację korzystającą z portu.
  • Blokowanie gniazd: Domyślnie gniazda są blokowane. Oznacza to, że wywołania takie jak Accept() lub recv() będą czekać w nieskończoność, jeśli nie ma połączenia z klientem lub danych. W razie potrzeby możesz ustawić gniazdo w tryb nieblokujący.
Utwórz quiz