PDA

View Full Version : [c] consiglio su app socket :)


aeroxr1
01-07-2014, 22:04
Ciao,
devo fare una semplice applicazione scritta in C modalità client-server su debian.

Le comunicazioni dovranno essere cliente - server usando tcp socket
e in uno step successivo client - client attraverso udp socket usando i dati presi dal server.

Il dubbio ora ce l'ho sulla comunicazione tra client e server.

In pratica il progetto mi richiede che prima di ogni invio di dati attraverso la socket tcp comunichi il numero di byte che sto inviando in maniera che il ricevente sappia quanti byte debba ricevere.

Server monoprocesso con la select() che individua la socket con i dati.

Il client invia il proprio username al server e dopo l'accettazione dal server (il quale andrà a creare una struttura apposita per ogni client) potrà inviare vari comandi che il server deve interpretare.

Dopo questa mega introduzione vi spiego il mio dubbio :
cosa potrei inventarmi per far si che il server discrimini tra i messaggi che gli arrivano per comunicare la dimensione in byte del msg successivo e il messaggio contenenti i contenuti veri e propri ?

:muro:

Una variabile che identifica lo stato di ogni client ? :mc:

Daniels118
02-07-2014, 08:56
Puoi realizzare una macchina a stati come avevi immaginato, oppure puoi cablare due read in sequenza, la prima lettura viene convertita in numero e il valore ottenuto viene utilizzato per dalla seconda per leggere il numero di byte corretto.

aeroxr1
02-07-2014, 10:05
Puoi realizzare una macchina a stati come avevi immaginato, oppure puoi cablare due read in sequenza, la prima lettura viene convertita in numero e il valore ottenuto viene utilizzato per dalla seconda per leggere il numero di byte corretto.

Inizialmente volevo fare con il secondo metodo da te detto,facendo così ho un dubbio però:
-> client A invia i dati al Server
-> una volta finita la prima read da parte del server il controllo mi torna alla select
-> la select controlla i descrittori
-> trova che il descrittore della socket del client A ha dei dati
-> faccio la recv , ma a questo punto arriva il dubbio: come faccio a sapere se devo eseguire la prima recv o la seconda se non mi sono segnato da nessuna parte che la prima recv è già stata fatta ? :confused:

Grazie mille per l'aiuto che mi state dando :)

Daniels118
02-07-2014, 11:54
No aspetta, se cabli le due read di seguito devi per forza utilizzare un thread per ogni connessione, altrimenti rischi di far bloccare l'intero server.
Per gestire tutto con un unico thread devi necessariamente utilizzare una variabile di stato per ogni connessione.

aeroxr1
02-07-2014, 12:31
No aspetta, se cabli le due read di seguito devi per forza utilizzare un thread per ogni connessione, altrimenti rischi di far bloccare l'intero server.
Per gestire tutto con un unico thread devi necessariamente utilizzare una variabile di stato per ogni connessione.

Eh è quello che pensavo , siccome le specifiche vogliono un monoprocesso monothread , quindi vado di select e variabili di stato. :sofico:

Oceans11
02-07-2014, 12:43
Che il server deve poter gestire più client contemporaneamente è richiesto dalle specifiche?
In caso contrario non vedo perché utilizzare la select, basta affidarsi alla coda delle connessioni in attesa che tiene il server.
Poi riflettevo anche sul significato di 'prima' nella frase prima di ogni invio di dati attraverso la socket tcp comunichi il numero di byte che sto inviando in maniera che il ricevente sappia quanti byte debba ricevere.
Siccome non è vero che ad ogni send corrisponde un pacchetto, prima lo interpreterei come appena prima dei dati. Esempio:
5hello
Così lato ricevente dovrai leggere un int e poi un array di char lungo quanto indicato dall'intero.
Occhio al terminatore di stringa e all'endianess degli interi (usa ntohl e htonl)

Daniels118
02-07-2014, 13:28
Nulla di più vero di quanto dice Oceans11.
Suggerirei di convertire il numero che rappresenta la lunghezza del dato in una stringa di lunghezza fissa, per esempio int 5 diventa char[4] "0005", in questo modo la lettura diventa più semplice (leggi sempre 4 byte fissi) e risolvi il problema dell'endianess.

PS. dovresti inoltre implementare una logica tri-stato (in attesa, inizio lettura, completamento lettura), poiché non è detto che con una sola read ricevi tutti gli n byte previsti; non dare inoltre per scontato che i dati arrivino tutti insieme mettendo la read in loop, potresti bloccare tutto.

aeroxr1
02-07-2014, 14:56
Intanto grazie per le risposte :) Siete gentilissimi :)
in sostanza voi fareste una cosa del genere (ditemi se ho capito bene) :

client:

char usernameC[250];
printf("Inserisci il tuo nome: ");
scanf("%s",usernameC);
length=strlen(usernameC);
(non capisco il problema dell'endianess, tanto essendo una grandezza interpretata dall'host non posso lasciarla direttamente in formato host ? senza usare htonl e dopo viceversa ? )
poi con le funzioni per le stringhe collegarci lo usernameC in una cosa del genere (pacchetto=0010usenameC)

send(sd,&pacchetto,sizeof(pacchetto),0);

Il problema lato client è quando faccio la recv che allora devo aver capito male come funziona.. In pratica questa va a leggere dal buffer del socket giusto ? e qui ci sarà 0010usernameC , io mi prendo i primi 4 byte leggo quanti byte devo ricevere ancora e faccio la recv dello usernameC con la lunghezza presa precedentemente ?

In questa maniera in effetti sarebbe sempre chiaro quanti byte devo ricevere ogni volta . Ho capito bene ? :stordita:

(chiaramente ci devono essere le gestioni di ricezione e invio di un numero minore di pacchetti quindi ci sarà da reinviare e o ricevere la restante parte ;) )

Daniels118
02-07-2014, 15:49
Le funzioni ntohl e htonl sono necessarie se vuoi trasmettere il numero in formato binario, perché il client e il server potrebbero avere processori con endianess diversa ed interpretare quindi il valore in modo diverso. Al limite i compilatori per le due architetture potrebbero avere addirittura una dimensione diversa per lo stesso tipo di dato (int o long).
Convertire il numero in stringa di lunghezza fissa produce esattamente lo stesso effetto, cambia solo il modo di rappresentazione.
Con il formato binario ovviamente riesci a risparmiare qualche byte.

Per il client puoi fare le due recv di seguito, tanto non devi servire richieste in parallelo.

aeroxr1
02-07-2014, 16:09
Le funzioni ntohl e htonl sono necessarie se vuoi trasmettere il numero in formato binario, perché il client e il server potrebbero avere processori con endianess diversa ed interpretare quindi il valore in modo diverso. Al limite i compilatori per le due architetture potrebbero avere addirittura una dimensione diversa per lo stesso tipo di dato (int o long).
Convertire il numero in stringa di lunghezza fissa produce esattamente lo stesso effetto, cambia solo il modo di rappresentazione.
Con il formato binario ovviamente riesci a risparmiare qualche byte.

Per il client puoi fare le due recv di seguito, tanto non devi servire richieste in parallelo.

Ah ho capito , in effetti io ragionavo pensando allo stesso compilatore stesso sistema operativo su entrambe le macchine :)

Nelle specifiche ho l'obbligo di utilizzo della select() per accettare nuove connessioni.

E' questa specifica che mi preoccupa :
"client e server si scambiano dei dati tramite socket TCP. Prima che inizi ogni scambio è necessario che il ricevente sappia quanti byte deve leggere dal socket. Non è ammesso che vengano inviati su socket numeri arbitrari di byte."

Ho paura di non rispettarla, ma ragionandoci mi sembra che la maniera di inviare in un unico pacchetto tutti i dati dimensione+contenuto rispetti in pieno le specifiche ;) Chiederò ! Intanto provo facendo così :)

Una domanda sulla recv, lei legge dal buffer del socket fino a len giusto ? quindi in teoria mi basta leggere fino a len=4 (considerando 4 byte per le dimensioni) e dopo leggere il valore della dimensione che è lì riportato impostare la nuova dim di len e rifare la recv che andrà a leggere i restanti byte del buffer fino a len . La teoria che so è giusta o c'è qualche pecca ? :D

CHiaramente devo gestire il caso in cui send non invii tutti gli n byte e stessa cosa recv non legge tutti gli n byte .
In quel caso devo andare di ritrasmissione ? :)

Daniels118
02-07-2014, 16:31
Con le connessioni TCP l'invio dei dati dovrebbe riuscire sempre (a meno di problemi di connessione), il sistema si preoccupa di spezzare il buffer in più pacchetti se necessario, l'unica operazione non sicura è la ricezione, poiché non è garantito che un messaggio diviso su due pacchetti arrivi tutto in un tempo così breve da consentirne il recupero con un'unica recv (sempre ammesso che il secondo pacchetto arrivi veramente :D).
Anche la lettura dei primi 4 byte andrebbe bufferizzata, immagina questa situazione con dei pacchetti da 512 byte:
1) invio un messaggio da 506 byte + 4 per la lunghezza, ma il pacchetto non parte perché il buffer ha ancora spazio per 2 byte e il sistema aspetta ancora un po' per vedere se si riempie;
2) subito dopo invio un messaggio di 100 byte + 4 per la lunghezza;
3) il sistema bufferizza i dati e li divide in due pacchetti, il primo contiene 4+506+2 byte, il secondo ne contiene 2+100;
4) il client riceve il primo pacchetto e ne legge 4 byte, vede che il dato è lungo 506 byte e li legge;
5) il buffer contiene ancora 2 byte, quindi il client prova a leggere 4 byte, ma ne ottiene solo due perché il secondo pacchetto non è ancora arrivato, quindi interpreta male la lunghezza del dato... da qui in poi si scatena l'inferno :muro:
Le connessioni UDP non le ho mai utilizzate, ma credo che per esse devi essere tu a spezzare i dati in buffer di lunghezza opportuna.

aeroxr1
02-07-2014, 17:04
infatti sono qui che mi sto scervellando per poter capire come fare il tutto senza avere problemi nella ricezione del dato contenente la lunghezza del successivo pacchetto in maniera da dimensionare il buffer nella maniera più opportuna.

Vabbè che questo è un progetto per scopo didattico, e quindi non dovrà trasferire grossi dati , però le cose mi piace impararle ammodino :muro:

fare la send in un unico colpo contenente tutti i dati o due send con buffer più piccolo ?

Ora come ora pensavo di fare la recv di 4 byte o di un intero per la dimensione e se questa riceveva meno dati abortire il tentativo di connessione e via chiudendo il server . Per scopo didattico può andare, dal punto di vista reale sarebbe una magagna non da poco. :stordita:

Oceans11
02-07-2014, 18:18
infatti sono qui che mi sto scervellando per poter capire come fare il tutto senza avere problemi nella ricezione del dato contenente la lunghezza del successivo pacchetto in maniera da dimensionare il buffer nella maniera più opportuna.

Aspè, forse sbagli approccio. Tu non sai a priori (prima che ti arriva il pacchetto) quanto è lungo il payload tcp. Quindi il buffer lo fai grosso a piacere.
Una cosa tipo:

#define SIZE 256
[...]
char buff[SIZE];
while (1) {
int brecv = recv(s, buff, SIZE, 0);
if (brecv < 0) {
// gestisci l'errore, chiudi tutto e liberi memoria
}
if (brecv == 0) {
// Nessun messaggio nel canale e il peer ha fatto shutdown()
break;
}
// qui consumi i brecv bytes letti
}
Come vedi il ciclo è infinito, o meglio, la recv si accorge se la controparte ha finito e ti restituisce 0, quindi esci con il break.

fare la send in un unico colpo contenente tutti i dati o due send con buffer più piccolo ?

direi un'unica send con un buffer che contiene sia la lunghezza (restante) che i dati veri e propri. Ma non sto parlando di efficienza.

Daniels118
02-07-2014, 18:42
[..] Come vedi il ciclo è infinito, o meglio, la recv si accorge se la controparte ha finito e ti restituisce 0, quindi esci con il break. [..]
Preciso che questo codice ovviamente va bene per un client o un server multithread, mentre per un server monothread bisogna inserire il discorso della select e degli stati.
fare la send in un unico colpo contenente tutti i dati o due send con buffer più piccolo ?
La mia opinione è che è del tutto indifferente, il risultato potrebbe essere avere uno o due pacchetti in entrambi i casi, dipende dall'implementazione dei vari strati software sottostanti.

Il mio parere è che dovresti implementare una routine che bufferizza i dati letti, quindi fai l'elaborazione solo se i dati sono effettivamente sufficienti.

aeroxr1
02-07-2014, 19:01
Aspè, forse sbagli approccio. Tu non sai a priori (prima che ti arriva il pacchetto) quanto è lungo il payload tcp. Quindi il buffer lo fai grosso a piacere.
Una cosa tipo:

#define SIZE 256
[...]
char buff[SIZE];
while (1) {
int brecv = recv(s, buff, SIZE, 0);
if (brecv < 0) {
// gestisci l'errore, chiudi tutto e liberi memoria
}
if (brecv == 0) {
// Nessun messaggio nel canale e il peer ha fatto shutdown()
break;
}
// qui consumi i brecv bytes letti
}
Come vedi il ciclo è infinito, o meglio, la recv si accorge se la controparte ha finito e ti restituisce 0, quindi esci con il break.



direi un'unica send con un buffer che contiene sia la lunghezza (restante) che i dati veri e propri. Ma non sto parlando di efficienza.

si si , il problema è che se recv riceve meno di len byte dovrò gestire l'errore , ed è li che mi si crea il problema di come fare .

Il prototipo di pacchetto che creerò sarà questa :

|header 2byte contenente la lunghezza del campo dati|comando 1byte|campo dati variabile|

supponiamo che la client invii un pacchetto così fatto :

|DATA LENGHT 25byte|comando|------------DATA----------------|

Cioè io ricevo con la recv e se questa mi riceve meno di due byte non avrò ricevuto per intero nemmeno l'header e dovrò gestire l'errore , stessa cosa se ricevo meno di 28 byte non ho ricevuto l'intero messaggio.
Questo errori non so come gestirli almeno per ora :D

Pensavo ad inviare un pacchetto del genere in maniera che con il campo "comando" possa evitare di fare la macchina a stati che può risultare noiosa e andrò ad inviare comandi del tipo :
- username
- porta
- chi è connesso
stilizzate in un semplice byte.

Non ho ben capito se mi consigli di fare una unica recv con una lenght dell'intero buffer [a questo punto però violo la specifica del progetto :( ] o due recv consecutive una con 3 byte cioè la lunghezza fissa [header|comando] e una relativa alla lunghezza variabile [DATA] :)
Il secondo metodo non so nemmeno se sia fattibile o meno :stordita:

Per eliminare il problema della ricezione frammentata devo fare come dice @Daniels118 una lettura bufferizzata .
E come impostare le recv ;)

Il problema mi deriva dal fatto che teoricamente pensavo che se la send inviava x byte la recv ricevesse fino a len=x byte , in realtà la recv può anche leggere n < x byte e poi i restanti rimangono disponibili per una futura recv .
In pratica la recv serve a leggere il buffer della socket . Giusto ?

Daniels118
02-07-2014, 20:25
Esatto! Tieni presente però che non è detto che la seconda parte dei dati sia subito disponibile, per cui fare due recv consecutive senza gli opportuni controlli è inutile.

aeroxr1
02-07-2014, 21:53
Esatto! Tieni presente però che non è detto che la seconda parte dei dati sia subito disponibile, per cui fare due recv consecutive senza gli opportuni controlli è inutile.

Ma anche la prima potrebbe avere questo problema giusto ? ( per capire se ho capito Ihih)

Pensavo che TCP del sender segmentasse i pezzetti, ma poi il receiver ricomponesse il tutto prima di darli alla socket . Non è così ? :/

Inviato dal mio Galaxy Nexus utilizzando Tapatalk

WarDuck
02-07-2014, 22:06
Usa e poi scorda TCP. TCP ti assicura che il ricevente legga tutto come uno stream di bytes. Quando chiedi di leggere 4 bytes dalla socket, la funzione non ritorna finché non ha letto 4 bytes (più o meno, vedi meglio le eccezioni, esempio l'altro lato chiude la connessione).

Comunque ti conviene strutturare tutto in messaggi aventi un header del tipo:


struct message_header
{
uint32_t length;
char variable_message[0]; // nn ricordo ma dovrebbe funzionare su C99
};


Quando allochi la memoria dinamicamente ti preoccupi di allocarne per

sizeof(struct message_header) + lunghezza messaggio

Così facendo allochi lo spazio per la struttura header e per il messaggio.

A quel punto casti il puntatore restituito dalla malloc con uno struct message_header e puoi impostare sia length che accedere direttamente al messaggio (in questo esempio è solo un array variabile di bytes).

All'invio invierai tutto il contenuto della struttura puntata.

Alla ricezione leggerai dapprima sizeof(struct message), e poi da lì continuerai a leggere per quanti bytes ti dice il campo length...

aeroxr1
03-07-2014, 01:08
Usa e poi scorda TCP. TCP ti assicura che il ricevente legga tutto come uno stream di bytes. Quando chiedi di leggere 4 bytes dalla socket, la funzione non ritorna finché non ha letto 4 bytes (più o meno, vedi meglio le eccezioni, esempio l'altro lato chiude la connessione).


Eh proprio per quello non capisco a cosa serva preoccuparmi del buffer , se tanto la recv aspetta fino a che non ho letto i 4byte :stordita: e si blocca se il buffer è vuoto .
L'unico problema sarebbe se il buffer non è stato riempito per quanti byte mi aspettavo a quel punto la mia recv mi riporterà i byte letti che saranno in numero minore a quelli che mi aspettavo.
Per gestire questo problema potrei mettere un buffer che riempio mano a mano con la recv. Ma non mi riesce capire il , perchè ciò sia necessario. :muro:


Comunque ti conviene strutturare tutto in messaggi aventi un header del tipo:


struct message_header
{
uint32_t length;
char variable_message[0]; // nn ricordo ma dovrebbe funzionare su C99
};


Quando allochi la memoria dinamicamente ti preoccupi di allocarne per

sizeof(struct message_header) + lunghezza messaggio

Così facendo allochi lo spazio per la struttura header e per il messaggio.

A quel punto casti il puntatore restituito dalla malloc con uno struct message_header e puoi impostare sia length che accedere direttamente al messaggio (in questo esempio è solo un array variabile di bytes).

All'invio invierai tutto il contenuto della struttura puntata.

Alla ricezione leggerai dapprima sizeof(struct message), e poi da lì continuerai a leggere per quanti bytes ti dice il campo length...

Non ci avevo pensato a creare una struttura e poi inviare quella pezzo per pezzo, pensavo ad una cosa più da newbie tipo un buffer di char con un byte per dimensione , 1 byte per il comando , restante per il campo dati.

Devo capire come inviare una struttura :stordita:
E poi devo capire un paio di chicche del codice che hai scritto :)
Dichiarando : "char variable_message[0];" se io dopo voglio allocare una stringa come dovrei fare ? :) Facendo così alloco dinamicamente il vettore ? Cercando su internet non avevo mai trovato questa soluzione :D
L'ho provata ma dipende dove la metto e mi funziona in altri posti no, ora domani la provo meglio :)
[ mi do una letta qui : http://stackoverflow.com/questions/9722632/what-happens-if-i-define-a-0-size-array-in-c-c ]

Grazie !!!

Daniels118
03-07-2014, 08:48
Ma anche la prima potrebbe avere questo problema giusto ? ( per capire se ho capito Ihih)

Pensavo che TCP del sender segmentasse i pezzetti, ma poi il receiver ricomponesse il tutto prima di darli alla socket . Non è così ? :/

Inviato dal mio Galaxy Nexus utilizzando Tapatalk
Come ha detto WarDuck, il protocollo TCP ti assicura che i dati arrivino tutti (a meno che non cada la connessione) e nell'ordine corretto, ma non garantisce nulla riguardo alla loro atomicità. La parte più piccola che sei sicuro di ricevere è il byte, inviare anche solo due byte in sequenza (anche utilizzando un'unica send) non garantisce che con un'unica recv li riceverai entrambi.

Nota a margine: di solito il sistema operativo fa automaticamente il flush del buffer anche se non è pieno se dopo un po' di tempo non gli arrivano dati, per cui se il protocollo applicativo è basato su una comunicazione messaggio/risposta (quindi la parte non manda mai due messaggi di seguito senza prima aver ricevuto risposta) e la lunghezza del messaggio non supera la dimensione del pacchetto, allora il messaggio arriverà in un unico pacchetto e senza dati eccedenti, e sarà possibile leggerlo con un'unica recv. Questo approccio naturalmente si basa sulla validità delle condizioni elencate, se anche una sola di esse non è soddisfatta si ottengono comportamenti inaspettati. Per quanto riguarda l'autoflush non bisogna commettere l'errore di pensare di utilizzare fflush, perché questo comando agisce solo sui buffer delle librerie del c e non su quelli di sistema. Anche impostare TCP_NODELAY non è sufficiente, perché c'è un ulteriore meccanismo di caching dei buffer legato all'acknowledgement... insomma, l'unico modo certo per far funzionare le cose sempre e comunque è quello di scrivere uno strato software che accumuli i dati fino alla lunghezza desiderata prima di leggerli.
struct message_header
{
uint32_t length;
char variable_message[0]; // nn ricordo ma dovrebbe funzionare su C99
};
In pratica inserisci nella struttura un campo che il compilatore interpreta come un array di char (attenzione, un array in-place, non un puntatore ad un array posizionato chissà dove), ma non ne definisci la lunghezza, poi a runtime allochi la struttura con dello spazio eccedente ed accedi al campo sforando oltre la dimensione teorica (0).
Se da qualche errore di compilazione si può tranquillamente definire l'array di lunghezza 1 e poi ricordarsi di allocare un byte in meno (ma anche se ci dimentichiamo non satureremo la memoria per un solo byte).
Il problema di questo metodo è che mandando quella struct sul socket non consideri eventuali differenze di endianess tra client e server. Personalmente opterei per le due send, non ci vedo nulla di male.
L'unico problema sarebbe se il buffer non è stato riempito per quanti byte mi aspettavo a quel punto la mia recv mi riporterà i byte letti che saranno in numero minore a quelli che mi aspettavo.
Esatto, perché recv legge fino a n byte, ma anche se ce n'è uno solo lo legge lo stesso.
Devo capire come inviare una struttura
La variabile che rappresenta la tua struttura non è altro che il puntatore restituito da malloc, basta che ne fai il cast a void* quando lo passi a send, tuttavia, come ho scritto prima, questo metodo non è l'ideale (a meno che non sei sicuro che tutte le piattaforme coinvolte abbiano la stessa endianess).

aeroxr1
03-07-2014, 10:48
Io pensavo ad unica send in cui inviavo un buffer
char *buffer[256]

fino alla lunghezza riempita :)

5cpippo

E di là 1 recv per ricevere 5c (cavolo in effetti sono due byte rischierei un errore)

e un altra recv con len 5 per ricevere pippo .

In effetti è una soluzione sparta da bimbiminkia :doh:

Indeciso tra questa soluzione e tra l'inviare due send una per la lunghezza e una per il msg + usare una variabile per gli stati :stordita:

Devo scegliere una via e portarla avanti senza guardarmi indietro :muro:

van9
03-07-2014, 11:50
struct message_header
{
uint32_t length;
char variable_message[0]; // nn ricordo ma dovrebbe funzionare su C99
};
In pratica inserisci nella struttura un campo che il compilatore interpreta come un array di char (attenzione, un array in-place, non un puntatore ad un array posizionato chissà dove), ma non ne definisci la lunghezza, poi a runtime allochi la struttura con dello spazio eccedente ed accedi al campo sforando oltre la dimensione teorica (0).
Se da qualche errore di compilazione si può tranquillamente definire l'array di lunghezza 1 e poi ricordarsi di allocare un byte in meno (ma anche se ci dimentichiamo non satureremo la memoria per un solo byte).

La dimensione a zero non è mai stata valida per lo standard - un compilatore (no estensioni ovviamente) che non emette un warning in questo caso è da buttare. La dimensione ad uno è legale, ma invoca undefined behavior quando vai ad accedere oltre il primo elemento e se le ottimizzazioni sono attive può accadere di tutto. La cosa giusta è "char variable_message[];" usando C99. Se C99 non è un'opzione si usa la dimensione ad uno ma è meglio controllare cosa combina il compilatore.

C and C++, where (almost) everything is not what it seems...

Oceans11
03-07-2014, 12:04
Sì scusate, non stavo tenendo conto della select.
Il codice rimane quello, con l'aggiunta del controllo su FD_ISSET e il settaggio delle opportune variabili di stato, che a mio parere sono obbligatorie.
La dimensione del buffer di invio è irrilevante, mentre in ricezione te ne servono 2 di buffer: uno per dimensione oaylod + comando (se ha dimensione fissa) e il secondo che allochi a runtime in base a ciò che hai ricevuto.
Questo vuol dire che oltre al file descriptor, il server deve mantenere, per ogni connessione, la dimensione del payload, il comando e il buffer del payload (almeno parziale).
Ti suggerirei una struct che contenga il tutto e funzioni per serializzare/deserializzare i suoi dati.

Se questa soluzione non ti piace, puoi dare uno sguardo all'opzione del socket SO_RCVLOWA che ti permette di impostare il valore minimo in byte di soglia necessario a far ritornare la select. Non ho mai provato e non mi sento di consigliarlo.

Daniels118
03-07-2014, 12:24
[..]
Indeciso tra questa soluzione e tra l'inviare due send una per la lunghezza e una per il msg + usare una variabile per gli stati :stordita:
Come già detto, dovendo fare la conversione del numero ti consiglio le due send, semplicemente per comodità... potremmo fare anche delle considerazioni su cosa sia meglio o peggio in termini di performance, ma visto che non stai scrivendo un server per streaming video direi che le performance possono passare in secondo piano.
Se questa soluzione non ti piace, puoi dare uno sguardo all'opzione del socket SO_RCVLOWA che ti permette di impostare il valore minimo in byte di soglia necessario a far ritornare la select. Non ho mai provato e non mi sento di consigliarlo.
Questa non la sapevo, ma leggo che su linux il valore è bloccato ad 1 byte... peccato.

Ribadisco anche io la necessità di una variabile di stato per ogni connessione, naturalmente solo sul client.

aeroxr1
03-07-2014, 12:57
ok quindi credo farò così :
- due send una fissa di un byte con la dimensione del campo successivo che verrà inviata con la send subito dopo
- sul server terrò una lista con un elemento per ogni client , contenente i vari buffer per ogni client e la variabile di stato che mi dirà in che punto sono della comunicazione client server
- la variabile comando potrei inviarla successivamente tanto il primo comando è per forza di cose l'invio dello username
- dopo negli invii successivi andrò a vedere cosa c'è nel campo data e qui ci sarà il comando e per ogni comando mi sposterò nello stato opportuno

Fin qui ragiono male ? (poi per carità lo deve vedere in pratica, però l'idea sarebbe questa )

per serializzare i dati della struct che andrò ad inviare passo passo, dato che la mia struct conterrà solo caratteri [username e comandi] e numeri interi , potrei usare per questi ultimi le semplici funzioni htons() , ntohs() , htonl() e ntohl() ? :)

E un ultima cosa la creazione del buffer dinamico sul server, io inizialmente pensavo di creare una struttura con all'interno un campo username[256], comando ecc ecc.
Allocando staticamente i vettori con uno spazio grande a sufficienza, e non usare la creazione dinamica, poichè non sono capace a creare un allocazione dinamica di stringhe-vettori in C :stordita: , però mi rendo conto che lo spreco è alto :rolleyes:

Vabbè sarà il caso di capire come creare allocazione dinamica di vettori :stordita:

Daniels118
03-07-2014, 13:15
No, dai, studia prima l'allocazione dinamica e poi ti cimenti coi socket!
Per il futuro, quando vorrai fare delle ottimizzazioni, ricordati di non allocare e deallocare continuamente, sono operazioni che costano, pensa bene dove vale la pena risparmiare e accorpa quello che si può accorpare.

van9
03-07-2014, 13:18
( SO_RCVLOWAT )
Questa non la sapevo, ma leggo che su linux il valore è bloccato ad 1 byte... peccato.


Quella è SO_SNDLOWAT, di SO_RCVLOWAT puoi fare il set ma poi non hai signaling affidabile e quindi serve a nulla.

Daniels118
03-07-2014, 13:30
Quella è SO_SNDLOWAT, di SO_RCVLOWAT puoi fare il set ma poi non hai signaling affidabile e quindi serve a nulla.
Qui (http://pubs.opengroup.org/onlinepubs/7908799/xns/setsockopt.html) leggo:
SO_RCVLOWT
[..] Note that not all implementations allow this option to be set.
Ancora, dalle manpages:
socket(7):
"SO_RCVLOWAT and SO_SNDLOWAT
Specify the minimum number of bytes in the buffer until the socket layer will
pass the data to the protocol (SO_SNDLOWAT) or the user on receiving
(SO_RCVLOWAT). These two values are initialized to 1. SO_SNDLOWAT is not
changeable on Linux (setsockopt(2) fails with the error ENOPROTOOPT).
SO_RCVLOWAT is changeable only since Linux 2.4. The select(2) and poll(2) sys-
tem calls currently do not respect the SO_RCVLOWAT setting on Linux, and mark a
socket readable when even a single byte of data is available. A subsequent
read from the socket will block until SO_RCVLOWAT bytes are available."
In altre parole SO_RCVLOWAT ha effetto solo sulla read (e su Linux solo a partire dalla versione 2.4), mentre su select e poll non ha alcun effetto.

aeroxr1
03-07-2014, 13:36
No, dai, studia prima l'allocazione dinamica e poi ti cimenti coi socket!
Per il futuro, quando vorrai fare delle ottimizzazioni, ricordati di non allocare e deallocare continuamente, sono operazioni che costano, pensa bene dove vale la pena risparmiare e accorpa quello che si può accorpare.

In C non mi è così banale allocare dinamicamente uno spazio indefinito, cioè faccio per dire lo username può essere 10 caratteri,ma anche 40.
Quindi non so di quanto spazio allocare in memoria con la malloc :)
Cercando su internet ho letto della funzione realloc, in pratica gli si assegna un blocco di dimensioni x e poi se non basta con la realloc si riassegna un altro spazio contiguo (se esiste , già questa cosa mi lascia perplesso :D ) . Però non ho trovato la maniera di creare un buffer perfettamente adatto all'input.

Però riflettendoci in questo caso saprei quanti byte mi arrivano quindi creo una porzione per quei byte e la riempio :D
Il problema è più sul client , però li metto un buffer grande e grosso :sofico:

Daniels118
03-07-2014, 14:20
La programmazione lascia abbastanza spazio alla fantasia, dipende dalla logica che vuoi utilizzare, comunque non restare nell'indecisione, intraprendi una strada che ritieni corretta, poi se ti si presenteranno dei problemi troverai il modo di affrontarli. Anche se commetti un errore non ti avvilire, sbagliando si impara :)

aeroxr1
03-07-2014, 14:39
Non avessi sempre delle scadenze vicine da rispettare il tutto sarebbe meno stressante :cry:

COmunque grazie a tutti ;) Provo un pò a mettere le mani nel codice vediamo che ne viene fuori :) Mi farò sicuramente risentire per rompervi le scatole con altri dubbi :p

aeroxr1
03-07-2014, 15:00
Non avessi sempre delle scadenze vicine da rispettare il tutto sarebbe meno stressante :cry:

COmunque grazie a tutti ;) Provo un pò a mettere le mani nel codice vediamo che ne viene fuori :) Mi farò sicuramente risentire per rompervi le scatole con altri dubbi :p

van9
03-07-2014, 17:15
Qui (http://pubs.opengroup.org/onlinepubs/7908799/xns/setsockopt.html) leggo:


Si non è obbligatoria per dirsi POSIX, è solo un sistema opzionale per risparmiare copie dal kernel alla userland.


Ancora, dalle manpages:

In altre parole SO_RCVLOWAT ha effetto solo sulla read (e su Linux solo a partire dalla versione 2.4), mentre su select e poll non ha alcun effetto.

Ah era nella man page, e io che d'istinto m'ero letto i sorgenti... babbè.

van9
03-07-2014, 17:20
In C non mi è così banale allocare dinamicamente uno spazio indefinito, cioè faccio per dire lo username può essere 10 caratteri,ma anche 40.

Un ottimo motivo per imparare a usare da subito librerie di qualità comprovata come Vstr o Bstring, e poi studiarsene gli internals e scriversi le proprie per imparare.

aeroxr1
03-07-2014, 20:37
Un ottimo motivo per imparare a usare da subito librerie di qualità comprovata come Vstr o Bstring, e poi studiarsene gli internals e scriversi le proprie per imparare.

Grazie a tutti per i suggerimenti :) Siete grandiosi :)
In molti forum se avessi chiesto le stesse cose mi avrebbero dato dell'ignorante , voi invece mi avete dato una grande mano !
Grazie veramente di cuore :)

aeroxr1
05-07-2014, 02:52
Prendendo un pò di vostri suggerimenti ho iniziato a cercare di mettere in pratica questa idea:

Lato Client:
- send comando (1byte)
- send dimensione dati (1byte)
- send dati (dimensione variabile)

Lato Server:
- recv comando (1byte)
- recv dimensione dati (1byte)
- recv dati (dimensione variabile)

una struttura lato server contenente :
- variabile comando
- variable lunghezza dati
- buffer dinamico contentente i dati
- variabile di stato

la variabile di stato pensavo di farla da 0 a 3 dove
0 attesa comando, 1 attesa dimensione dati , 2 attesa dati, 3 esecuzione comando (forse lo stato 3 è evitabile)

- Voi che dite sono troppe tre recv due delle quali usate per ricevere un byte per volta ?
- e anche la variabile lunghezza dati pensavo di inviarla sempre anche quando il campo dati contiene un dato di dimensione 0 cioè vuoto e quando il campo dati contiene 1 solo byte . Dite che è uno spreco ?

Daniels118
05-07-2014, 09:49
La struttura sul server deve avere un'ulteriore variabile che conserva la lunghezza dei dati effettivamente ricevuti, perché il server deve rimanere nello stato "2 attesa dati" fino a quando i dati ricevuti raggiungono la lunghezza dati prevista.
Sul client vale lo stesso discorso, e non avendo il problema di dover gestire più connessioni puoi mettere la recv in un loop.
Lo stato 3 lo eviterei, quando i dati ricevuti raggiungono la lunghezza prevista puoi eseguire il comando e reimpostare lo stato zero.
Per me non c'è alcun motivo per non usare una recv per ogni dato.
La lunghezza dato la invierei sempre, in questo modo non c'è bisogno di controllare che tipo di comando è arrivato.

aeroxr1
05-07-2014, 10:13
Esatto la lunghezza dato pensavo proprio di inviarla sempre per quel motivo se no dovrei fare un doppio controllo sul comando.
E la variabile che hai detto mi ero scordata di scriverla XD

Io però lato server hi creato una struttura ad hoc per ogni client mentre lato client no.
Sulla serializzazione delle strutture non avendolo mai fatto mi incuriosisce , ma dovrei informarmi .
Lato client c'è una connessione UDP verso un altro client
Devo un attimo riflettere se mentre riceve ha bisogno anche di comunicare con l'altro client o no. In caso positivo devo gestire la recv per evitare il loop :/
Vedrò !

Inviato dal mio Galaxy Nexus utilizzando Tapatalk

WarDuck
05-07-2014, 10:43
La dimensione a zero non è mai stata valida per lo standard - un compilatore (no estensioni ovviamente) che non emette un warning in questo caso è da buttare. La dimensione ad uno è legale, ma invoca undefined behavior quando vai ad accedere oltre il primo elemento e se le ottimizzazioni sono attive può accadere di tutto. La cosa giusta è "char variable_message[];" usando C99. Se C99 non è un'opzione si usa la dimensione ad uno ma è meglio controllare cosa combina il compilatore.

C and C++, where (almost) everything is not what it seems...

Ho provato su Debian 7 con GCC 4.7 e con i flag -Wall -Wextra non riporta nulla (non ho dovuto neanche passargli std C99).

Ora giusto per curiosità provo sulle ultime versioni di clang e Visual Studio 2013 :D.

Edit: neanche Visual Studio si lamenta (sempre abilitando tutti i Warning), sarà perché usa un compilatore C++.

Edit2: neanche llvm 3.4, sempre passando -Wall -Wextra.

In pratica possiamo buttare tutti i compilatori esistenti ;).

nico159
05-07-2014, 13:45
Ho provato su Debian 7 con GCC 4.7 e con i flag -Wall -Wextra non riporta nulla (non ho dovuto neanche passargli std C99).

Ora giusto per curiosità provo sulle ultime versioni di clang e Visual Studio 2013 :D.

Edit: neanche Visual Studio si lamenta (sempre abilitando tutti i Warning), sarà perché usa un compilatore C++.

Edit2: neanche llvm 3.4, sempre passando -Wall -Wextra.

In pratica possiamo buttare tutti i compilatori esistenti ;).
Devi usare -Wpedantic
warning: ISO C++ forbids zero-size array ‘variable_message’ [-Wpedantic]

Issue all the warnings demanded by strict ISO C and ISO C++; reject all programs that use forbidden extensions, and some other programs that do not follow ISO C and ISO C++. For ISO C, follows the version of the ISO C standard specified by any -std option used.

WarDuck
05-07-2014, 14:10
Ah ecco, effettivamente bisognava essere molto pedantici :D.

A questo punto la cosa migliore è come dice van:


struct message_h
{
...
char message[];
};


Se C99 non è un opzione, allora dico io butta il compilatore o l'ambiente in cui stai sviluppando :D.

Daniels118
06-07-2014, 11:12
Ah ecco, effettivamente bisognava essere molto pedantici :D.

A questo punto la cosa migliore è come dice van:


struct message_h
{
...
char message[];
};


Se C99 non è un opzione, allora dico io butta il compilatore o l'ambiente in cui stai sviluppando :D.
Premesso che sono assolutamente contrario sia ai "dialetti" dei linguaggi, che alla trasmissione di struct sul socket che lasciano spazio alle interpretazioni (leggasi endianness & co.), scrivendo "char message[];" non si definisce un array nella struttura, ma un puntatore ad un array da allocare che risiederà da qualche parte in memoria, per cui scrivere la struttura sul socket non invierebbe il messaggio ma un puntatore, che ovviamente perde di significato nel momento in cui viene trasmesso sulla rete.
In altre parole "char message[];" è esattamente equivalente a "char *message;", ed è assolutamente diverso da "char message[0];". Ma ribadisco che non mi piace in alcun caso.

aeroxr1
06-07-2014, 12:10
Nuovo dubbietto :)

Sto facendo la recv modificata per gestire la situazione recv==0 cioè la chiusura di una connessione aspettata o inaspettata , e recv<length .

In questo caso pensavo di segnare il ritorno della recv in una variabile per riniziare da lì quando arrivano i rimanenti dati, una cosa del genere :

il client fa send(strlen(dati));
il server :
char *buffer;
buffer=malloc(length*sizeof(char));

recvmod()
{
ret=recv(...)
alla recv dopo
inizio a scrivere da buffer[ret]
}

e questo con i dati che arrivano sotto forma di stringa.

Quando deve arrivare la lunghezza dei dati però li mi nasce il problema, perchè non mi viene in mente un metodo per ricevere un campo int bufferizzandolo.. lo dovrei fare shiftando i dati della quantità ricevuta e facendo poi dopo la or ? :fagiano:
(ora per ora faccio in maniera che la lunghezza length non sia mai superiore ad un byte)

Mettiu_
06-07-2014, 12:42
Mi inserisco tardi in questa discussione, per cui non sono sicuro di aver capito il tuo dubbio...
Per inviare un intero a dimensione fissa (esempio un intero senza segno a 32 bit) puoi fare qualcosa del genere:


uint32_t size = 123465; // l'intero che vuoi inviare
size = htonl(size); // conversione in formato network
send (sock, &size, sizeof(size), 0);


Lato ricevente invece:

uint32_t size;
recv(sock, &size, sizeof(size), 0); // ricevo esattamente quattro bytes
size = ntohl (size); // conversione


NON dimenticare l'uso di ntohl e htonl (come già suggerito da altri) altrimenti l'endianess può portarti ad interpretare male i bytes che ricevi. Ovviamente finchè fai prove su localhost il problema non si vede :D

aeroxr1
06-07-2014, 13:16
Mi inserisco tardi in questa discussione, per cui non sono sicuro di aver capito il tuo dubbio...
Per inviare un intero a dimensione fissa (esempio un intero senza segno a 32 bit) puoi fare qualcosa del genere:


uint32_t size = 123465; // l'intero che vuoi inviare
size = htonl(size); // conversione in formato network
send (sock, &size, sizeof(size), 0);


Lato ricevente invece:

uint32_t size;
recv(sock, &size, sizeof(size), 0); // ricevo esattamente quattro bytes
size = ntohl (size); // conversione


NON dimenticare l'uso di ntohl e htonl (come già suggerito da altri) altrimenti l'endianess può portarti ad interpretare male i bytes che ricevi. Ovviamente finchè fai prove su localhost il problema non si vede :D

Ciao e grazie per l'aiuto :)
Il mio dubbio viene dal fatto che la recv ci sta non riceva tutti i byte insieme. Quindi considerando i 32 bit dell'int , se ricevo solo i primi 8 bit , cioè solo il primo byte avrei un errore no ?
Quindi non mi servirebbe un sistema di bufferizzazione anche per l'int ? :stordita:

(prima di iniziare questa discussione non pensavo ci fossero di questi problemi e la ricezione la facevo anche io ricevendo la size dell'int :muro: )

Questa è la struttura che ho creato :

struct elemento
{
char *username; //Username del giocatore , crerò dinamicamente lo spazio in maniera di non sprecare memoria
short int UDPports;
unsigned long IPaddress;
int socket; //id del socket
char ready; //indica se il client ha effettuato il login o è sempre in attesa di login e quindi non ancora connesso
char impegnato; //mi indicherà Se il client è impegnato in un'altra partita

char cmd ; //il comando che stiamo eseguendo. Inizialmente in waiting !
int stato; //variabile di stato che mi dice in che parte della macchina a stati si trova il client
int length; //lunghezza campo dati
char *buffer; //buffer dove vengono ricevuti i dati temporaneamente prima di essere ricevuti completamente, buffer dinamico
int last; //variabile che indica fino a che punto abbiamo riempito il buffer in caso di lettura non terminata

struct elemento *next; //puntatore all'elemento successivo della lista
struct elemento *enemy; //puntatore alla struttura dell'avversario
};

typedef struct elemento player;


queste le funzioni di ricezione :

int recvTCP(int sd, void* buf, int len) // possibile implementazione
{
int ret;
ret = recv(sd,buf,len,0);
if (ret<len)
{
if(ret==0) //chiusura connessione da parte di un client oppure errore
{
//chiusura connessione da parte di un client oppure errore
close(sd); //chiudo la socket
FD_CLR(sd,&master); //rimuovo la socket dal master set
printf("%s si è disconnesso dal server",client->username);
if(client->impegnato==YES) //si è chiuso durante una partita quindi è un problema da notificare all'avversario
{
//notifica(client->enemy->socket);
}
elem_remove(client,&connessi); //rimuove l'elemento dalla lista connessi
return -1; //non ritorna nessun byte c'è solo stata una chiusura
}
//qui ci vorrebbe un buffer da riempire fino a quando sono disponibili tutti i dati, cosa faccio ?
//chiedere al professore
return ret; //ritorna il numero di byte letti

}
return 0; // tutto ok
}

void riceviLength() //creato una funzione separata per la futura gestione del buffer della lunghezza
{
recvTCP(client->socket,&(client->length),sizeof(int)); //dato che ricevo un solo byte forse //è meglio metta 1 al posto di sizeof(int)
}

int riceviMsg()
{
int ret;
ret = recvTCP(client->socket,&(client->buffer[client->last]),client->length);
if(ret<0) return -1; //c'è stata una chiusura della connessione
if(ret==0)
{
client->last=0; //rimetto a 0 la variabile che mi indica dove sono rimasto a leggere
return 0; //dato completamente ricevuto
}
client->last=ret;
client->length=(client->length)-(client->last); //aggiorno la lunghezza da leggere all'interno della struttura client
return ret;
}


VIene compilato senza errori, ma non l'ho provato, perchè mi manca ancora una gestione dei comandi da applicare. Però non so se ho commesso qualche errore di logica nel codice qui sopra :p

Daniels118
07-07-2014, 07:14
Quando deve arrivare la lunghezza dei dati però li mi nasce il problema, perchè non mi viene in mente un metodo per ricevere un campo int bufferizzandolo.. lo dovrei fare shiftando i dati della quantità ricevuta e facendo poi dopo la or ?
Ricordati che la recv non ritorna il dato letto, ma devi passare in input un puntatore ad un buffer. Io personalmente farei così:
char buf[4];
int n = 0;
while (n < 4) {
n += recv(sockfd, &buf[n], 4 - n, 0);
}
oppure così:
char buf[4];
while (recv(sockfd, buf, 4, MSG_PEEK) < 4) {}
recv(sockfd, buf, 4, 0);
e poi usi shift e or per ricostruire il numero.
Ovviamente sul server il ciclo va gestito diversamente con la select, inoltre vanno gestiti gli errori.
Dei due metodi il primo è da preferire, soprattutto nel caso di dati corposi.

aeroxr1
07-07-2014, 09:35
Ricordati che la recv non ritorna il dato letto, ma devi passare in input un puntatore ad un buffer. Io personalmente farei così:
char buf[4];
int n = 0;
while (n < 4) {
n += recv(sockfd, &buf[n], 4 - n, 0);
}
oppure così:
char buf[4];
while (recv(sockfd, buf, 4, MSG_PEEK) < 4) {}
recv(sockfd, buf, 4, 0);
e poi usi shift e or per ricostruire il numero.
Ovviamente sul server il ciclo va gestito diversamente con la select, inoltre vanno gestiti gli errori.
Dei due metodi il primo è da preferire, soprattutto nel caso di dati corposi.

Grazie :)
Quei cicli li posso fare sul client , sul server li dovrò spezzettare tutti :stordita:
Certo era tanto semplice se la select segnalava solo i socket pronti con il numero di byte voluti :rolleyes:

Per il passaggio da int a string itoa() ha problemi di portabilità che voi sappiate ? :)


p.s : ora provo il codice che ho postato ieri, però lo provo in locale mettendo server e client sulla stessa macchina quindi sarà difficile vedere se ho strutturato bene i cicli per il buffer della recv :mad: :O

Daniels118
07-07-2014, 09:47
Itoa converte un int in una stringa in formato decimale, di lunghezza variabile. Ti conviene utilizzare sprintf se vuoi trasmettere in formato decimale di lunghezza fissa, oppure utilizzare shift e and per prendere i byte uno alla volta.

aeroxr1
07-07-2014, 09:52
Itoa converte un int in una stringa in formato decimale, di lunghezza variabile. Ti conviene utilizzare sprintf se vuoi trasmettere in formato decimale di lunghezza fissa, oppure utilizzare shift e and per prendere i byte uno alla volta.

Ah ok grazie :) Intanto per ora cerco di far funzionare il tutto mandando massimo un byte di lunghezza. Quando tutto funzionerà implementerò anche questo buffer D

aeroxr1
07-07-2014, 13:11
Ho deciso di implementare da subito il buffer anche per la lunghezza in maniera da non avere limiti sul buffer del dato :D

Ecco qua come l'ho scritto, potete dargli un occhiata per vedere se secondo voi è corretto ? Va bè capisco se non ne avrete voglia :p

int recvTCP(int sd, void* buf, int len) //DUBBIO : Per ora in caso di ritorno < lenght non faccio la chiusura, ma gestisco il buffer !
{
int ret;
ret = recv(sd,buf,len,0);
if (ret<len)
{
if(ret==0) //chiusura connessione da parte di un client oppure errore
{
//chiusura connessione da parte di un client oppure errore
close(sd); //chiudo la socket
FD_CLR(sd,&master); //rimuovo la socket dal master set
printf("%s si è disconnesso dal server",client->username);
if(client->impegnato==YES) //si è chiuso durante una partita quindi è un problema da notificare all'avversario
{
//notifica(client->enemy->socket);
}
elem_remove(client,&connessi); //rimuove l'elemento dalla lista connessi
return -1; //non ritorna nessun byte c'è solo stata una chiusura
}
//qui ci vorrebbe un buffer da riempire fino a quando sono disponibili tutti i dati, cosa faccio ?
//chiedere al professore
return ret; //ritorna il numero di byte letti

}
return 0; // tutto ok
}

int riceviLength() //creato una funzione separata per la futura gestione del buffer della lunghezza
{
//in realtà inviando un int per la dimensione invio ben 4byte, devo gestire un buffer per la lunghezza
int ret;
int temp; //ci viene salvato il dato letto
ret = recvTCP(client->socket,&temp,sizeof(int));
temp=(int)ntohl(temp);
client->last=(client->last)|temp;
if(ret<0) return -1; //c'è stata una chiusura della connessione già gestita dalla recvTCP
if (ret==0)
{
client->length=client->last;
client->last=0;
return 0;
}

client->last=(client->last)<<(8*(ret));

return ret;
}


Grazie di nuovo a tutti :)

p.s : nelle dispense da cui studio è scritto che anche la send può inviare meno dati di quelli previsti e i dati inviati vengono ritornati dalla funzione. Ora il dubbio è : seguendo quanto dice la dispensa dovrei fare un buffer anche di quella :muro:
Sotto TCP se la send() mi ritorna un valore < di quello previsto posso direttamente gestire la chiusura di connessione in quanto c'è stato un errore di connessione ? e sotto UDP ? :mc:

van9
07-07-2014, 14:41
Ho provato su Debian 7 con GCC 4.7 e con i flag -Wall -Wextra non riporta nulla (non ho dovuto neanche passargli std C99).

Ora giusto per curiosità provo sulle ultime versioni di clang e Visual Studio 2013 :D.

Edit: neanche Visual Studio si lamenta (sempre abilitando tutti i Warning), sarà perché usa un compilatore C++.

Edit2: neanche llvm 3.4, sempre passando -Wall -Wextra.

In pratica possiamo buttare tutti i compilatori esistenti ;).

Naa, mi pagano troppo bene per saperli usare.

Mettiu_
07-07-2014, 15:24
Itoa converte un int in una stringa in formato decimale, di lunghezza variabile. Ti conviene utilizzare sprintf se vuoi trasmettere in formato decimale di lunghezza fissa, oppure utilizzare shift e and per prendere i byte uno alla volta.

No, questo approccio ha diversi problemi:
- la lunghezza non va inviata come stringa altrimenti diventa essa stessa variabile. Ad esempio se la lunghezza è 10 io invio la stringa "10" che è lunga due bytes mentre se la lunghezza è 100, invio la stringa "100" che è lunga tre bytes. Fare in modo che questa STRINGA abbia una lunghezza fissa mi sembra un lavoro inutile;
- fare gli shift e gli OR che dite voi (inviando byte per byte) è anche peggio in quanto uno non può semplicemente leggere la memoria byte per byte e inviarne il contenuto perchè poi il ricevente, appena prova a interpretare quei byte come intero, non sa come fare a causa dell'endianess (e il risultato può essere errato).

Un modo possibile (che eviti questi problemi) l'ho già scritto qualche post fa... In aggiunta a quel codice, si può dire che uno può aggiungere un controllo per verificare che recv riceva il numero esatto di bytes (cioè 4 per un uint32). Se l'altro end-point è implementato bene questa condizione deve essere rispettata (l'intero deve essere inviato con una singola chiamata send chiaramente). Altrimenti io chiuderei pure la socket perchè se c'è un protocollo e una delle parti non lo rispetta allora puoi benissimo chiudergli la porta in faccia.

aeroxr1
07-07-2014, 15:29
No, questo approccio ha diversi problemi:
- la lunghezza non va inviata come stringa altrimenti diventa essa stessa variabile. Ad esempio se la lunghezza è 10 io invio la stringa "10" che è lunga due bytes mentre se la lunghezza è 100, invio la stringa "100" che è lunga tre bytes. Fare in modo che questa STRINGA abbia una lunghezza fissa mi sembra un lavoro inutile;
- fare gli shift e gli OR che dite voi (inviando byte per byte) è anche peggio in quanto uno non può semplicemente leggere la memoria byte per byte e inviarne il contenuto perchè poi il ricevente, appena prova a interpretare quei byte come intero, non sa come fare a causa dell'endianess (e il risultato può essere errato).

Un modo possibile (che eviti questi problemi) l'ho già scritto qualche post fa... In aggiunta a quel codice, si può dire che uno può aggiungere un controllo per verificare che recv riceva il numero esatto di bytes (cioè 4 per un uint32). Se l'altro end-point è implementato bene questa condizione deve essere rispettata (l'intero deve essere inviato con una singola chiamata send chiaramente). Altrimenti io chiuderei pure la socket perchè se c'è un protocollo e una delle parti non lo rispetta allora puoi benissimo chiudergli la porta in faccia.

ora mi vado a rileggere come avevi detto di fare te :)

Ma in questa maniera quale sarebbe l'errore ?

int riceviLength() //creato una funzione separata per la futura gestione del buffer della lunghezza
{
//in realtà inviando un int per la dimensione invio ben 4byte, devo gestire un buffer per la lunghezza
int ret;
int temp; //ci viene salvato il dato letto
ret = recvTCP(client->socket,&temp,sizeof(int));
temp=(int)ntohl(temp);
client->last=(client->last)|temp;
if(ret<0) return -1; //c'è stata una chiusura della connessione già gestita dalla recvTCP
if (ret==0)
{
client->length=client->last;
client->last=0;
return 0;
}

client->last=(client->last)<<(8*(ret));

return ret;
}

recvTCP è una funzione che ho creato io che ho postato sopra :)

Te lo chiedo giusto per capire dove sbaglio :)

ho ritrovato il codice che hai consigliato te:
Mi inserisco tardi in questa discussione, per cui non sono sicuro di aver capito il tuo dubbio...
Per inviare un intero a dimensione fissa (esempio un intero senza segno a 32 bit) puoi fare qualcosa del genere:


uint32_t size = 123465; // l'intero che vuoi inviare
size = htonl(size); // conversione in formato network
send (sock, &size, sizeof(size), 0);


Lato ricevente invece:

uint32_t size;
recv(sock, &size, sizeof(size), 0); // ricevo esattamente quattro bytes
size = ntohl (size); // conversione


NON dimenticare l'uso di ntohl e htonl (come già suggerito da altri) altrimenti l'endianess può portarti ad interpretare male i bytes che ricevi. Ovviamente finchè fai prove su localhost il problema non si vede :D

facendo come dici te la send è fatta in una volta sola come d'altronde faccio anche io
La recv non è detto riceva tutto in una volta sola giusto ?
quindi ci sta benissimo che la length che sta su 4byte viene si inviata intera, ma che alla recv arrivi solo il primo dei 4 byte no ??
In quel caso non devo aspettare mi arrivino gli altri 3 byte ? :stordita:

Oppure non c'è possibilità che venga spezzettato un int ?

Daniels118
07-07-2014, 15:36
- la lunghezza non va inviata come stringa altrimenti diventa essa stessa variabile. Ad esempio se la lunghezza è 10 io invio la stringa "10" che è lunga due bytes mentre se la lunghezza è 100, invio la stringa "100" che è lunga tre bytes. Fare in modo che questa STRINGA abbia una lunghezza fissa mi sembra un lavoro inutile;
E' proprio per questo che ho consigliato sprintf al posto di itoa, perché utilizzando la stringa di formattazione opportuna è possibile allineare il numero ad una lunghezza fissa, per esempio 10 diventa "00010" e 100 diventa "00100", il numero di caratteri è sempre 5.
- fare gli shift e gli OR che dite voi (inviando byte per byte) è anche peggio in quanto uno non può semplicemente leggere la memoria byte per byte e inviarne il contenuto perchè poi il ricevente, appena prova a interpretare quei byte come intero, non sa come fare a causa dell'endianess (e il risultato può essere errato).
Gli shift agiscono sul valore, indipendentemente dall'endiannes del sistema.

aeroxr1
07-07-2014, 15:45
@Mettiu_ ho modificato la risposta al tuo post :)

@Daniels118 quindi se gli shift agiscono sul valore indipendentemente dall'endianess del sistema è inutile il ntohl che ho applicato al mio int prima di fare la shift ? :D

van9
07-07-2014, 15:53
Premesso che sono assolutamente contrario sia ai "dialetti" dei linguaggi, che alla trasmissione di struct sul socket che lasciano spazio alle interpretazioni (leggasi endianness & co.), scrivendo "char message[];" non si definisce un array nella struttura, ma un puntatore ad un array da allocare che risiederà da qualche parte in memoria, per cui scrivere la struttura sul socket non invierebbe il messaggio ma un puntatore, che ovviamente perde di significato nel momento in cui viene trasmesso sulla rete.
In altre parole "char message[];" è esattamente equivalente a "char *message;", ed è assolutamente diverso da "char message[0];". Ma ribadisco che non mi piace in alcun caso.

No. Come dicevo, in C99 "char message[]" come ultimo membro di una struct e quando effettivamente preceduto da (almeno) un altro named member non è un puntatore ma un incomplete array type che prende il nome di "flexible array member". Studialo nello standard.

Il problema dello spedire su socket intere struct si risolve semplicemente non facendolo visto che è sempre e comunque una cattiva idea.

L' "equivalenza" tra array e puntatori non esiste - non sono la stessa cosa. Punto. Sono intercambiabili in alcune circostanze, che è molto diverso. Possibile sia così difficile da capire?

Di "char message[0];" già detto.

Daniels118
07-07-2014, 16:10
@van9
Credo che tu ti stia rivolgendo alla persona sbagliata, non sono io a voler trasmettere la struct sul socket.

Daniels118
07-07-2014, 16:12
@Mettiu_ ho modificato la risposta al tuo post :)

@Daniels118 quindi se gli shift agiscono sul valore indipendentemente dall'endianess del sistema è inutile il ntohl che ho applicato al mio int prima di fare la shift ? :D
Si, se usi gli shift puoi fare a meno di ntohl.

Mettiu_
07-07-2014, 16:39
@Mettiu_ ho modificato la risposta al tuo post :)


Non c'è ragione per cui l'int possa essere spezzettato se il trasmettitore fà una sola send().


@Daniels118 quindi se gli shift agiscono sul valore indipendentemente dall'endianess del sistema è inutile il ntohl che ho applicato al mio int prima di fare la shift ? :D

Vorrei ribadire la regola generale: se il tuo dato copre più bytes (come un uint32, che occupa 4 bytes) allora hai il problema dell'endianess e devi gestire la situazione tramite ntohl et similia. Se il tuo dato occupa solo un byte (come nel caso dei char) non c'è il problema dell'endianess perchè il modo di interpretare quel dato in memoria non cambia su piattaforme con diverso endianess. Per cui per la mia soluzione, valgono le mie considerazioni (devi usare htonl). Per la soluzione di Daniels, valgono le sue. La differenza è dovuta al fatto che lui suggerisce di inviare char, mentre io suggerisco di inviare un uint32.

van9
07-07-2014, 16:40
@van9
Credo che tu ti stia rivolgendo alla persona sbagliata, non sono io a voler trasmettere la struct sul socket.

Ah ok sorry. Il resto del post resta per te, ovviamente.

edit: no aspè mi sto imbrogliando. L'ho capito che tu non sei per la trasmissione diretta. Quello che non ho chiarito è che, utilizzando i flexible members di C99 è vero che si perde la possibilità di copia diretta tra due struct tramite operatore d'assegnamento (non perché si copi un pointer come erroneamente pensi tu ma perché viene copiato un incomplete array). Ma la cosa non ci tange comunque visto che un invio di dati binari su socket come in questo caso e non serializzato a dovere può solo portare problemi, a cominciare dal padding nelle strutture e via dicendo.

Daniels118
07-07-2014, 16:47
@Mettiu_
non c'è ragione per cui l'int possa essere spezzettato se il trasmettitore fà una sola send().
Ti invito a vedere questo esempio che avevo fatto:
[..] immagina questa situazione con dei pacchetti da 512 byte:
1) invio un messaggio da 506 byte + 4 per la lunghezza, ma il pacchetto non parte perché il buffer ha ancora spazio per 2 byte e il sistema aspetta ancora un po' per vedere se si riempie;
2) subito dopo invio un messaggio di 100 byte + 4 per la lunghezza;
3) il sistema bufferizza i dati e li divide in due pacchetti, il primo contiene 4+506+2 byte, il secondo ne contiene 2+100;
4) il client riceve il primo pacchetto e ne legge 4 byte, vede che il dato è lungo 506 byte e li legge;
5) il buffer contiene ancora 2 byte, quindi il client prova a leggere 4 byte, ma ne ottiene solo due perché il secondo pacchetto non è ancora arrivato, quindi interpreta male la lunghezza del dato... da qui in poi si scatena l'inferno :muro:
Questo non significa che facendo una sola recv il programma non funziona mai, ma significa che a volte potrebbe funzionare e a volte no. Per l'esattezza se siamo fortunati potrebbe anche funzionare sempre, se si verificano le condizioni che avevo in un post successivo.

Daniels118
07-07-2014, 16:56
No. Come dicevo, in C99 "char message[]" come ultimo membro di una struct e quando effettivamente preceduto da (almeno) un altro named member non è un puntatore ma un incomplete array type che prende il nome di "flexible array member". Studialo nello standard.
Ammetto di essere rimasto qualche standard indietro, se ne avrò la necessità mi aggiornerò :)
L' "equivalenza" tra array e puntatori non esiste - non sono la stessa cosa. Punto. Sono intercambiabili in alcune circostanze, che è molto diverso. Possibile sia così difficile da capire?
Non è difficile da capire, come giustamente affermi dipende dalle circostanze, purtroppo l'uso improprio di alcuni termini a volte diventa un'abitudine.

aeroxr1
07-07-2014, 18:01
Si, se usi gli shift puoi fare a meno di ntohl.

ma alla fine comunque un ntohl lo devo fare o sbaglio ?

cioè io shifto il valore che ho ricevuto di (8bit*il numero di byte letti) e poi faccio la or con i byte letti al nuovo giro.

Ma con questa soluzione ho due dubbi :

- ammettendo di aver fatto la procedura corretta alla fine quando ho il dato definitivo devo comunque usare ntohl sulla variabile contenente la lettura finale o sbaglio ?

- questo metodo funziona se il primo byte ricevuto è il byte più significativo, non so come mai ma nella mia testa ho dato per scontato funzionasse così, ma è realmente così ?

Daniels118
09-07-2014, 08:28
No, se usi lo shift non c'è bisogno di usare ntohl perché sei tu stesso a controllare l'ordine dei byte.

aeroxr1
09-07-2014, 09:50
No, se usi lo shift non c'è bisogno di usare ntohl perché sei tu stesso a controllare l'ordine dei byte.

Ho riguardato il codice e qualcosa non torna devo rivedere un pò la logica della bufferizzazione dell'int :rolleyes:

In pseudocodice :

lato client :
send(intero 32bit)

lato server :
ret=recv(&temp)
variabile lunghezza salvata nella struttura client
poi ad ogni recv farò una cosa del genere :

rimanenti=4-ret;
lunghezza | temp
if(ret != rimanenti) //non ho finito di ricevere
client->lunghezza<<8*ret (shifto la lunghezza per metterla in posizione di aggiungerci i byte che verranno ricevuti al giro dopo)
if (ret==rimanenti)
{
client-> temp = 0;
client->rimanenti = 4;
return;
}

Facendo così però mi va bene se il client invia il byte in questo modo:

parte più significativa
parte meno significativa

così il ricevitore riceverà prima la parte più significativa e poi la parte meno significativa.

Se lato sender invio in network byte order send(htonl(lunghezza)) quindi con la parte più significativa a destra dovrei ricevere lato ricevitore prima la parte più alta e poi quella più bassa giusto ? :stordita:

WarDuck
09-07-2014, 10:12
[..] immagina questa situazione con dei pacchetti da 512 byte:
1) invio un messaggio da 506 byte + 4 per la lunghezza, ma il pacchetto non parte perché il buffer ha ancora spazio per 2 byte e il sistema aspetta ancora un po' per vedere se si riempie;
2) subito dopo invio un messaggio di 100 byte + 4 per la lunghezza;
3) il sistema bufferizza i dati e li divide in due pacchetti, il primo contiene 4+506+2 byte, il secondo ne contiene 2+100;
4) il client riceve il primo pacchetto e ne legge 4 byte, vede che il dato è lungo 506 byte e li legge;
5) il buffer contiene ancora 2 byte, quindi il client prova a leggere 4 byte, ma ne ottiene solo due perché il secondo pacchetto non è ancora arrivato, quindi interpreta male la lunghezza del dato... da qui in poi si scatena l'inferno

Tu dici ho un pacchetto con: 4+506+2 bytes.

Leggo 4, capisco che devo leggere altri 506, leggo 506. Rimangono 2 bytes.

Quando chiedo 4 bytes, ovviamente mi devo assicurare che la recv legga 4 bytes, quindi il giusto modo di fare la cosa è fare la recv in un ciclo finché non leggo effettivamente il giusto numero di bytes.

Quello che dici non è un problema.

aeroxr1
09-07-2014, 10:54
Tu dici ho un pacchetto con: 4+506+2 bytes.

Leggo 4, capisco che devo leggere altri 506, leggo 506. Rimangono 2 bytes.

Quando chiedo 4 bytes, ovviamente mi devo assicurare che la recv legga 4 bytes, quindi il giusto modo di fare la cosa è fare la recv in un ciclo finché non leggo effettivamente il giusto numero di bytes.

Quello che dici non è un problema.

si infatti lui mi ha consigliato di fare un ciclo per finire di leggere i byte rimanenti :)

Solo che il mio server deve essere monoprocesso monothread e devo usare la select , ma al tempo stesso non vorrei rimanere in loop per troppo tempo aspettando di aver ricevuto tutti i byte.

Per questo mi ha poi consigliato di gestire un buffer :)

Ora il buffer per la stringa dati mi sembra di averlo fatto e teoricamente mi sembra funzioni.

Il buffer per la ricezione dell'int della lunghezza del dato invcece mi crea non pochi problemi :stordita:

EDIT [forse così potrebbe funzionare] :
int riceviLength() //usando lo shift sistemo i byte in host order senza usare la funzione ntohl
{
//in realtà inviando un int per la dimensione invio ben 4byte, devo gestire un buffer per la lunghezza
int ret;
int temp; //al suo interno ci viene salvato il dato letto
ret = recvTCP(client->socket,&temp,4-client->last);
client->length=(client->length)<<(8*(ret));
client->length=(client->length)|temp;

if(ret<0) return -1; //c'è stata una chiusura della connessione già gestita dalla recvTCP o il dato è ricevuto correttamente
if (ret==0) //abbiamo finito di leggere
{
client->last=0;
return 0;
}
client->last=client->last-ret;
return ret;
}


ha qualche problemuccio questa funzione ?

Ah recvTCP mi ritorna 0 se ho letto tutto length , -1 se ci sono stati errori , ret se non abbiamo ancora finito di leggere.

WarDuck
09-07-2014, 14:33
si infatti lui mi ha consigliato di fare un ciclo per finire di leggere i byte rimanenti :)

Solo che il mio server deve essere monoprocesso monothread e devo usare la select , ma al tempo stesso non vorrei rimanere in loop per troppo tempo aspettando di aver ricevuto tutti i byte.


Questo è inevitabile, a meno di non usare socket non bloccanti.

Se ti aspetti di leggere 4 bytes, dovrai per forza aspettare che arrivino :D.

aeroxr1
09-07-2014, 16:40
Questo è inevitabile, a meno di non usare socket non bloccanti.

Se ti aspetti di leggere 4 bytes, dovrai per forza aspettare che arrivino :D.

QUel problema l'ho risolto con gli stati :D

3 stati :
stato 0 -> attesa comando
stato 1 -> attesa lunghezza dati
stato 2 -> attesa dato

Ogni client ha una struttura e in essa è presente una variabile stato ultimo dato letto e un buffer, e da uno stato a l'altro ci va solo quando ha finito di fare la lettura. Così nel frattempo che il determinato socket non ha i dati disponibile la select riprende il controllo e può gestire le altre connessioni.

Mi manca di capire se ho gestito in maniera corretta i buffer. Che in locale è difficile capirlo :muro:

aeroxr1
10-07-2014, 20:15
ah scusate , ho una domandina nuova :)

Se mando una stringa avete detto non c'è problemi di endianess, come mai questa differenza di comportamento ? :D

e una domanda un pò off topic :

scanf vs fgets nella lettura dello stdin .

se leggo la stringa "pippo" e l'assegno alla variabile stringa usando scanf questa mi lascia \n in memoria e aggiunge l'eof a pippo e quindi in stringa ci sarà "pippo\0"
con fgets il buffer stdin mi viene completamente ripulito e viene assegnato a stringa "pippo\n\0" ? corretto ? :)
Quindi in una lettura di un un numero che poi dopo vado a convertire con strtol tutto ok perchè \n non me lo converte , ma se devo leggere una stringa è bene elimini l'ultimo carattere \n o sbaglio ? :) Come dovrei gestire questa situazione :D

Daniels118
11-07-2014, 14:01
Se mando una stringa avete detto non c'è problemi di endianess, come mai questa differenza di comportamento ?
Alcune famiglie di processori scambiano l'ordine dei byte quando li copiano tra memoria e registri. Ciò naturalmente vale solo quando si copiano 2 o più byte alla volta, e di conseguenza si applica alla maggior parte dei tipi di dato numerici ed anche alle stringhe con codifica multibyte.
Questa differenza di comportamento è nata diversi decenni fa, fondamentalmente per risparmiare sulla realizzazione dei circuiti (non so nello specifico in che modo), ed è stata ereditata dai processori più recenti per mantenere la retrocompatibilità.
Anche i compilatori possono impattare sull'endiannes quando lavorano con dati la cui dimensione supera quella dei registri, in questi casi infatti è il compilatore stesso a decidere l'ordine delle "parole" in memoria.

aeroxr1
14-07-2014, 09:17
ma quindi se invio la stringa

char pippo[10]={asino/0};

dall'altra parte, dato che invio 6 byte non è detto che arrivi la parola nell'ordine corretto ? :doh:

:stordita:

Oceans11
14-07-2014, 12:49
ma quindi se invio la stringa

char pippo[10]={asino/0};

dall'altra parte, dato che invio 6 byte non è detto che arrivi la parola nell'ordine corretto ? :doh:

:stordita:

No.
Come ti è stato detto, si applica alla maggior parte dei tipi di dato numerici ed anche alle stringhe con codifica multibyte.

Daniels118
14-07-2014, 12:57
La risposta di Oceans11 è corretta.
Il motivo è che quando copi una stringa con codifica a byte singolo (per esempio ASCII), l'algoritmo copia i byte uno alla volta, nell'ordine in cui si trovano.
Se invece copi una stringa con codifica a 2 byte, l'algoritmo potrebbe copiare i byte due per volta (nel senso che fa un'unica MOVE a 16 bit); l'ordine delle lettere resta invariato, ma l'ordine dei byte di ciascuna lettera potrebbe essere invertito o meno in base all'endiannes.
Se hai intenzione di utilizzare codifiche multibyte devi tener conto anche di questo aspetto, altrimenti puoi semplicemente ignorarlo.

aeroxr1
15-07-2014, 09:51
La risposta di Oceans11 è corretta.
Il motivo è che quando copi una stringa con codifica a byte singolo (per esempio ASCII), l'algoritmo copia i byte uno alla volta, nell'ordine in cui si trovano.
Se invece copi una stringa con codifica a 2 byte, l'algoritmo potrebbe copiare i byte due per volta (nel senso che fa un'unica MOVE a 16 bit); l'ordine delle lettere resta invariato, ma l'ordine dei byte di ciascuna lettera potrebbe essere invertito o meno in base all'endiannes.
Se hai intenzione di utilizzare codifiche multibyte devi tener conto anche di questo aspetto, altrimenti puoi semplicemente ignorarlo.

scusate ho domandato senza nemmeno essere sicuro di aver capito completamente la risposta , avevo confuso la "codifica multibyte" con una stringa di più byte :doh:

Grazie a tutti :)

aeroxr1
15-07-2014, 17:11
Ho un grosso problema , ma non sapendo bene fare ad usare il debug è difficile che riuscirò a risolverlo :(

Non capisco come , ma nonostante elimini un elemento dalla lista dei client al passo dopo mi continua a dire che quell'elemento è ancora presente .. boh...

Ho trovato dove è l'errore, ma non capisco come mai....
Qualche anima pia , ha due secondi per dare un occhiata al file ? :(

Ecco di seguito :


struct elemento
{
char username[256]; //Username del giocatore [max 14 caratteri]
short int UDPport;
unsigned long IPaddress;
int socket; //id del socket
char ready; //indica se il client ha effettuato il login o è sempre in attesa di login e quindi non ancora connesso
char impegnato; //mi indicherà Se il client è impegnato in un'altra partita
char cmd ; //il comando che stiamo eseguendo. Inizialmente in waiting !
int stato; //variabile di stato che mi dice in che parte della macchina a stati si trova il client
int length; //lunghezza campo dati
char buffer[256]; //buffer dove vengono ricevuti i dati temporaneamente prima di essere ricevuti completamente, buffer dinamico
int last; //variabile che indica fino a che punto abbiamo riempito il buffer in caso di lettura non terminata
struct elemento *next; //puntatore all'elemento successivo della lista
struct elemento *enemy; //puntatore alla struttura dell'avversario
};

typedef struct elemento player;


di seguito la funzione che uso per creare un elemento player :


player *creatElem(int socket,unsigned long IPaddress)
{
player *elem = NULL;
elem = malloc(sizeof(player));
elem->IPaddress = IPaddress;
elem->socket = socket;
elem->ready = NO; //inizialmente si crea l'elemento ma non è realmente connesso !
elem->impegnato = NO;
elem->next = NULL;
elem->enemy = NULL; //puntatore al socket dell'avversario
elem->stato = 0;
elem->last=0;
memset(elem->username,0,sizeof(elem->username));
memset(elem->buffer,0,sizeof(elem->buffer));
return elem; //ritorno l'elemento creato
}


// PARTE DEL MAIN CHE MI CREA PROBLEMI

for(i=0;i<=fdmax;i++)
{
if(FD_ISSET(i,&read_fds)){ //controlla se un descrittore è settato
if (i==listener) // se è il listener vuol dire che c'è una nuova connessione in sospeso
{
//gestisco la nuova connessione
addrlen = sizeof(clientaddr);
newfd=accept(listener,(struct sockaddr*)&clientaddr,&addrlen);
if(newfd==-1)
{
perror("errore nell'accept");
exit(1);
}
printf("connessione stabilita con il client\n");
FD_SET(newfd,&master); //setto il descrittore del socket appena connesso nel master
if (newfd>fdmax) fdmax=newfd; //fdmax segnala quale è il più alto descrittore
//creo un elemento del tipo Player socket newfd e ind IPaddress e lo inserisco nella lista ""inconnection"
client=creatElem(newfd,clientaddr.sin_addr.s_addr);
addToList(client);
printf("Il nuovo elemento puntato è : %s \n",client->username);
}
else
{
// gestisco dei dati provenienti da uno dei client che sono o nella lista connessi
client=elem_findbysock(i);
if (client==NULL)
{
printf("il client non esiste, deve esserci stato un errore\n");
exit(1);
}
//è stato trovato un client, verifichiamo in che stato si trova tale client

......................



se levo la parte in neretto , nella parte in rosso nonostante crei un nuovo elemento con la malloc rimane comunque il dato inserito da un utente che è stato rimosso precedentemente.

Come è possibile ? Dove ho sbagliato ??

aeroxr1
16-07-2014, 19:33
Allora ho deciso passo alla codifica del numero in un char da 4byte con sprintf come mi ha consigliato @Daniels118

(con la shift non sono sicuro sia riuscito ad implementarla nel modo corretto)

Quindi farò così :

Nulla di più vero di quanto dice Oceans11.
Suggerirei di convertire il numero che rappresenta la lunghezza del dato in una stringa di lunghezza fissa, per esempio int 5 diventa char[4] "0005", in questo modo la lettura diventa più semplice (leggi sempre 4 byte fissi) e risolvi il problema dell'endianess.


In realtà leggo sempre 5byte non 4byte inviando una stringa di 4 giusto ? :) sarebbe tipo 0005\0 o sbaglio ? :D

Ricordati che la recv non ritorna il dato letto, ma devi passare in input un puntatore ad un buffer. Io personalmente farei così:
char buf[4];
int n = 0;
while (n < 4) {
n += recv(sockfd, &buf[n], 4 - n, 0);
}


mettendo un bel buf[5] e il ciclo n<5 , no ? :)

p.s : in effetti potrei anche evitare di ricevere \0 , però mi potrebbe tornare utile quando chiamerò la funzione di conversione da stringa ad intero :)