Programmazione socket in C

Programmazione socket in C

Programmazione delle prese è un modo per connettere due nodi su una rete per comunicare tra loro. Un socket (nodo) è in ascolto su una particolare porta su un IP mentre l'altro socket si collega all'altro per formare una connessione. Il server forma il socket del listener mentre il client raggiunge il server.
La programmazione socket è ampiamente utilizzata nelle applicazioni di messaggistica istantanea, streaming binario e collaborazioni di documenti, piattaforme di streaming online, ecc.

Esempio

In questo programma C stiamo scambiando un messaggio di saluto tra server e client per dimostrare il modello client/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  ;   }   

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


Compilazione

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


Produzione

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

Componenti della programmazione socket

1. Prese

Prese sono uno dei componenti principali utilizzati dal programma per accedere alla rete per comunicare con altri processi/nodi sulla rete. È semplicemente una combinazione di un indirizzo IP e un numero di porta che funge da endpoint per la comunicazione.
Esempio: 192.168.1.1:8080 dove le due parti separate dai due punti rappresentano il Indirizzo IP (192.168.1.1) e il numero di porta (8080).

Tipi di prese:

  • Socket TCP (socket flusso): Fornisce una comunicazione affidabile basata sulla connessione (ad es. Protocollo TCP ).
  • Socket UDP (socket datagramma): Fornisce comunicazioni senza connessione più veloci ma inaffidabili (ad es. Protocollo UDP ).

2. Modello client-server

IL modello client-server si riferisce all'architettura utilizzata nella programmazione socket in cui un client e un server interagiscono tra loro per scambiare informazioni o servizi. Questa architettura consente al client di inviare richieste di servizio e al server di elaborare e inviare risposta a tali richieste di servizio.

Diagramma di stato per il modello server e client

Programmazione socket in CDiagramma di stato per il modello server e client di Socket

La programmazione dei socket in C è un modo potente per gestire la comunicazione di rete.

Creazione di un processo lato server

Il server viene creato utilizzando i seguenti passaggi:

1. Creazione del socket

Questo passaggio prevede la creazione del socket utilizzando la funzione socket().

parametri:

  • sockfd: descrittore del socket un numero intero (come un handle di file)
  • dominio: il numero intero specifica il dominio di comunicazione. Utilizziamo AF_ LOCAL come definito nello standard POSIX per la comunicazione tra processi sullo stesso host. Per comunicare tra processi su host diversi collegati tramite IPV4 utilizziamo AF_INET e AF_I NET 6 per processi collegati tramite IPV6.
  • tipo: tipo di comunicazione
    SOCK_STREAM: TCP (orientato alla connessione affidabile)
    SOCK_DGRAM: UDP (senza connessione inaffidabile)
  • protocollo: Valore del protocollo per Internet Protocol (IP) che è 0. Questo è lo stesso numero che appare nel campo del protocollo nell'intestazione IP di un pacchetto. (protocolli man per maggiori dettagli)
C
   sockfd     =     socket  (  domain       type       protocol  )   

2. Impostare presa opt

Questo aiuta a manipolare le opzioni per il socket a cui fa riferimento il descrittore di file sockfd. Questo è completamente facoltativo ma aiuta a riutilizzare l'indirizzo e la porta. Previene errori come: indirizzo già in uso.

C
   setsockopt  (  sockfd       level       optname       optval       socklen_t     optlen  );   

3. Rilegare

Dopo la creazione del socket, la funzione bind() associa il socket all'indirizzo e al numero di porta specificati in addr(struttura dati personalizzata). Nel codice di esempio leghiamo il server al localhost quindi utilizziamo INADDR_ANY per specificare l'indirizzo IP.

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

parametri:

  • sockfd : descrittore di file socket creato utilizzando la funzione socket().
  • indirizzo : puntatore a una struttura sockaddr che contiene l'indirizzo IP e il numero di porta per associare il socket.
  • addrlen : lunghezza della struttura dell'indirizzo.

4. Ascolta

In questo passaggio il server utilizza la funzione listen() che mette il socket del server in modalità passiva in cui attende che il client si avvicini al server per stabilire una connessione. Il backlog definisce la lunghezza massima alla quale può crescere la coda delle connessioni in sospeso per sockfd. Se arriva una richiesta di connessione quando la coda è piena il client potrebbe ricevere un errore con l'indicazione ECONNREFUSED.

C
   listen  (  sockfd       backlog  );   

Parametri :

  • sockfd : descrittore di file socket creato utilizzando la funzione socket().
  • arretrato : numero che rappresenta la dimensione della coda che contiene le connessioni in sospeso mentre il server è in attesa di accettare una connessione.

5. Accetta

In questo passaggio il server estrae la prima richiesta di connessione dalla coda delle connessioni pendenti per il socket in ascolto sockfd crea un nuovo socket connesso utilizzando il metodo accettare() funzione e restituisce un nuovo descrittore di file che fa riferimento a quel socket. A questo punto viene stabilita la connessione tra client e server e sono pronti per trasferire i dati.

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

parametri:

  • sockfd : descrittore del file socket restituito da socket() e bind().
  • indirizzo : puntatore a una struct sockaddr che manterrà l'indirizzo IP e il numero di porta del client.
  • addrlen : puntatore a una variabile che specifica la lunghezza della struttura dell'indirizzo.

6. Invia/Ricevi

In questa fase il server può inviare o ricevere dati dal client.

Inviare(): per inviare i dati al client

C
   send  (  sockfd       *  buf       len       flags  );   

parametri:

  • sockfd : descrittore del file socket restituito dalla funzione socket().
  • buf : puntatore al buffer contenente i dati da inviare.
  • soltanto : numero di byte di dati da inviare.
  • bandiere : numero intero che specifica varie opzioni per la modalità di invio dei dati, in genere 0 viene utilizzato per il comportamento predefinito.

Ricevere() : per ricevere i dati dal cliente.

C
   recv  (     sockfd       *  buf       len       flags  );   

parametri:

  • sockfd : descrittore del file socket restituito dalla funzione socket().
  • buf : puntatore al buffer contenente i dati da memorizzare.
  • soltanto : numero di byte di dati da inviare.
  • bandiere : numero intero che specifica varie opzioni per la modalità di invio dei dati, in genere 0 viene utilizzato per il comportamento predefinito.

6. Chiudi

Una volta completato lo scambio di informazioni, il server chiude il socket utilizzando la funzione close() e rilascia le risorse di sistema.

C
   close  (  fd  );   

parametri:

  • fd: descrittore di file del socket.

Creazione del processo lato client

Seguire i passaggi seguenti per creare un processo lato client:

1. Connessione presa

Questo passaggio prevede la creazione del socket che viene eseguita allo stesso modo di quella della creazione del socket del server

2. Connetti

La chiamata di sistema connect() collega il socket a cui fa riferimento il descrittore di file sockfd all'indirizzo specificato da addr. L'indirizzo e la porta del server sono specificati in addr.

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

Parametri

  • sockfd : descrittore del file socket restituito dalla funzione socket().
  • indirizzo : puntatore alla struttura sockaddr contenente l'indirizzo IP e il numero di porta del server.
  • addrlen : dimensione dell'indir.

3. Invia/Ricevi

In questo passaggio il client può inviare o ricevere dati dal server, operazione eseguita utilizzando le funzioni send() e receve() in modo simile a come il server invia/riceve dati dal client.

4. Chiudi

Una volta completato lo scambio di informazioni, anche il client deve chiudere il socket creato e rilasciare le risorse di sistema utilizzando la funzione close() allo stesso modo del server.

Problemi comuni e relative soluzioni nella programmazione socket

  • Errori di connessione: Per evitare errori di connessione dovremmo assicurarci che il client stia tentando di connettersi al corretto Indirizzo IP e porta .
  • Errori di associazione della porta: Questi errori si verificano quando una porta è già utilizzata da un'altra applicazione e in questo scenario l'associazione a quella porta avrà esito negativo. Prova a utilizzare una porta diversa o chiudi l'applicazione precedente utilizzando la porta.
  • Socket di blocco: Per impostazione predefinita i socket sono bloccanti. Ciò significa che chiamate come accetta() o recv() attenderanno indefinitamente se non sono presenti connessioni o dati del client. Se necessario, è possibile impostare il socket in modalità non bloccante.
Crea quiz