Unix Socket - Guida rapida

I socket consentono la comunicazione tra due processi diversi sulla stessa macchina o su macchine diverse. Per essere più precisi, è un modo per parlare con altri computer utilizzando descrittori di file Unix standard. In Unix, ogni azione di I / O viene eseguita scrivendo o leggendo un descrittore di file. Un descrittore di file è semplicemente un numero intero associato a un file aperto e può essere una connessione di rete, un file di testo, un terminale o qualcos'altro.

Per un programmatore, un socket appare e si comporta in modo molto simile a un descrittore di file di basso livello. Questo perché comandi come read () e write () funzionano con i socket nello stesso modo in cui funzionano con file e pipe.

I socket furono introdotti per la prima volta in 2.1BSD e successivamente perfezionati nella loro forma attuale con 4.2BSD. La funzione socket è ora disponibile con le versioni più recenti del sistema UNIX.

Dove viene utilizzato il socket?

Un socket Unix viene utilizzato in un framework di applicazioni client-server. Un server è un processo che esegue alcune funzioni su richiesta di un client. La maggior parte dei protocolli a livello di applicazione come FTP, SMTP e POP3 utilizza i socket per stabilire la connessione tra client e server e quindi per lo scambio di dati.

Tipi di socket

Ci sono quattro tipi di prese disponibili per gli utenti. I primi due sono quelli più comunemente usati e gli ultimi due sono usati raramente.

Si presume che i processi comunichino solo tra socket dello stesso tipo, ma non vi sono restrizioni che impediscano la comunicazione tra socket di tipo diverso.

  • Stream Sockets- La consegna in un ambiente di rete è garantita. Se invii tramite il socket stream tre elementi "A, B, C", arriveranno nello stesso ordine - "A, B, C". Questi socket utilizzano il protocollo TCP (Transmission Control Protocol) per la trasmissione dei dati. Se la consegna è impossibile, il mittente riceve un indicatore di errore. I record di dati non hanno confini.

  • Datagram Sockets- La consegna in un ambiente di rete non è garantita. Sono privi di connessione perché non è necessario disporre di una connessione aperta come in Stream Socket: si crea un pacchetto con le informazioni di destinazione e lo si invia. Usano UDP (User Datagram Protocol).

  • Raw Sockets- Forniscono agli utenti l'accesso ai protocolli di comunicazione sottostanti, che supportano le astrazioni dei socket. Questi socket sono normalmente orientati al datagramma, sebbene le loro caratteristiche esatte dipendono dall'interfaccia fornita dal protocollo. I socket raw non sono destinati all'utente generico; sono stati forniti principalmente a coloro che sono interessati allo sviluppo di nuovi protocolli di comunicazione o per ottenere l'accesso ad alcune delle strutture più criptiche di un protocollo esistente.

  • Sequenced Packet Sockets- Sono simili a un socket di flusso, con l'eccezione che i limiti dei record vengono preservati. Questa interfaccia viene fornita solo come parte dell'astrazione dei socket NS (Network Systems) ed è molto importante nelle applicazioni NS più serie. I socket per pacchetti sequenziati consentono all'utente di manipolare le intestazioni SPP (Sequence Packet Protocol) o IDP (Internet Datagram Protocol) su un pacchetto o un gruppo di pacchetti, scrivendo un'intestazione prototipo insieme ai dati da inviare o specificando un'intestazione predefinita da utilizzare con tutti i dati in uscita e consente all'utente di ricevere le intestazioni sui pacchetti in arrivo.

Cosa c'è dopo?

I prossimi capitoli hanno lo scopo di rafforzare le tue basi e preparare una base prima di poter scrivere programmi Server e Client usando socket . Se vuoi passare direttamente a vedere come scrivere un programma client e server, puoi farlo ma non è raccomandato. Si consiglia vivamente di andare passo dopo passo e completare questi primi capitoli per fare la propria base prima di passare alla programmazione.

Prima di procedere con le cose effettive, discutiamo un po 'degli indirizzi di rete: l'indirizzo IP.

L'indirizzo IP host, o più comunemente solo l'indirizzo IP, viene utilizzato per identificare gli host connessi a Internet. IP sta per protocollo Internet e si riferisce al livello Internet dell'architettura di rete complessiva di Internet.

Un indirizzo IP è una quantità a 32 bit interpretata come quattro numeri o ottetti a 8 bit. Ogni indirizzo IP identifica in modo univoco la rete utente partecipante, l'host sulla rete e la classe della rete utente.

Un indirizzo IP viene solitamente scritto in una notazione decimale puntata nella forma N1.N2.N3.N4, dove ogni Ni è un numero decimale compreso tra 0 e 255 decimale (da 00 a FF esadecimale).

Classi di indirizzi

Gli indirizzi IP vengono gestiti e creati dalla IANA ( Internet Assigned Numbers Authority ). Sono disponibili cinque diverse classi di indirizzi. È possibile determinare in quale classe si trova un indirizzo IP esaminando i primi quattro bit dell'indirizzo IP.

  • Class A gli indirizzi iniziano con 0xxx, o 1 to 126 decimale.

  • Class B gli indirizzi iniziano con 10xx, o 128 to 191 decimale.

  • Class C gli indirizzi iniziano con 110x, o 192 to 223 decimale.

  • Class D gli indirizzi iniziano con 1110, o 224 to 239 decimale.

  • Class E gli indirizzi iniziano con 1111, o 240 to 254 decimale.

Indirizzi che iniziano con 01111111, o 127 decimali, sono riservati per il loopback e per i test interni su una macchina locale [Puoi testarlo: dovresti sempre essere in grado di eseguire il ping 127.0.0.1, che punta a te stesso]; Gli indirizzi di classe D sono riservati per il multicasting; Gli indirizzi di classe E sono riservati per uso futuro. Non dovrebbero essere usati per gli indirizzi host.

Esempio

Class Leftmost bits Start address Finish address
UN 0xxx 0.0.0.0 127.255.255.255
B 10xx 128.0.0.0 191.255.255.255
C 110x 192.0.0.0 223.255.255.255
D 1110 224.0.0.0 239.255.255.255
E 1111 240.0.0.0 255.255.255.255

Sottoreti

Sottoreti o sottoreti significa fondamentalmente diramare una rete. Può essere fatto per una serie di motivi, come la rete in un'organizzazione, l'uso di diversi supporti fisici (come Ethernet, FDDI, WAN, ecc.), La conservazione dello spazio degli indirizzi e la sicurezza. Il motivo più comune è controllare il traffico di rete.

L'idea di base nel subnetting è quella di partizionare la parte dell'identificatore host dell'indirizzo IP in due parti:

  • Un indirizzo di sottorete all'interno dell'indirizzo di rete stesso; e
  • Un indirizzo host sulla sottorete.

Ad esempio, un formato di indirizzo di classe B comune è N1.N2.SH, dove N1.N2 identifica la rete di classe B, il campo S a 8 bit identifica la sottorete e il campo H a 8 bit identifica l'host sulla sottorete.

I nomi degli host in termini di numeri sono difficili da ricordare e quindi sono definiti con nomi ordinari come Takshila o Nalanda. Scriviamo applicazioni software per scoprire l'indirizzo IP puntato corrispondente a un determinato nome.

Il processo di individuazione dell'indirizzo IP puntato in base al nome host alfanumerico specificato è noto come hostname resolution.

Una risoluzione del nome host viene eseguita da un software speciale che risiede su sistemi ad alta capacità. Questi sistemi sono chiamati Domain Name System (DNS), che mantengono la mappatura degli indirizzi IP e dei corrispondenti nomi ordinari.

Il file / etc / hosts

La corrispondenza tra i nomi host e gli indirizzi IP viene mantenuta in un file chiamato host . Sulla maggior parte dei sistemi, questo file si trova in/etc directory.

Le voci in questo file hanno il seguente aspetto:

# This represents a comments in /etc/hosts file.
127.0.0.1       localhost
192.217.44.207  nalanda metro
153.110.31.18   netserve
153.110.31.19   mainserver centeral
153.110.31.20   samsonite
64.202.167.10   ns3.secureserver.net
64.202.167.97   ns4.secureserver.net
66.249.89.104   www.google.com
68.178.157.132  services.amrood.com

Tieni presente che più di un nome può essere associato a un determinato indirizzo IP. Questo file viene utilizzato durante la conversione dall'indirizzo IP al nome host e viceversa.

Non avresti accesso per modificare questo file, quindi se vuoi inserire un nome host insieme all'indirizzo IP, devi avere il permesso di root.

La maggior parte delle Net Applications utilizza l'architettura Client-Server, che fa riferimento a due processi o due applicazioni che comunicano tra loro per scambiare alcune informazioni. Uno dei due processi funge da processo client e un altro processo funge da server.

Processo client

Questo è il processo, che in genere effettua una richiesta di informazioni. Dopo aver ottenuto la risposta, questo processo potrebbe terminare o potrebbe eseguire altre elaborazioni.

Example, Il browser Internet funziona come un'applicazione client, che invia una richiesta al server Web per ottenere una pagina Web HTML.

Processo server

Questo è il processo che prende una richiesta dai client. Dopo aver ricevuto una richiesta dal client, questo processo eseguirà l'elaborazione richiesta, raccoglierà le informazioni richieste e le invierà al client richiedente. Una volta terminato, diventa pronto per servire un altro cliente. I processi del server sono sempre attenti e pronti a soddisfare le richieste in arrivo.

Example - Il server Web continua ad attendere le richieste dai browser Internet e non appena riceve una richiesta da un browser, raccoglie una pagina HTML richiesta e la rimanda a quel browser.

Notare che il client ha bisogno di conoscere l'indirizzo del server, ma il server non ha bisogno di conoscere l'indirizzo o anche l'esistenza del client prima che la connessione venga stabilita. Una volta stabilita una connessione, entrambe le parti possono inviare e ricevere informazioni.

Architetture a 2 e 3 livelli

Esistono due tipi di architetture client-server:

  • 2-tier architecture- In questa architettura, il client interagisce direttamente con il server. Questo tipo di architettura potrebbe presentare alcuni buchi di sicurezza e problemi di prestazioni. Internet Explorer e Web Server funzionano su un'architettura a due livelli. Qui i problemi di sicurezza vengono risolti utilizzando Secure Socket Layer (SSL).

  • 3-tier architectures- In questa architettura, un altro software si trova tra il client e il server. Questo software intermedio è chiamato "middleware". Il middleware viene utilizzato per eseguire tutti i controlli di sicurezza e il bilanciamento del carico in caso di carico pesante. Un middleware accetta tutte le richieste dal client e, dopo aver eseguito l'autenticazione richiesta, passa tale richiesta al server. Quindi il server esegue l'elaborazione richiesta e invia la risposta al middleware e infine il middleware restituisce questa risposta al client. Se si desidera implementare un'architettura a 3 livelli, è possibile mantenere qualsiasi middleware come Web Logic o software WebSphere tra il server Web e il browser Web.

Tipi di server

Esistono due tipi di server che puoi avere:

  • Iterative Server- Questa è la forma più semplice di server in cui un processo server serve un client e dopo aver completato la prima richiesta, accetta la richiesta da un altro client. Nel frattempo, un altro cliente continua ad aspettare.

  • Concurrent Servers- Questo tipo di server esegue più processi simultanei per servire molte richieste alla volta perché un processo può richiedere più tempo e un altro client non può aspettare così a lungo. Il modo più semplice per scrivere un server simultaneo in Unix è eseguire il fork di un processo figlio per gestire separatamente ogni client.

Come rendere cliente

Le chiamate di sistema per stabilire una connessione sono in qualche modo diverse per il client e il server, ma entrambi coinvolgono il costrutto di base di un socket. Entrambi i processi stabiliscono i propri socket.

I passaggi necessari per stabilire un socket sul lato client sono i seguenti:

  • Crea un socket con l'estensione socket() chiamata di sistema.

  • Collegare il socket all'indirizzo del server utilizzando il connect() chiamata di sistema.

  • Invia e ricevi dati. Ci sono diversi modi per farlo, ma il modo più semplice è usare ilread() e write() chiamate di sistema.

Come creare un server

I passaggi necessari per stabilire un socket sul lato server sono i seguenti:

  • Crea un socket con l'estensione socket() chiamata di sistema.

  • Associare il socket a un indirizzo utilizzando il bind()chiamata di sistema. Per un socket del server su Internet, un indirizzo è costituito da un numero di porta sulla macchina host.

  • Ascolta i collegamenti con il listen() chiamata di sistema.

  • Accetta una connessione con accept()chiamata di sistema. Questa chiamata in genere blocca la connessione fino a quando un client non si connette al server.

  • Inviare e ricevere dati utilizzando il read() e write() chiamate di sistema.

Interazione tra client e server

Di seguito è riportato il diagramma che mostra l'intera interazione tra client e server:

Varie strutture sono usate nella programmazione Unix Socket per contenere informazioni su indirizzo e porta, e altre informazioni. La maggior parte delle funzioni socket richiede un puntatore a una struttura di indirizzi socket come argomento. Le strutture definite in questo capitolo sono correlate alla famiglia di protocolli Internet.

sockaddr

La prima struttura è sockaddr che contiene le informazioni sul socket -

struct sockaddr {
   unsigned short   sa_family;
   char             sa_data[14];
};

Questa è una struttura di indirizzi socket generica, che verrà passata nella maggior parte delle chiamate di funzione socket. La tabella seguente fornisce una descrizione dei campi dei membri:

Attributo Valori Descrizione
sa_family

AF_INET

AF_UNIX

AF_NS

AF_IMPLINK

Rappresenta una famiglia di indirizzi. Nella maggior parte delle applicazioni basate su Internet, utilizziamo AF_INET.
sa_data Indirizzo specifico del protocollo Il contenuto dei 14 byte dell'indirizzo specifico del protocollo viene interpretato in base al tipo di indirizzo. Per la famiglia Internet, useremo l'indirizzo IP del numero di porta, che è rappresentato dalla struttura sockaddr_in definita di seguito.

sockaddr in

La seconda struttura che ti aiuta a fare riferimento agli elementi del socket è la seguente:

struct sockaddr_in {
   short int            sin_family;
   unsigned short int   sin_port;
   struct in_addr       sin_addr;
   unsigned char        sin_zero[8];
};

Ecco la descrizione dei campi dei membri:

Attributo Valori Descrizione
sa_family

AF_INET

AF_UNIX

AF_NS

AF_IMPLINK

Rappresenta una famiglia di indirizzi. Nella maggior parte delle applicazioni basate su Internet, utilizziamo AF_INET.
sin_port Porta di servizio Un numero di porta a 16 bit in Network Byte Order.
sin_addr Indirizzo IP Un indirizzo IP a 32 bit in Network Byte Order.
sin_zero Non usato È sufficiente impostare questo valore su NULL poiché non viene utilizzato.

in addr

Questa struttura viene utilizzata solo nella struttura sopra come campo struttura e contiene netid / hostid a 32 bit.

struct in_addr {
   unsigned long s_addr;
};

Ecco la descrizione dei campi dei membri:

Attributo Valori Descrizione
s_addr porta di servizio Un indirizzo IP a 32 bit in Network Byte Order.

hostent

Questa struttura viene utilizzata per conservare le informazioni relative all'host.

struct hostent {
   char *h_name; 
   char **h_aliases; 
   int h_addrtype;  
   int h_length;    
   char **h_addr_list
	
#define h_addr  h_addr_list[0]
};

Ecco la descrizione dei campi dei membri:

Attributo Valori Descrizione
h_name ti.com ecc. È il nome ufficiale dell'ospite. Ad esempio, tutorialspoint.com, google.com, ecc.
h_aliases TI Contiene un elenco di alias del nome host.
h_addrtype AF_INET Contiene la famiglia di indirizzi e in caso di applicazione basata su Internet, sarà sempre AF_INET.
h_length 4 Contiene la lunghezza dell'indirizzo IP, che è 4 per l'indirizzo Internet.
h_addr_list in_addr Per gli indirizzi Internet, l'array di puntatori h_addr_list [0], h_addr_list [1] e così via sono punti alla struttura in_addr.

NOTE - h_addr è definito come h_addr_list [0] per mantenere la compatibilità con le versioni precedenti.

servo

Questa particolare struttura viene utilizzata per conservare le informazioni relative al servizio e alle porte associate.

struct servent {
   char  *s_name; 
   char  **s_aliases; 
   int   s_port;  
   char  *s_proto;
};

Ecco la descrizione dei campi dei membri:

Attributo Valori Descrizione
s_name http Questo è il nome ufficiale del servizio. Ad esempio, SMTP, FTP POP3, ecc.
s_aliases ALIAS Contiene l'elenco degli alias di servizio. Il più delle volte sarà impostato su NULL.
sport 80 Avrà un numero di porta associato. Ad esempio, per HTTP, questo sarà 80.
s_proto

TCP

UDP

È impostato sul protocollo utilizzato. I servizi Internet vengono forniti utilizzando TCP o UDP.

Suggerimenti sulle strutture socket

Le strutture degli indirizzi socket sono parte integrante di ogni programma di rete. Li allochiamo, li riempiamo e passiamo loro dei puntatori a varie funzioni socket. A volte passiamo un puntatore a una di queste strutture a una funzione socket e riempie il contenuto.

Passiamo sempre queste strutture per riferimento (cioè passiamo un puntatore alla struttura, non alla struttura stessa), e passiamo sempre la dimensione della struttura come un altro argomento.

Quando una funzione socket riempie una struttura, anche la lunghezza viene passata per riferimento, in modo che il suo valore possa essere aggiornato dalla funzione. Chiamiamo questi argomenti valore-risultato.

Sempre, imposta le variabili di struttura su NULL (cioè, '\ 0') usando memset () per le funzioni bzero (), altrimenti potrebbe ottenere valori spazzatura imprevisti nella tua struttura.

Quando un processo client desidera connettersi a un server, il client deve avere un modo per identificare il server a cui desidera connettersi. Se il client conosce l'indirizzo Internet a 32 bit dell'host su cui risiede il server, può contattare quell'host. Ma come fa il client a identificare il particolare processo del server in esecuzione su quell'host?

Per risolvere il problema di identificare un particolare processo server in esecuzione su un host, sia TCP che UDP hanno definito un gruppo di porte note.

Per il nostro scopo, una porta sarà definita come un numero intero compreso tra 1024 e 65535. Questo perché tutti i numeri di porta inferiori a 1024 sono considerati ben noti - ad esempio, telnet utilizza la porta 23, http utilizza 80, ftp utilizza 21, e così via.

Le assegnazioni delle porte ai servizi di rete possono essere trovate nel file / etc / services. Se stai scrivendo il tuo server, devi fare attenzione ad assegnare una porta al tuo server. È necessario assicurarsi che questa porta non venga assegnata a nessun altro server.

Normalmente è pratica assegnare a qualsiasi numero di porta più di 5000. Ma ci sono molte organizzazioni che hanno scritto server con numeri di porta superiori a 5000. Ad esempio, Yahoo Messenger gira su 5050, SIP Server gira su 5060, ecc.

Porte e servizi di esempio

Ecco un piccolo elenco di servizi e porte associate. Puoi trovare l'elenco più aggiornato delle porte Internet e dei servizi associati su IANA - Assegnazione delle porte TCP / IP .

Service Port Number Service Description
eco 7 UDP / TCP restituisce ciò che riceve.
scartare 9 UDP / TCP elimina l'input.
giorno 13 UDP / TCP restituisce l'ora ASCII.
chargen 19 UDP / TCP restituisce caratteri.
ftp 21 Trasferimento di file TCP.
telnet 23 Accesso remoto TCP.
smtp 25 Email TCP.
giorno 37 UDP / TCP restituisce l'ora binaria.
tftp 69 Trasferimento di file banale UDP.
dito 79 Informazioni TCP sugli utenti.
http 80 TCP World Wide Web.
accesso 513 Accesso remoto TCP.
chi 513 UDP diverse informazioni sugli utenti.
Xserver 6000 TCP X windows (NB> 1023).

Funzioni portuali e di servizio

Unix fornisce le seguenti funzioni per recuperare il nome del servizio dal file / etc / services.

  • struct servent *getservbyname(char *name, char *proto) - Questa chiamata prende il nome del servizio e il nome del protocollo e restituisce il numero di porta corrispondente per quel servizio.

  • struct servent *getservbyport(int port, char *proto) - Questa chiamata prende il numero di porta e il nome del protocollo e restituisce il nome del servizio corrispondente.

Il valore restituito per ogni funzione è un puntatore a una struttura con la seguente forma:

struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
};

Ecco la descrizione dei campi dei membri:

Attributo Valori Descrizione
s_name http È il nome ufficiale del servizio. Ad esempio, SMTP, FTP POP3, ecc.
s_aliases ALIAS Contiene l'elenco degli alias di servizio. La maggior parte delle volte sarà impostato su NULL.
sport 80 Avrà il numero di porta associato. Ad esempio, per HTTP, sarà 80.
s_proto

TCP

UDP

È impostato sul protocollo utilizzato. I servizi Internet vengono forniti utilizzando TCP o UDP.

Sfortunatamente, non tutti i computer memorizzano i byte che comprendono un valore multibyte nello stesso ordine. Considera una connessione Internet a 16 bit composta da 2 byte. Esistono due modi per memorizzare questo valore.

  • Little Endian - In questo schema, il byte di ordine inferiore viene memorizzato sull'indirizzo iniziale (A) e il byte di ordine superiore viene memorizzato sull'indirizzo successivo (A + 1).

  • Big Endian - In questo schema, il byte di ordine superiore viene memorizzato sull'indirizzo iniziale (A) e il byte di ordine inferiore viene memorizzato sull'indirizzo successivo (A + 1).

Per consentire la comunicazione tra macchine con convenzioni dell'ordine dei byte differenti, i protocolli Internet specificano una convenzione canonica dell'ordine dei byte per i dati trasmessi sulla rete. Questo è noto come Network Byte Order.

Quando si stabilisce una connessione socket Internet, è necessario assicurarsi che i dati nei membri sin_port e sin_addr della struttura sockaddr_in siano rappresentati in Network Byte Order.

Funzioni di ordinamento dei byte

Le routine per la conversione dei dati tra la rappresentazione interna di un host e Network Byte Order sono le seguenti:

Funzione Descrizione
htons () Host to Network Short
htonl () Host to Network Long
ntohl () Rete per ospitare a lungo
ntohs () Rete per ospitare breve

Di seguito sono elencati alcuni dettagli in più su queste funzioni:

  • unsigned short htons(unsigned short hostshort) - Questa funzione converte le quantità a 16 bit (2 byte) dall'ordine di byte dell'host all'ordine di byte di rete.

  • unsigned long htonl(unsigned long hostlong) - Questa funzione converte le quantità a 32 bit (4 byte) dall'ordine di byte dell'host all'ordine di byte di rete.

  • unsigned short ntohs(unsigned short netshort) - Questa funzione converte le quantità a 16 bit (2 byte) dall'ordine dei byte di rete all'ordine dei byte host.

  • unsigned long ntohl(unsigned long netlong) - Questa funzione converte le quantità a 32 bit dall'ordine dei byte di rete all'ordine dei byte host.

Queste funzioni sono macro e determinano l'inserimento del codice sorgente di conversione nel programma chiamante. Sulle macchine Little Endian, il codice cambierà i valori intorno all'ordine dei byte di rete. Sulle macchine big-endian, non viene inserito alcun codice poiché non è necessario; le funzioni sono definite come null.

Programma per determinare l'ordine dei byte host

Conserva il codice seguente in un file byteorder.c, quindi compilarlo ed eseguirlo sulla tua macchina.

In questo esempio, memorizziamo il valore a due byte 0x0102 nel numero intero corto e quindi guardiamo i due byte consecutivi, c [0] (l'indirizzo A) ec [1] (l'indirizzo A + 1) per determinare il byte ordine.

#include <stdio.h>

int main(int argc, char **argv) {

   union {
      short s;
      char c[sizeof(short)];
   }un;
	
   un.s = 0x0102;
   
   if (sizeof(short) == 2) {
      if (un.c[0] == 1 && un.c[1] == 2)
         printf("big-endian\n");
      
      else if (un.c[0] == 2 && un.c[1] == 1)
         printf("little-endian\n");
      
      else
         printf("unknown\n");
   }
   else {
      printf("sizeof(short) = %d\n", sizeof(short));
   }
	
   exit(0);
}

Un output generato da questo programma su una macchina Pentium è il seguente:

$> gcc byteorder.c $> ./a.out
little-endian
$>

Unix fornisce varie chiamate di funzione per aiutarti a manipolare gli indirizzi IP. Queste funzioni convertono gli indirizzi Internet tra stringhe ASCII (ciò che gli esseri umani preferiscono usare) e valori binari ordinati per byte di rete (valori memorizzati nelle strutture degli indirizzi socket).

Le seguenti tre chiamate di funzione vengono utilizzate per l'indirizzamento IPv4:

  • int inet_aton (const char * strptr, struct in_addr * addrptr)
  • in_addr_t inet_addr (const char * strptr)
  • char * inet_ntoa (struct in_addr inaddr)

int inet_aton (const char * strptr, struct in_addr * addrptr)

Questa chiamata di funzione converte la stringa specificata nella notazione punto standard Internet in un indirizzo di rete e memorizza l'indirizzo nella struttura fornita. L'indirizzo convertito sarà in Network Byte Order (byte ordinati da sinistra a destra). Restituisce 1 se la stringa era valida e 0 in caso di errore.

Di seguito è riportato l'esempio di utilizzo:

#include <arpa/inet.h>

(...)

   int retval;
   struct in_addr addrptr
   
   memset(&addrptr, '\0', sizeof(addrptr));
   retval = inet_aton("68.178.157.132", &addrptr);

(...)

in_addr_t inet_addr (const char * strptr)

Questa chiamata di funzione converte la stringa specificata nella notazione punto standard Internet in un valore intero adatto per l'uso come indirizzo Internet. L'indirizzo convertito sarà in Network Byte Order (byte ordinati da sinistra a destra). Restituisce un byte di rete binario a 32 bit ordinato indirizzo IPv4 e INADDR_NONE in caso di errore.

Di seguito è riportato l'esempio di utilizzo:

#include <arpa/inet.h>

(...)

   struct sockaddr_in dest;

   memset(&dest, '\0', sizeof(dest));
   dest.sin_addr.s_addr = inet_addr("68.178.157.132");
   
(...)

char * inet_ntoa (struct in_addr inaddr)

Questa chiamata di funzione converte l'indirizzo host Internet specificato in una stringa nella notazione punto standard Internet.

Di seguito è riportato l'esempio di utilizzo:

#include <arpa/inet.h>

(...)

   char *ip;
   
   ip = inet_ntoa(dest.sin_addr);
   
   printf("IP Address is: %s\n",ip);
   
(...)

Questo capitolo descrive le funzioni principali del socket richieste per scrivere un client e un server TCP completo.

Il diagramma seguente mostra l'interazione completa tra client e server:

La funzione socket

Per eseguire l'I / O di rete, la prima cosa che un processo deve fare è chiamare la funzione socket, specificando il tipo di protocollo di comunicazione desiderato e la famiglia di protocolli, ecc.

#include <sys/types.h>
#include <sys/socket.h>

int socket (int family, int type, int protocol);

Questa chiamata restituisce un descrittore di socket che puoi utilizzare nelle chiamate di sistema successive o -1 in caso di errore.

Parametri

family - Specifica la famiglia di protocollo ed è una delle costanti mostrate di seguito -

Famiglia Descrizione
AF_INET Protocolli IPv4
AF_INET6 Protocolli IPv6
AF_LOCAL Protocolli di dominio Unix
AF_ROUTE Prese di instradamento
AF_KEY Presa Ket

Questo capitolo non copre altri protocolli eccetto IPv4.

type- Specifica il tipo di presa che desideri. Può assumere uno dei seguenti valori:

genere Descrizione
SOCK_STREAM Presa di flusso
SOCK_DGRAM Presa datagramma
SOCK_SEQPACKET Presa di pacchetti sequenziata
SOCK_RAW Presa grezza

protocol - L'argomento deve essere impostato sul tipo di protocollo specifico indicato di seguito, o 0 per selezionare l'impostazione predefinita del sistema per la data combinazione di famiglia e tipo -

Protocollo Descrizione
IPPROTO_TCP Protocollo di trasporto TCP
IPPROTO_UDP Protocollo di trasporto UDP
IPPROTO_SCTP Protocollo di trasporto SCTP

La funzione di connessione

La funzione di connessione viene utilizzata da un client TCP per stabilire una connessione con un server TCP.

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

Questa chiamata restituisce 0 se si connette correttamente al server, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • serv_addr - È un puntatore a struct sockaddr che contiene l'indirizzo IP e la porta di destinazione.

  • addrlen - Impostalo su sizeof (struct sockaddr).

La funzione bind

La funzione bind assegna un indirizzo di protocollo locale a un socket. Con i protocolli Internet, l'indirizzo del protocollo è la combinazione di un indirizzo IPv4 a 32 bit o di un indirizzo IPv6 a 128 bit, insieme a un numero di porta TCP o UDP a 16 bit. Questa funzione viene chiamata solo dal server TCP.

#include <sys/types.h>
#include <sys/socket.h>

int bind(int sockfd, struct sockaddr *my_addr,int addrlen);

Questa chiamata restituisce 0 se si collega con successo all'indirizzo, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • my_addr - È un puntatore a struct sockaddr che contiene l'indirizzo IP e la porta locali.

  • addrlen - Impostalo su sizeof (struct sockaddr).

Puoi inserire automaticamente il tuo indirizzo IP e la tua porta

Un valore 0 per il numero di porta significa che il sistema sceglierà una porta casuale e il valore INADDR_ANY per l'indirizzo IP significa che l'indirizzo IP del server verrà assegnato automaticamente.

server.sin_port = 0;  		     
server.sin_addr.s_addr = INADDR_ANY;

NOTE- Tutte le porte inferiori a 1024 sono riservate. È possibile impostare una porta superiore a 1024 e inferiore a 65535 a meno che non siano quelle utilizzate da altri programmi.

La funzione di ascolto

La funzione di ascolto viene chiamata solo da un server TCP ed esegue due azioni:

  • La funzione di ascolto converte un socket non connesso in un socket passivo, indicando che il kernel dovrebbe accettare le richieste di connessione in entrata dirette a questo socket.

  • Il secondo argomento di questa funzione specifica il numero massimo di connessioni che il kernel deve mettere in coda per questo socket.

#include <sys/types.h>
#include <sys/socket.h>

int listen(int sockfd,int backlog);

Questa chiamata restituisce 0 in caso di successo, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • backlog - È il numero di connessioni consentite.

La funzione di accettazione

La funzione di accettazione viene chiamata da un server TCP per restituire la successiva connessione completata dalla parte anteriore della coda di connessione completata. La firma della chiamata è la seguente:

#include <sys/types.h>
#include <sys/socket.h>

int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

Questa chiamata restituisce un descrittore non negativo in caso di successo, altrimenti restituisce -1 in caso di errore. Si presume che il descrittore restituito sia un descrittore di socket client e tutte le operazioni di lettura-scrittura verranno eseguite su questo descrittore per comunicare con il client.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • cliaddr - È un puntatore a struct sockaddr che contiene l'indirizzo IP e la porta del client.

  • addrlen - Impostalo su sizeof (struct sockaddr).

La funzione di invio

La funzione di invio viene utilizzata per inviare dati tramite stream socket o socket datagram CONNECTED. Se vuoi inviare dati su socket datagram UNCONNECTED, devi usare la funzione sendto ().

È possibile utilizzare la chiamata di sistema write () per inviare i dati. La sua firma è la seguente:

int send(int sockfd, const void *msg, int len, int flags);

Questa chiamata restituisce il numero di byte inviati, altrimenti restituirà -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • msg - È un puntatore ai dati che vuoi inviare.

  • len - È la lunghezza dei dati che vuoi inviare (in byte).

  • flags - È impostato su 0.

La funzione recv

La funzione recv viene utilizzata per ricevere dati su stream socket o socket datagram CONNECTED. Se vuoi ricevere dati su socket datagramma UNCONNECTED devi usare recvfrom ().

È possibile utilizzare la chiamata di sistema read () per leggere i dati. Questa chiamata è spiegata nel capitolo sulle funzioni di supporto.

int recv(int sockfd, void *buf, int len, unsigned int flags);

Questa chiamata restituisce il numero di byte letti nel buffer, altrimenti restituirà -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • buf - È il buffer in cui leggere le informazioni.

  • len - È la lunghezza massima del buffer.

  • flags - È impostato su 0.

La funzione sendto

La funzione sendto viene utilizzata per inviare dati su socket datagramma UNCONNECTED. La sua firma è la seguente:

int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);

Questa chiamata restituisce il numero di byte inviati, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • msg - È un puntatore ai dati che vuoi inviare.

  • len - È la lunghezza dei dati che vuoi inviare (in byte).

  • flags - È impostato su 0.

  • to - È un puntatore a struct sockaddr per l'host a cui devono essere inviati i dati.

  • tolen - È impostato su sizeof (struct sockaddr).

La funzione recvfrom

La funzione recvfrom viene utilizzata per ricevere dati da socket datagrammi UNCONNECTED.

int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);

Questa chiamata restituisce il numero di byte letti nel buffer, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • buf - È il buffer in cui leggere le informazioni.

  • len - È la lunghezza massima del buffer.

  • flags - È impostato su 0.

  • from - È un puntatore a struct sockaddr per l'host in cui i dati devono essere letti.

  • fromlen - È impostato su sizeof (struct sockaddr).

La funzione di chiusura

La funzione di chiusura viene utilizzata per chiudere la comunicazione tra il client e il server. La sua sintassi è la seguente:

int close( int sockfd );

Questa chiamata restituisce 0 in caso di successo, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

La funzione di spegnimento

La funzione di arresto viene utilizzata per chiudere normalmente la comunicazione tra il client e il server. Questa funzione offre un maggiore controllo rispetto alla funzione di chiusura . Di seguito è riportata la sintassi dell'arresto :

int shutdown(int sockfd, int how);

Questa chiamata restituisce 0 in caso di successo, altrimenti restituisce -1 in caso di errore.

Parametri

  • sockfd - È un descrittore di socket restituito dalla funzione socket.

  • how - Metti uno dei numeri -

    • 0 - indica che la ricezione non è consentita,

    • 1 - indica che l'invio non è consentito e

    • 2- indica che sia l'invio che la ricezione non sono consentiti. Quando how è impostato su 2, è la stessa cosa di close ().

La funzione di selezione

La funzione di selezione indica quale dei descrittori di file specificati è pronto per la lettura, pronto per la scrittura o ha una condizione di errore in sospeso.

Quando un'applicazione chiama recv o recvfrom , viene bloccata finché non arrivano dati per quel socket. Un'applicazione potrebbe eseguire altre elaborazioni utili mentre il flusso di dati in entrata è vuoto. Un'altra situazione è quando un'applicazione riceve dati da più socket.

La chiamata a recv o recvfrom su un socket che non ha dati nella coda di input impedisce la ricezione immediata di dati da altri socket. La chiamata alla funzione select risolve questo problema consentendo al programma di interrogare tutti gli handle di socket per vedere se sono disponibili per operazioni di lettura e scrittura non bloccanti.

Di seguito è riportata la sintassi di select -

int select(int  nfds, fd_set  *readfds, fd_set  *writefds, fd_set *errorfds, struct timeval *timeout);

Questa chiamata restituisce 0 in caso di successo, altrimenti restituisce -1 in caso di errore.

Parametri

  • nfds- Specifica la gamma di descrittori di file da testare. La funzione select () verifica i descrittori di file nell'intervallo da 0 a nfds-1

  • readfds- Punta a un oggetto di tipo fd_set che in input specifica i descrittori di file da controllare per essere pronti per la lettura e in output indica quali descrittori di file sono pronti per la lettura. Può essere NULL per indicare un set vuoto.

  • writefds- Punta a un oggetto di tipo fd_set che in input specifica i descrittori di file da controllare per essere pronti per la scrittura e in output indica quali descrittori di file sono pronti per la scrittura. Può essere NULL per indicare un set vuoto.

  • exceptfds- Punta a un oggetto di tipo fd_set che in input specifica i descrittori di file da controllare per le condizioni di errore in sospeso e in output indica quali descrittori di file hanno condizioni di errore in sospeso. Può essere NULL per indicare un set vuoto.

  • timeout- Punta a una struttura temporale che specifica per quanto tempo la chiamata select deve interrogare i descrittori per un'operazione di I / O disponibile. Se il valore di timeout è 0, select tornerà immediatamente. Se l'argomento timeout è NULL, select si bloccherà fino a quando almeno un handle di file / socket sarà pronto per un'operazione di I / O disponibile. In caso contrario, select tornerà dopo che è trascorso il tempo nel timeout OPPURE quando almeno un descrittore di file / socket è pronto per un'operazione di I / O.

Il valore restituito da select è il numero di handle specificato nei set di descrittori di file pronti per l'I / O. Se viene raggiunto il limite di tempo specificato dal campo timeout, selezionare return 0. Esistono le seguenti macro per manipolare un set di descrittori di file:

  • FD_CLR(fd, &fdset)- Cancella il bit per il descrittore di file fd nel descrittore di file impostato fdset.

  • FD_ISSET(fd, &fdset)- Restituisce un valore diverso da zero se il bit per il descrittore di file fd è impostato nel set di descrittori di file puntato da fdset , e 0 altrimenti.

  • FD_SET(fd, &fdset) - Imposta il bit per il descrittore di file fd nel descrittore di file impostato fdset.

  • FD_ZERO(&fdset) - Inizializza il descrittore di file impostato fdset in modo che abbia zero bit per tutti i descrittori di file.

Il comportamento di queste macro non è definito se l'argomento fd è minore di 0 o maggiore o uguale a FD_SETSIZE.

Esempio

fd_set fds;

struct timeval tv;

/* do socket initialization etc.
tv.tv_sec = 1;
tv.tv_usec = 500000;

/* tv now represents 1.5 seconds */
FD_ZERO(&fds);

/* adds sock to the file descriptor set */
FD_SET(sock, &fds); 

/* wait 1.5 seconds for any data to be read from any single socket */
select(sock+1, &fds, NULL, NULL, &tv);

if (FD_ISSET(sock, &fds)) {
   recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
   /* do something */
}
else {
   /* do something else */
}

Questo capitolo descrive tutte le funzioni di supporto, che vengono utilizzate durante la programmazione dei socket. Altre funzioni di supporto sono descritte nei capitoli:Ports and Servicese Rete Byte Orders.

La funzione di scrittura

La funzione write tenta di scrivere nbyte byte dal buffer puntato da buf nel file associato al descrittore di file aperto, fildes .

È inoltre possibile utilizzare la funzione send () per inviare dati a un altro processo.

#include <unistd.h>

int write(int fildes, const void *buf, int nbyte);

Dopo il completamento con successo, write () restituisce il numero di byte effettivamente scritti nel file associato ai file. Questo numero non è mai maggiore di nbyte. In caso contrario, viene restituito -1.

Parametri

  • fildes - È un descrittore di socket restituito dalla funzione socket.

  • buf - È un puntatore ai dati che vuoi inviare.

  • nbyte- È il numero di byte da scrivere. Se nbyte è 0, write () restituirà 0 e non avrà altri risultati se il file è un file normale; in caso contrario, i risultati non sono specificati.

La funzione di lettura

La funzione read tenta di leggere nbyte byte dal file associato al buffer, fildes, nel buffer puntato da buf.

È inoltre possibile utilizzare la funzione recv () per leggere i dati in un altro processo.

#include <unistd.h>

int read(int fildes, const void *buf, int nbyte);

Dopo il completamento con successo, write () restituisce il numero di byte effettivamente scritti nel file associato ai file. Questo numero non è mai maggiore di nbyte. In caso contrario, viene restituito -1.

Parametri

  • fildes - È un descrittore di socket restituito dalla funzione socket.

  • buf - È il buffer in cui leggere le informazioni.

  • nbyte - È il numero di byte da leggere.

La funzione della forcella

La funzione fork crea un nuovo processo. Il nuovo processo chiamato processo figlio sarà una copia esatta del processo chiamante (processo genitore). Il processo figlio eredita molti attributi dal processo padre.

#include <sys/types.h>
#include <unistd.h>

int fork(void);

Dopo il completamento con successo, fork () restituisce 0 al processo figlio e l'ID del processo figlio al processo genitore. Altrimenti viene restituito -1 al processo genitore, nessun processo figlio viene creato e errno viene impostato per indicare l'errore.

Parametri

  • void - Significa che non è richiesto alcun parametro.

La funzione bzero

La funzione bzero inserisce nbyte byte nulli nella stringa s . Questa funzione viene utilizzata per impostare tutte le strutture socket con valori nulli.

void bzero(void *s, int nbyte);

Questa funzione non restituisce nulla.

Parametri

  • s- Specifica la stringa che deve essere riempita con byte nulli. Questo sarà un punto per la variabile della struttura del socket.

  • nbyte- Specifica il numero di byte da riempire con valori nulli. Questa sarà la dimensione della struttura del socket.

La funzione bcmp

La funzione bcmp confronta la stringa di byte s1 con la stringa di byte s2. Si presume che entrambe le stringhe siano lunghe nbyte byte.

int bcmp(const void *s1, const void *s2, int nbyte);

Questa funzione restituisce 0 se entrambe le stringhe sono identiche, 1 altrimenti. La funzione bcmp () restituisce sempre 0 quando nbyte è 0.

Parametri

  • s1 - Specifica la prima stringa da confrontare.

  • s2 - Specifica la seconda stringa da confrontare.

  • nbyte - Specifica il numero di byte da confrontare.

La funzione bcopy

La funzione bcopy copia nbyte byte dalla stringa s1 alla stringa s2. Le stringhe sovrapposte vengono gestite correttamente.

void bcopy(const void *s1, void *s2, int nbyte);

Questa funzione non restituisce nulla.

Parametri

  • s1 - Specifica la stringa di origine.

  • s2v - Specifica la stringa di destinazione.

  • nbyte - Specifica il numero di byte da copiare.

La funzione memset

La funzione memset viene utilizzata anche per impostare le variabili di struttura allo stesso modo dibzero. Dai un'occhiata alla sua sintassi, fornita di seguito.

void *memset(void *s, int c, int nbyte);

Questa funzione restituisce un puntatore a void; in effetti, un puntatore alla memoria impostata e devi castrarlo di conseguenza.

Parametri

  • s - Specifica la sorgente da impostare.

  • c - Specifica il carattere da impostare sui posti nbyte.

  • nbyte - Specifica il numero di byte da impostare.

Per rendere un processo un server TCP, è necessario seguire i passaggi indicati di seguito:

  • Crea un socket con la chiamata di sistema socket () .

  • Associare il socket a un indirizzo utilizzando la chiamata di sistema bind () . Per un socket del server su Internet, un indirizzo è costituito da un numero di porta sulla macchina host.

  • Ascolta le connessioni con la chiamata di sistema listen () .

  • Accetta una connessione con la chiamata di sistema accept () . Questa chiamata in genere si blocca finché un client non si connette al server.

  • Inviare e ricevere dati utilizzando le chiamate di sistema read () e write () .

Ora mettiamo questi passaggi sotto forma di codice sorgente. Metti questo codice nel file server.c e compilarlo con il compilatore gcc .

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int  n;
   
   /* First call to socket() function */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
   
   /* Initialize socket structure */
   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = 5001;
   
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY;
   serv_addr.sin_port = htons(portno);
   
   /* Now bind the host address using bind() call.*/
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR on binding");
      exit(1);
   }
      
   /* Now start listening for the clients, here process will
      * go in sleep mode and will wait for the incoming connection
   */
   
   listen(sockfd,5);
   clilen = sizeof(cli_addr);
   
   /* Accept actual connection from the client */
   newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
	
   if (newsockfd < 0) {
      perror("ERROR on accept");
      exit(1);
   }
   
   /* If connection is established then start communicating */
   bzero(buffer,256);
   n = read( newsockfd,buffer,255 );
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
   
   printf("Here is the message: %s\n",buffer);
   
   /* Write a response to the client */
   n = write(newsockfd,"I got your message",18);
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
      
   return 0;
}

Gestire più connessioni

Per consentire al server di gestire più connessioni simultanee, apportiamo le seguenti modifiche al codice precedente:

  • Inserire l' istruzione di accettazione e il codice seguente in un ciclo infinito.

  • Dopo aver stabilito una connessione, chiama fork () per creare un nuovo processo.

  • Il processo figlio chiuderà sockfd e chiamerà la funzione doprocessing , passando il nuovo descrittore di file socket come argomento. Quando i due processi hanno completato la loro conversazione, come indicato dal ritorno di doprocessing () , questo processo termina semplicemente.

  • Il processo genitore chiude newsockfd . Poiché tutto questo codice è in un ciclo infinito, tornerà all'istruzione di accettazione per attendere la connessione successiva.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

void doprocessing (int sock);

int main( int argc, char *argv[] ) {
   int sockfd, newsockfd, portno, clilen;
   char buffer[256];
   struct sockaddr_in serv_addr, cli_addr;
   int n, pid;
   
   /* First call to socket() function */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
   
   /* Initialize socket structure */
   bzero((char *) &serv_addr, sizeof(serv_addr));
   portno = 5001;
   
   serv_addr.sin_family = AF_INET;
   serv_addr.sin_addr.s_addr = INADDR_ANY;
   serv_addr.sin_port = htons(portno);
   
   /* Now bind the host address using bind() call.*/
   if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR on binding");
      exit(1);
   }
   
   /* Now start listening for the clients, here
      * process will go in sleep mode and will wait
      * for the incoming connection
   */
   
   listen(sockfd,5);
   clilen = sizeof(cli_addr);
   
   while (1) {
      newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
		
      if (newsockfd < 0) {
         perror("ERROR on accept");
         exit(1);
      }
      
      /* Create child process */
      pid = fork();
		
      if (pid < 0) {
         perror("ERROR on fork");
         exit(1);
      }
      
      if (pid == 0) {
         /* This is the client process */
         close(sockfd);
         doprocessing(newsockfd);
         exit(0);
      }
      else {
         close(newsockfd);
      }
		
   } /* end of while */
}

La seguente sequenza di codice mostra una semplice implementazione della funzione di doprocessing .

void doprocessing (int sock) {
   int n;
   char buffer[256];
   bzero(buffer,256);
   n = read(sock,buffer,255);
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
   
   printf("Here is the message: %s\n",buffer);
   n = write(sock,"I got your message",18);
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
	
}

Per rendere un processo un client TCP, devi seguire i passaggi indicati di seguito & meno;

  • Crea un socket con la chiamata di sistema socket () .

  • Connetti il ​​socket all'indirizzo del server usando la chiamata di sistema connect () .

  • Invia e ricevi dati. Ci sono molti modi per farlo, ma il modo più semplice è usare le chiamate di sistema read () e write () .

Ora mettiamo questi passaggi sotto forma di codice sorgente. Inserisci questo codice nel fileclient.c e compilarlo con gcc compilatore.

Esegui questo programma e passa il nome host e il numero di porta del server, per connetterti al server, che devi già aver eseguito in un'altra finestra Unix.

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

int main(int argc, char *argv[]) {
   int sockfd, portno, n;
   struct sockaddr_in serv_addr;
   struct hostent *server;
   
   char buffer[256];
   
   if (argc < 3) {
      fprintf(stderr,"usage %s hostname port\n", argv[0]);
      exit(0);
   }
	
   portno = atoi(argv[2]);
   
   /* Create a socket point */
   sockfd = socket(AF_INET, SOCK_STREAM, 0);
   
   if (sockfd < 0) {
      perror("ERROR opening socket");
      exit(1);
   }
	
   server = gethostbyname(argv[1]);
   
   if (server == NULL) {
      fprintf(stderr,"ERROR, no such host\n");
      exit(0);
   }
   
   bzero((char *) &serv_addr, sizeof(serv_addr));
   serv_addr.sin_family = AF_INET;
   bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
   serv_addr.sin_port = htons(portno);
   
   /* Now connect to the server */
   if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
      perror("ERROR connecting");
      exit(1);
   }
   
   /* Now ask for a message from the user, this message
      * will be read by server
   */
	
   printf("Please enter the message: ");
   bzero(buffer,256);
   fgets(buffer,255,stdin);
   
   /* Send message to the server */
   n = write(sockfd, buffer, strlen(buffer));
   
   if (n < 0) {
      perror("ERROR writing to socket");
      exit(1);
   }
   
   /* Now read server response */
   bzero(buffer,256);
   n = read(sockfd, buffer, 255);
   
   if (n < 0) {
      perror("ERROR reading from socket");
      exit(1);
   }
	
   printf("%s\n",buffer);
   return 0;
}

Di seguito è riportato un elenco di tutte le funzioni relative alla programmazione dei socket.

Funzioni portuali e di servizio

Unix fornisce le seguenti funzioni per recuperare il nome del servizio dal file / etc / services.

  • struct servent *getservbyname(char *name, char *proto) - Questa chiamata prende un nome di servizio e un nome di protocollo e restituisce il numero di porta corrispondente per quel servizio.

  • struct servent *getservbyport(int port, char *proto) - Questa chiamata accetta un numero di porta e un nome di protocollo e restituisce il nome del servizio corrispondente.

Funzioni di ordinamento dei byte

  • unsigned short htons (unsigned short hostshort) - Questa funzione converte le quantità a 16 bit (2 byte) dall'ordine di byte dell'host all'ordine di byte di rete.

  • unsigned long htonl (unsigned long hostlong) - Questa funzione converte le quantità a 32 bit (4 byte) dall'ordine di byte dell'host all'ordine di byte di rete.

  • unsigned short ntohs (unsigned short netshort) - Questa funzione converte le quantità a 16 bit (2 byte) dall'ordine dei byte di rete all'ordine dei byte host.

  • unsigned long ntohl (unsigned long netlong) - Questa funzione converte le quantità a 32 bit dall'ordine dei byte di rete all'ordine dei byte host.

Funzioni di indirizzo IP

  • int inet_aton (const char *strptr, struct in_addr *addrptr)- Questa chiamata di funzione converte la stringa specificata, nella notazione punto standard Internet, in un indirizzo di rete e memorizza l'indirizzo nella struttura fornita. L'indirizzo convertito sarà in Network Byte Order (byte ordinati da sinistra a destra). Restituisce 1 se la stringa è valida e 0 in caso di errore.

  • in_addr_t inet_addr (const char *strptr)- Questa chiamata di funzione converte la stringa specificata, nella notazione punto standard Internet, in un valore intero adatto per essere utilizzato come indirizzo Internet. L'indirizzo convertito sarà in Network Byte Order (byte ordinati da sinistra a destra). Restituisce un byte di rete binario a 32 bit ordinato indirizzo IPv4 e INADDR_NONE in caso di errore.

  • char *inet_ntoa (struct in_addr inaddr) - Questa chiamata di funzione converte l'indirizzo host Internet specificato in una stringa nella notazione punto standard Internet.

Funzioni di base del socket

  • int socket (int family, int type, int protocol) - Questa chiamata restituisce un descrittore di socket che puoi usare nelle successive chiamate di sistema o ti dà -1 in caso di errore.

  • int connect (int sockfd, struct sockaddr *serv_addr, int addrlen)- La funzione di connessione viene utilizzata da un client TCP per stabilire una connessione con un server TCP. Questa chiamata restituisce 0 se si connette correttamente al server, altrimenti restituisce -1.

  • int bind(int sockfd, struct sockaddr *my_addr,int addrlen)- La funzione bind assegna un indirizzo di protocollo locale a un socket. Questa chiamata restituisce 0 se si lega correttamente all'indirizzo, altrimenti restituisce -1.

  • int listen(int sockfd, int backlog)- La funzione di ascolto viene chiamata solo da un server TCP per ascoltare la richiesta del client. Questa chiamata restituisce 0 in caso di successo, altrimenti restituisce -1.

  • int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)- La funzione di accettazione viene chiamata da un server TCP per accettare le richieste del client e per stabilire una connessione effettiva. Questa chiamata restituisce un descrittore non negativo in caso di successo, altrimenti restituisce -1.

  • int send(int sockfd, const void *msg, int len, int flags)- La funzione di invio viene utilizzata per inviare dati su stream socket o socket datagram CONNECTED. Questa chiamata restituisce il numero di byte inviati, altrimenti restituisce -1.

  • int recv (int sockfd, void *buf, int len, unsigned int flags)- La funzione recv viene utilizzata per ricevere dati su stream socket o socket datagram CONNECTED. Questa chiamata restituisce il numero di byte letti nel buffer, altrimenti restituisce -1 in caso di errore.

  • int sendto (int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen)- La funzione sendto viene utilizzata per inviare dati su socket datagrammi NON CONNESSI. Questa chiamata restituisce il numero di byte inviati, altrimenti restituisce -1 in caso di errore.

  • int recvfrom (int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen)- La funzione recvfrom viene utilizzata per ricevere dati da socket datagrammi NON CONNESSI. Questa chiamata restituisce il numero di byte letti nel buffer, altrimenti restituisce -1 in caso di errore.

  • int close (int sockfd)- La funzione di chiusura viene utilizzata per chiudere una comunicazione tra il client e il server. Questa chiamata restituisce 0 in caso di successo, altrimenti restituisce -1.

  • int shutdown (int sockfd, int how)- La funzione di arresto viene utilizzata per chiudere correttamente una comunicazione tra il client e il server. Questa funzione offre un maggiore controllo rispetto alla funzione di chiusura. Restituisce 0 in caso di successo, -1 in caso contrario.

  • int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) - Questa funzione viene utilizzata per leggere o scrivere più socket.

Funzioni di Socket Helper

  • int write (int fildes, const void *buf, int nbyte)- La funzione write tenta di scrivere nbyte byte dal buffer puntato da buf nel file associato al descrittore di file aperto, fildes. Dopo il completamento con successo, write () restituisce il numero di byte effettivamente scritti nel file associato ai file. Questo numero non è mai maggiore di nbyte. In caso contrario, viene restituito -1.

  • int read (int fildes, const void *buf, int nbyte)- La funzione read tenta di leggere nbyte byte dal file associato al descrittore di file aperto, fildes, nel buffer puntato da buf. Dopo il completamento con successo, write () restituisce il numero di byte effettivamente scritti nel file associato ai file. Questo numero non è mai maggiore di nbyte. In caso contrario, viene restituito -1.

  • int fork (void)- La funzione fork crea un nuovo processo. Il nuovo processo, chiamato processo figlio, sarà una copia esatta del processo chiamante (processo genitore).

  • void bzero (void *s, int nbyte)- La funzione bzero inserisce nbyte byte nulli nella stringa s. Questa funzione verrà utilizzata per impostare tutte le strutture socket con valori nulli.

  • int bcmp (const void *s1, const void *s2, int nbyte)- La funzione bcmp confronta la stringa di byte s1 con la stringa di byte s2. Si presume che entrambe le stringhe siano lunghe nbyte byte.

  • void bcopy (const void *s1, void *s2, int nbyte)- La funzione bcopy copia nbyte byte dalla stringa s1 alla stringa s2. Le stringhe sovrapposte vengono gestite correttamente.

  • void *memset(void *s, int c, int nbyte) - La funzione memset viene utilizzata anche per impostare le variabili di struttura allo stesso modo di bzero.