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
ProduzioneClient:Hello message sentHello from serverServer:Hello from clientHello message sentComponenti 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
Diagramma 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)
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.