|
|
|
![]() |
|
Strumenti |
![]() |
#1 |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
[c] consiglio su app socket :)
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 ? ![]() Una variabile che identifica lo stato di ogni client ? ![]() |
![]() |
![]() |
![]() |
#2 |
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
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.
|
![]() |
![]() |
![]() |
#3 | |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Quote:
-> 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 ? ![]() Grazie mille per l'aiuto che mi state dando ![]() |
|
![]() |
![]() |
![]() |
#4 |
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
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. |
![]() |
![]() |
![]() |
#5 | |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Quote:
![]() |
|
![]() |
![]() |
![]() |
#6 | |
Senior Member
Iscritto dal: Sep 2005
Città: Torino
Messaggi: 606
|
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 Quote:
Codice:
5hello Occhio al terminatore di stringa e all'endianess degli interi (usa ntohl e htonl)
__________________
"Se proprio dovete piratare un prodotto, preferiamo che sia il nostro piuttosto che quello di qualcun altro." [Jeff Raikes] "Pirating software? Choose Microsoft!" |
|
![]() |
![]() |
![]() |
#7 |
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
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. Ultima modifica di Daniels118 : 02-07-2014 alle 12:34. |
![]() |
![]() |
![]() |
#8 |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Intanto grazie per le risposte
![]() ![]() 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 ? ![]() (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 ![]() |
![]() |
![]() |
![]() |
#9 |
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
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. Ultima modifica di Daniels118 : 02-07-2014 alle 14:51. |
![]() |
![]() |
![]() |
#10 | |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Quote:
![]() 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 ![]() ![]() 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 ? ![]() 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 ? ![]() |
|
![]() |
![]() |
![]() |
#11 |
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
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
![]() 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 ![]() 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. |
![]() |
![]() |
![]() |
#12 |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
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 ![]() 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. ![]() |
![]() |
![]() |
![]() |
#13 | ||
Senior Member
Iscritto dal: Sep 2005
Città: Torino
Messaggi: 606
|
Quote:
Una cosa tipo: Codice:
#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 } Quote:
__________________
"Se proprio dovete piratare un prodotto, preferiamo che sia il nostro piuttosto che quello di qualcun altro." [Jeff Raikes] "Pirating software? Choose Microsoft!" |
||
![]() |
![]() |
![]() |
#14 | ||
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
Quote:
Quote:
Il mio parere è che dovresti implementare una routine che bufferizza i dati letti, quindi fai l'elaborazione solo se i dati sono effettivamente sufficienti. |
||
![]() |
![]() |
![]() |
#15 | |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Quote:
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 ![]() 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 ![]() ![]() Il secondo metodo non so nemmeno se sia fattibile o meno ![]() 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 ? Ultima modifica di aeroxr1 : 02-07-2014 alle 20:49. |
|
![]() |
![]() |
![]() |
#16 |
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
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.
|
![]() |
![]() |
![]() |
#17 | |
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Quote:
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 |
|
![]() |
![]() |
![]() |
#18 |
Senior Member
Iscritto dal: May 2001
Messaggi: 12814
|
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: Codice:
struct message_header { uint32_t length; char variable_message[0]; // nn ricordo ma dovrebbe funzionare su C99 }; Codice:
sizeof(struct message_header) + lunghezza 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... Ultima modifica di WarDuck : 02-07-2014 alle 21:09. |
![]() |
![]() |
![]() |
#19 | ||
Senior Member
Iscritto dal: Mar 2006
Messaggi: 2056
|
Quote:
![]() 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. ![]() Quote:
Devo capire come inviare una struttura ![]() E poi devo capire un paio di chicche del codice che hai scritto ![]() Dichiarando : Codice:
"char variable_message[0];" ![]() ![]() 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/9...e-array-in-c-c ] Grazie !!! Ultima modifica di aeroxr1 : 03-07-2014 alle 00:33. |
||
![]() |
![]() |
![]() |
#20 | |||
Senior Member
Iscritto dal: Jan 2014
Messaggi: 852
|
Quote:
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. Codice:
struct message_header { uint32_t length; char variable_message[0]; // nn ricordo ma dovrebbe funzionare su C99 }; 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. Quote:
Quote:
|
|||
![]() |
![]() |
![]() |
Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 09:07.