library - gcc socket example



Fa sin_addr.s_addr=INADDR_ANY; hai bisogno di niente? (5)

Mi sono imbattuto in due thread:

Presa con recv-timeout: cosa c'è di sbagliato in questo codice?

Lettura / Scrittura su un socket utilizzando un flusso FILE in c

uno usa htonl e l'altro no.

Quale è giusto?

https://src-bin.com


Answer #1

Di solito non mi piace rispondere quando c'è già una risposta "decente". In questo caso, farò un'eccezione perché le informazioni che ho aggiunto a queste risposte sono state fraintese.

INADDR_ANY è definito come indirizzo IPv4 a zero bit, 0.0.0.0 o 0x00000000 . Chiamando htonl() su questo valore si otterrà lo stesso valore, zero. Pertanto, chiamare htonl() su questo valore costante non è tecnicamente necessario.

INADDR_ALL è definito come indirizzo IPv4 a tutti i bit, 255.255.255.255 o 0xFFFFFFFF . Chiamando htonl() con INADDR_ALL si restituirà INADDR_ALL . Ancora una volta, chiamare htonl() non è tecnicamente necessario.

Un'altra costante definita nei file di intestazione è INADDR_LOOPBACK , definita come 127.0.0.1 o 0x7F000001 . Questo indirizzo è fornito in ordine di byte di rete e non può essere passato all'interfaccia socket senza htonl() . Devi usare htonl() con questa costante.

Alcuni suggeriscono che la coerenza e la leggibilità del codice richiedono che i programmatori utilizzino htonl() per ogni costante denominata INADDR_* - perché è necessario per alcuni di essi. Questi poster sono sbagliati.

Un esempio dato in questa discussione è:

if (some_condition)
    sa.s_addr = htonl(INADDR_LOOPBACK);
else
    sa.s_addr = INADDR_ANY;

Citando da "John Zwinck":

"Se dovessi rivedere questo codice, metterei subito in discussione il motivo per cui una delle costanti ha applicato Htonl e l'altra no. E lo denuncio come un bug, sia che mi sia capitato di avere la" conoscenza interna "che INADDR_ANY è sempre 0 convertirlo è un no-op. E penso (e spero) che molti altri manutentori farebbero lo stesso. "

Se ricevessi una segnalazione di questo tipo, la butterei immediatamente via. Questo processo mi farebbe risparmiare un sacco di tempo, mettendo in campo segnalazioni di bug da persone che non hanno la "conoscenza minima di base" che INADDR_ANY è sempre 0. (Suggerendo che conoscere i valori di INADDR_ANY e altri in qualche modo viola l'incapsulamento o qualunque altra cosa è un'altra non-starter - gli stessi numeri sono usati nell'output netcat e all'interno del kernel.I programmatori devono conoscere i valori numerici effettivi.Le persone che non sanno non mancano di conoscenza, mancano di conoscenza di base dell'area. )

In realtà, se si ha un programmatore che gestisce il codice socket, e il programmatore non conosce i pattern di bit di INADDR_ANY e INADDR_ALL, si è già nei guai. Il wrapping di 0 in una macro che restituisce 0 è il tipo di mentalità che è schiavo della coerenza senza senso e non rispetta la conoscenza del dominio.

Mantenere il codice socket è più che capire C. Se non si capisce la differenza tra INADDR_LOOPBACK e INADDR_ANY ad un livello compatibile con l'output netstat , allora si è pericolosi in quel codice e non si dovrebbe cambiarlo.

Argomenti di paglia proposti da Zwinck per l'uso inutile di htonl() :

  1. Può offendere i programmatori di socket esperti di usare htonl perché sapranno che non fa nulla (dal momento che conoscono a memoria il valore della costante).

Questo è un argomento di scarsa importanza perché abbiamo una rappresentazione che i programmatori di socket hanno conosciuto a memoria il valore di INADDR_ANY . È come scrivere che solo un programmatore C esperto conosce a memoria il valore di NULL . Scrivere "a memoria" dà l'impressione che il numero sia leggermente difficile da memorizzare, forse qualche cifra, come 127.0.0.1 . Ma no, stiamo discutendo in modo iperbolico sulla difficoltà di memorizzare i pattern denominati "all zero bits" e "all one bit bit".

Considerando che questi valori numerici compaiono nell'output di, ad esempio, netstat e altre utilità di sistema, e anche considerando che alcuni di questi valori appaiono nelle intestazioni IP, non esiste un programmatore di socket competente che non conosca questi valori, sia a memoria o dal cervello. In effetti, tentare di programmare le prese senza conoscere queste basi può essere pericoloso per la disponibilità della rete.

  1. Richiede meno digitazioni per ometterlo.

Questo argomento è destinato ad essere assurdo e sprezzante, quindi non ha bisogno di molto confutare.

  1. Ottimizzazione "performance" fasulla (chiaramente non importa).

È difficile sapere da dove viene questa argomentazione. Potrebbe essere un tentativo di fornire argomenti stupidi all'opposizione. In ogni caso, non usare la macro htonl() non fa alcuna differenza quando si fornisce una costante e si usa un tipico compilatore C - le espressioni costanti sono ridotte ad una costante in entrambi i casi.

Un motivo per non utilizzare htonl() con INADDR_ANY è che il programmatore di socket più esperto sa che non è necessario. Cosa c'è di più: quei programmatori che non sanno devono imparare. Non vi è alcun "costo" aggiuntivo con l'uso di htonl() , il problema è il costo di stabilire uno standard di codifica che favorisca l'ignoranza di tali valori di importanza critica.

Per definizione, l'incapsulamento favorisce l'ignoranza. Quella stessa ignoranza è il solito beneficio dell'uso di un'interfaccia incapsulata: la conoscenza è costosa e finita, quindi l'incapsulamento è generalmente buono. La domanda diventa: quali sforzi di programmazione vengono migliorati meglio tramite l'incapsulamento? Ci sono attività di programmazione che sono disattese dall'incapsulamento?

Non è tecnicamente scorretto usare htonl() , perché non ha alcun effetto su questo valore. Tuttavia, gli argomenti che dovresti usare potrebbero essere fuorvianti.

Ci sono quelli che sostengono che una situazione migliore sarebbe quella in cui lo sviluppatore non ha bisogno di sapere che INADDR_ANY è tutti zeri e così via. Questa terra di ignoranza è peggio, non migliore. Considera che questi "valori magici" sono usati in varie interfacce con TCP / IP. Ad esempio, quando si configura Apache, se si desidera ascoltare solo IPv4 (e non IPv6), è necessario specificare:

Listen 0.0.0.0:80

Ho incontrato programmatori che erroneamente hanno fornito l'indirizzo IP locale anziché INADDR_ANY (0.0.0.0) sopra. Questi programmatori non sanno cosa sia INADDR_ANY , e probabilmente lo racchiudono in htonl() mentre ci sono. Questa è la terra dell'astinenza-pensiero e incapsulamento.

Le idee di "incapsulamento" e "astrazione" sono state ampiamente accettate e applicate troppo ampiamente, ma non sempre si applicano. Nel dominio dell'indirizzamento IPv4, non è appropriato trattare questi valori costanti come "astratti" - vengono convertiti direttamente in bit sul filo.

Il mio punto è questo: non esiste un uso "corretto" di INADDR_ANY con htonl() - entrambi sono equivalenti. Non consiglierei di adottare un requisito per utilizzare il valore in un modo particolare, poiché la famiglia di costanti INADDR_X solo quattro membri e solo uno di essi, INADDR_LOOPBACK ha un valore diverso a seconda dell'ordine dei byte. È meglio conoscere questo fatto piuttosto che stabilire uno standard per l'utilizzo dei valori che trasformano un "occhio cieco" nei modelli di bit dei valori.

In molte altre API, è utile che i programmatori procedano senza conoscere il valore numerico oi modelli di bit delle costanti utilizzate dalle API. Nel caso dell'API socket, questi modelli di bit e valori vengono utilizzati come input e visualizzati in modo pervasivo. È meglio conoscere questi valori numericamente piuttosto che sprecare tempo pensando di utilizzare htonl() su di essi.

Quando si programmano in C, in particolare, la maggior parte "uso" dell'API socket implica l'acquisizione del codice sorgente di un'altra persona e l'adattamento. Questo è un altro motivo per cui è così importante sapere cosa INADDR_ANY è prima di toccare una linea che lo utilizza.


Answer #2

Né è giusto , nel senso che sia INADDR_ANY che htonl sono deprecati e portano a un codice complesso e brutto che funziona solo con IPv4. Passa all'utilizzo di getaddrinfo per tutte le esigenze di creazione degli indirizzi del tuo socket:

struct addrinfo *ai, hints = { .ai_flags = AI_PASSIVE|AI_ADDRCONFIG };
getaddrinfo(0, "1234", &hints, &ai);

Sostituire "1234" con il numero di porta o il nome del servizio.


Answer #3

Riassumiamolo un po ', poiché nessuna delle risposte precedenti sembra essere aggiornata e potrei non essere l'ultima persona che vedrà questa pagina di domande. Ci sono state opinioni sia a favore che contro l'uso di htonl attorno alla costante INADDR_ANY o evitandola del tutto.

Al giorno d'oggi (ed è ormai da un po 'di tempo ormai) le librerie di sistema sono per la maggior parte pronte per IPv6, quindi usiamo IPv4 e IPv6. La situazione con IPv6 è molto più semplice in quanto le strutture e le costanti di dati non soffrono dell'ordine dei byte. Uno userebbe 'in6addr_any' e 'in6addr_loopback' (entrambi in struct in6_addr type) ed entrambi sono oggetti costanti nell'ordine dei byte di rete.

Scopri perché IPv6 non soffre dello stesso problema (se gli indirizzi IPv4 fossero definiti come array a quattro byte non ne risentirebbero neanche):

struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};

struct in6_addr {
    unsigned char   s6_addr[16];   /* IPv6 address */
};

Per IPv4, sarebbe bello avere anche le costanti 'inaddr_any' e 'inaddr_loopback' come 'struct in_addr' (in modo che possano anche essere confrontate con memcmp o copiate con memcpy). In effetti potrebbe essere una buona idea crearli nel tuo programma in quanto non sono forniti da glibc e da altre librerie:

const struct in_addr inaddr_loopback = { htonl(INADDR_LOOPBACK) };

Con glibc, questo funziona solo per me all'interno di una funzione (e non riesco a renderlo static ), poiché htonl non è una macro ma una funzione ordinaria.

Il problema è che glibc (contrariamente a quanto affermato in altre risposte) non fornisce htonl come macro ma piuttosto come una funzione. Quindi dovresti:

static const struct in_addr inaddr_any = { 0 };
#if BYTE_ORDER == BIG_ENDIAN
static const struct in_addr inaddr_loopback = { 0x7f000001 };
#elif BYTE_ORDER == LITTLE_ENDIAN
static const struct in_addr inaddr_loopback = { 0x0100007f };
#else
    #error Neither big endian nor little endian
#endif

Sarebbe una bella aggiunta alle intestazioni e quindi potresti lavorare con le costanti IPv4 il più facilmente possibile con IPv6.

Ma poi per implementarlo, ho dovuto usare delle costanti per inizializzarlo. Quando conosco esattamente i rispettivi byte, non ho bisogno di costanti. Proprio come alcune persone sostengono che htonl() è ridondante per una costante che valuta zero, chiunque altro potrebbe affermare che anche la costante stessa è ridondante. E avrebbe ragione.

Nel codice preferisco essere esplicito che implicito. Pertanto se tali costanti (come INADDR_ANY, INADDR_ALL, INADDR_LOOPBACK) sono tutte coerentemente nell'ordine di byte host, allora è corretto solo se le trattate in questo modo. Vedi per esempio (quando non usi la costante sopra):

struct in_addr address4 = { htonl(use_loopback ? INADDR_LOOPBACK : INADDR_ANY };

Ovviamente si potrebbe dire che non è necessario chiamare htonl per INADDR_ANY e quindi è possibile:

struct in_addr address4 = { use_loopback ? htonl(INADDR_LOOPBACK) : INADDR_ANY };

Ma poi quando si ignora l'ordine dei byte della costante perché è zero in ogni caso, allora non vedo alcuna logica nell'usare la costante. Lo stesso vale per INADDR_ALL, poiché è facile digitare anche 0xffffffff;

Un altro modo per aggirarlo è evitare di impostare questi valori direttamente insieme:

struct in_addr address4;

inet_pton(AF_INET, "127.0.0.1", &address4);

Questo aggiunge un po 'di elaborazione inutile ma non ha problemi di ordine dei byte ed è praticamente lo stesso per IPv4 e IPv6 (basta cambiare la stringa dell'indirizzo).

Ma la domanda è: perché lo fai? Se vuoi connect() a IPv4 localhost (ma a volte a IPv6 localhost, o qualsiasi altro hostname), getaddrinfo () (menzionato in una delle risposte) è molto meglio per questo, come:

  1. È una funzione utilizzata per tradurre qualsiasi hostname / servizio / famiglia / socktype / protocollo a in un elenco di record di struct addrinfo corrispondenti.

  2. Ogni struct addrinfo include un puntatore polimorfico a struct sockaddr che è possibile utilizzare direttamente con connect() . Quindi non devi preoccuparti della costruzione di struct sockaddr_in , typecasting (tramite un puntatore) per struct sockaddr , ecc.

    struct addrinfo * ai, hints = {.ai_family = AF_INET}; getaddrinfo (0, "1234", & hints, & ai);

    registra che a sua volta include le struct sockaddr polimorfiche della struct sockaddr puntatori struct sockaddr cui hai bisogno per la chiamata connect() .

Quindi, la conclusione è:

1) L'API standard non riesce a fornire le struct in_addr direttamente utilizzabili (invece fornisce costanti intere senza segno piuttosto inutili nell'ordine host).

struct addrinfo *ai, hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_TCP };
int error;

error = getaddrinfo(NULL, 80, &hints, &ai);
if (error)
    ...

for (item = result; item; item = item->ai_next) {
    sock = socket(item->ai_family, item->ai_socktype, item->ai_protocol);

    if (sock == -1)
        continue;

    if (connect(sock, item->ai_addr, item->ai_addrlen) != -1) {
        fprintf(stderr, "Connected successfully.");
        break;
    }

    close(sock);
}

Quando sei sicuro che la tua query sia abbastanza selettiva da restituire solo un risultato, puoi fare (omettendo la gestione degli errori per brevità) quanto segue:

struct *result, hints = { .ai_family = AF_INET, .ai_protocol = IPPROTO_TCP };
getaddrinfo(NULL, 80, &hints, &ai);
sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
connect(sock, result->ai_addr, result->ai_addrlen);

Se hai paura che getaddrinfo() possa essere molto più lento di usare le costanti, la libreria di sistema è il posto migliore per risolverlo. Una buona implementazione restituirà solo l'indirizzo di loopback richiesto quando il service è nullo e viene impostato hints.ai_family .


Answer #4

Stavo per aggiungere questo come un commento, ma è diventato un po 'prolisso ...

Penso che sia chiaro dalle risposte e dal commento che htonl() deve essere usato su queste costanti (anche se chiamarlo su INADDR_ANY e INADDR_NONE equivale a no-ops). Il problema che vedo da dove sorge la confusione è che non è esplicitamente richiamato nella documentazione - qualcuno per favore correggimi se mi mancasse semplicemente, ma non l'ho visto nelle pagine man, né nell'intestazione include dove esplicitamente afferma che le definizioni per INADDR_* sono nell'ordine host. Anche in questo caso, non è un grosso problema per INADDR_ANY , INADDR_NONE e INADDR_BROADCAST , ma è significativo per INADDR_LOOPBACK .

Ora, ho fatto un bel po 'di socket di basso livello in C, ma l'indirizzo di loopback raramente, se mai, viene usato nel mio codice. Anche se questo argomento ha più di un anno, questo stesso problema è balzato in alto per mordermi in ritardo oggi, ed è stato perché ho assunto erroneamente che gli indirizzi definiti nell'intestazione include sono in ordine di rete. Non sono sicuro del motivo per cui ho avuto questa idea - probabilmente perché la struttura in_addr deve avere l'indirizzo nell'ordine della rete, inet_aton e inet_addr restituiscono i loro valori nell'ordine della rete, e quindi la mia ipotesi logica era che queste costanti fossero utilizzabili così com'è. Lanciare insieme un rapido 5-liner per testare che la teoria mi mostrasse il contrario. Se qualcuno dei poteri si accorge di vederlo, farei il suggerimento di richiamare esplicitamente che i valori sono, in effetti, nell'ordine host, non nell'ordine di rete, e che a loro dovrebbe essere applicato htonl() . Per coerenza, suggerirei anche, come altri hanno già fatto qui, che htonl() sia usato per tutti i valori INADDR_* , anche se non fa nulla al valore.


Answer #5

INADDR_ANY è il "qualsiasi indirizzo" in IPV4. L'indirizzo è 0.0.0.0 in notazione a punti, quindi 0x000000 in esadecimale su qualsiasi endianità. Passare attraverso l' htonl non ha alcun effetto.

Ora se vuoi interrogarti sulle altre costanti macro, guarda INADDR_LOOPBACK se è definito sulla tua piattaforma. È probabile che sarà una macro come questa:

#define INADDR_LOOPBACK     0x7f000001  /* 127.0.0.1   */

(da linux/in.h , definizione equivalente in winsock.h ).

Quindi per INADDR_LOOPBACK , è necessario un htonl .

Per coerenza, potrebbe essere quindi preferibile usare htonl in tutti i casi.





sockets