View Full Version : [c++]Come capire se un socket è attivo...
stefanoxjx
08-12-2016, 21:06
Ciao a tutti, sto giocando un po' con i socket per imparare qualcosa di nuovo.
Facendo un po' di prove sono riuscito a mettere su un pseudo server telnet, però come sempre c'è un problema che non riesco a risolvere :(
Allora, il server che ho scritto, accetta due comandi, ciao e quit.
Ovviamente con ciao risponde saluti e con quit chiude la connessione.
Fin qui non ci sono problemi, nel senso che con quit richiamo close(socket) e imposto socket a -1, così il server sa che dovrà chiamare nuovamente accept() per reimpostare un socket valido.
Il problema però lo trovo quando a chiudere la connessione è il client, vuoi perchè il pc remoto si spegne, perchè l'utente chiude brutalmente il programma, perchè prende fuoco il cavo di rete ecc.
In questo caso, non riesco a trovare un modo per capire se la connessione è ancora attiva oppure no.
Nel caso il client chiuda la connessione, il socket del server rimane invariato.
Ho provato varie soluzioni anche proposte nei vari forum che ho visto facendo ricerche in merito, ma sembra che nemmeno una funzioni :(
L'ultima che ho provato per dirne una è questa:
int error;
socklen_t errCodeSize = sizeof(error);
getsockopt(clientSocket, SOL_SOCKET, SO_ERROR, &error, &errCodeSize);
ma anche in questo caso la variabile error rimane invariata in caso di caduta della connessione con il client.
Inutile che chieda se esiste una soluzione, sono sicuro che c'è, ma a questo punto chiedo aiuto ai più esperti.
Grazie.
Stefano
Usa setsockopt con SO_KEEPALIVE.
Ovviamente il SIGPIPE non arrivera' immediatamente ma questo e' il meglio che si possa fare perche' ovviamente non esiste modo di distinguere un silenzio del client dovuto al non invio di dati da un silenzio dovuto alla caduta della connessione.
BTW, se il client chiude la connessione in maniera regolare il server se ne dovrebbe accorgere, se cosi' non e' hai sbagliato qualcosa.
Il sistema operativo dell'host su cui gira il client dovrebbe chiudere le connessioni TCP in maniera regolare persino quando il processo viene killato malamente. Il keepalive serve solo a gestire casi come "mia sorella e' inciampata sul cavo".
sottovento
09-12-2016, 15:09
In realta' il server dovrebbe accorgersi immediatamente anche se la socket e' chiusa in maniera irregolare (per esempio se il client e' killato) se e' in attesa su una read.
C'e' qualcosa di strano nella descrizione che hai fatto del problema: hai detto che in caso di chiusura il server potra' passare a fare una nuova accept().
In realta' le cose sono scorrelate e normalmente un server e' continuamente in attesa di nuove connessioni: hai fatto per caso un server che puo' servire un solo client alla volta?
stefanoxjx
09-12-2016, 16:17
Alllora... per iniziare ringrazio entrambi per l'attenzione :)
Ora, venendo al problema su cui ho continuato a lavorare e per il quale non ho trovato ancora una soluzione:
@71106: A dire il vero non sono ancora arrivato a far chiudere la connessione regolarmente al client, al momento stavo lavorando solo sul server e come client sto usando telnet.
Se invio il comando quit al server è il server che chiude la connessione, ma come dicevo in questo caso per fare in modo che poi il server mi becchi una nuova connessione devo rilanciare un nuovo accept(), altrimenti poi riesco a collegarmi ancora con telnet senza errori, ma sembra che il server non riceva più nulla.
@sottovento:
C'e' qualcosa di strano nella descrizione che hai fatto del problema: hai detto che in caso di chiusura il server potra' passare a fare una nuova accept().
In realta' le cose sono scorrelate e normalmente un server e' continuamente in attesa di nuove connessioni: hai fatto per caso un server che puo' servire un solo client alla volta?
Su listen() ho specificato 3 connessioni anche se in realtà una volta messo a punto tutto volevo portare il valore a 1.
Siccome sto studiando anche il C++ e sto cercando di capire anche questo "nuovo" linguaggio, la voglia di studiare i socket mi è venuta per prendere due piccioni con una fava.
Cioè stavo cercando di scrivere una classe per gestire i socket.
Quindi mi sa che faccio prima a postare il codice che forse capite meglio voi e anche io :)
servertelnet.cpp
#include <fcntl.h>
#include <signal.h>
#include "servertelnet.h"
ServerTelnet::ServerTelnet() {}
int8_t ServerTelnet::createSocket(uint16_t port)
{
//Salva la porta in uso sulla variabile della classe
tcpPort=port;
//Crea il descrittore del socket
socketDescriptor = socket(AF_INET , SOCK_STREAM , 0);
if (socketDescriptor == -1) return ERRSOCKET;
//Prepare the sockaddr_in structure
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(tcpPort);
//Bind
if( bind(socketDescriptor,(struct sockaddr *)&server , sizeof(server)) < 0) return ERRBIND;
//Listen
listen(socketDescriptor , 3);
acceptConnection();
/*int32_t c = sizeof(struct sockaddr_in);
//accept connection from an incoming client
clientSocket = accept(socketDescriptor, (struct sockaddr *)&client, (socklen_t*)&c);
if (clientSocket < 0) return ERRCLIENTSOCKET;*/
return(0);
}
//Chiude il socket
void ServerTelnet::closeSocket(void)
{
close(clientSocket);
clientSocket=-1;
}
//Attende dati dal client e li restituisce come argomento string
string ServerTelnet::receiveData(void)
{
char clientMessage[BUFFERSIZE];
int16_t readSize;
//cout << clientSocket << endl;
//perror("boh");
//cout << errno << endl;
checkConn();
if(clientSocket==-1) return("");
//Legge i dati
readSize=recv(clientSocket,clientMessage, BUFFERSIZE , 0);
//Termina il buffer togliend gli ultimi due caratteri \r\n
if(readSize > 2) clientMessage[readSize-2]=0;
else clientMessage[0]=0;
//Ritorna il dato letto convertito in string
return(string(clientMessage));
}
//Invia dati al client e ritorna il numero di byte inviati oppure < 0 se errore
int8_t ServerTelnet::sendData(string pkg)
{
if(clientSocket==-1) return(-1);
else return(write(clientSocket, pkg.c_str(), pkg.length()));
}
//Ritorna la porta TCP con cui è stato aperto il socket
int16_t ServerTelnet::getPort(void)
{
return tcpPort;
}
//Ritorna il descrittore del socket
int64_t ServerTelnet::getSocketDescriptor(void)
{
return socketDescriptor;
}
//Ritorna il socket aperto dal client
int64_t ServerTelnet::getClientSocket(void)
{
return clientSocket;
}
//Crea un socket che può accettare connessioni
int8_t ServerTelnet::acceptConnection(void)
{
int32_t c = sizeof(struct sockaddr_in);
clientSocket = accept(socketDescriptor, (struct sockaddr *)&client, (socklen_t*)&c);
if (clientSocket < 0) return ERRCLIENTSOCKET;
return(0);
}
int8_t ServerTelnet::checkConn(void)
{
char buf[10];
int i=0;
i=write(clientSocket, "", 1);
// i=recv(clientSocket, &buf, 1, MSG_PEEK | MSG_DONTWAIT);
//cout << i << endl;
/*
cout << "EAGAIN" << EAGAIN << endl;
cout << "EBADF" << EBADF << endl;
cout << "ENOTCONN" << ENOTCONN << endl;
cout << errno << endl;*/
int err = 0;
socklen_t size = sizeof (err);
int check = getsockopt (clientSocket, SOL_SOCKET, SO_ERROR, &err, &size);
if (check != 0)
{
cout << "Errore socket" << endl;
}
//cout << check << endl;
return(check);
}
Il metodo checkConn è un po' pasticciato perchè ci sono varie prove che stavo facendo oltre a dell'altro codice che in tutte le varie prove ho buttato via perchè non funzionava.
servertelnet.h
#ifndef SERVERTELNET_H
#define SERVERTELNET_H
#include <string>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
const int8_t ERRSOCKET=-1;
const int8_t ERRBIND=-2;
const int8_t ERRCLIENTSOCKET=-3;
const uint16_t BUFFERSIZE=512;
using namespace std;
class ServerTelnet
{
public:
ServerTelnet();
int8_t createSocket(uint16_t port);
void closeSocket(void);
string receiveData(void);
int8_t sendData(string pkg);
int16_t getPort(void);
int64_t getSocketDescriptor(void);
int64_t getClientSocket(void);
int8_t acceptConnection(void);
int8_t checkConn(void);
private:
int64_t socketDescriptor;
int64_t clientSocket;
uint16_t tcpPort;
struct sockaddr_in server , client;
};
#endif // SERVERTELNET_H
main.cpp
#include <iostream>
#include <algorithm>
#include "servertelnet.h"
#include <signal.h>
using namespace std;
const uint8_t CIAO=0;
const uint8_t BUONGIORNO=1;
const uint8_t QUIT=2;
const uint8_t HELP=3;
const uint8_t STRINGEMPTY=100;
int main(void)
{
uint8_t i;
vector<string> cmd={"CIAO","BUONGIORNO","QUIT","HELP"};
ServerTelnet s;
cout << "Server in attesa..." << endl;
if(s.createSocket(8888) < 0)
{
perror("Errore");
exit(0);
}
string str;
while(1)
{
s.checkConn();
str=s.receiveData();
transform(str.begin(), str.end(), str.begin(), ::toupper);
if(s.getClientSocket() == -1) s.acceptConnection();
for(i=0; i < cmd.size(); i++) if(cmd[i]==str) break;
if(str=="") i=STRINGEMPTY;
switch(i)
{
case CIAO:
s.sendData("Ciao anche da me\n");
break;
case BUONGIORNO:
s.sendData("Buonanotte\n");
break;
case QUIT:
s.sendData("Fine sessione!\n");
s.closeSocket();
break;
case HELP:
for(i=0; i < cmd.size(); i++)
{
s.sendData(cmd[i]);
s.sendData("\n");
}
break;
case STRINGEMPTY:
break;
default:
s.sendData("Comando sconosciuto!\n");
}
}
}
Allora, con questo codice, risponde correttamente ai vari comandi, se digito quit mi chiude la sessione e poi rilanciando un nuovo accept() posso ricollegarmi tranquillamente, però se chiudo brutalmente telnet mi si chiude brutalmente anche il server con un bel SIGPIPE (che ho visto solo ora).
Quindi, visto che la segnalazione del sigpipe l'ho vista solo ora potrei provare ad intercettarla, non ho la più pallida idea di come si faccia ma confermatemi se può essere la strada giusta.
Visto che ci siete, se conoscete il C++ mi dite anche se ci sono errori nell'implementazione della classe?
Grazie.
Stefano
Io di solito uso il C su Linux comunque visto che nel codice C++ che hai scritto vedo solo C praticamente... quando la connessione è chiusa dal client la read() dovrebbe ritornarti con 0 (attento non leggi il byte '0x00', ma il valore di ritorno è proprio 0 che nel modo perverso di Linux significa End of File ovvero "Caduta Connessione") a quel punto tu chiudi la connessione dal tuo lato e ritorni in attesa di una nuova connessione.
Altro consiglio usa la funzione select() così potrai ascoltare eventuali input da tastiera (per esempio per chiudere il server con CONTROL+C), gestire i dati in input dal / dai client e una nuova connessione da un client (aggiungendo ai socket gestiti da select() il socket di listen appunto) tutto "contemporaneamente".
In molti casi comunque per rilevare velocemente lo scollegamento almeno su Linux dove SO_KEEPALIVE non sembra fare proprio nulla è avere un comando di ping da inviare di continuo a tutti i client per vedere se sono ancora vivi... è triste, ma alla fine è il modo più sicuro :D
sottovento
09-12-2016, 18:29
Su listen() ho specificato 3 connessioni anche se in realtà una volta messo a punto tutto volevo portare il valore a 1.
In realta' la listen serve solo per marcare la socket come passiva, ed il numero seguente e' il numero di connessioni client pendenti.
Infatti se la esegui con il debugger, ti accorgerai che l'esecuzione non si ferma li', ma la listen() viene eseguita subito e si prosegue fino alla accept().
L'esecuzione verra' fermata alla accept() fintanto che si rende disponibile un client e la socket con il client viene creata.
In effetti, cosi' come hai scritto il codice, il tuo server effettuera' solo una accept(); una volta accettata la connessione con il client, vai ad operare con il client connesso e non accetterai piu' ulteriori client, almeno fino a quando il client attuale non chiudera' la connessione. Il tuo server pertanto puo' interagire con un solo client alla volta. Un po' poco, vero? :D
Comunque non disperare, questa cosa la puoi vedere sicuramente dopo.
Volevo farti notare una cosa:
string ServerTelnet::receiveData(void)
{
char clientMessage[BUFFERSIZE];
int16_t readSize;
//cout << clientSocket << endl;
//perror("boh");
//cout << errno << endl;
checkConn();
if(clientSocket==-1) return("");
//Legge i dati
readSize=recv(clientSocket,clientMessage, BUFFERSIZE , 0);
//Termina il buffer togliend gli ultimi due caratteri \r\n
if(readSize > 2) clientMessage[readSize-2]=0;
else clientMessage[0]=0;
//Ritorna il dato letto convertito in string
return(string(clientMessage));
}
Stai leggendo i dati da uno stream (tcp/ip = stream). Tcp/ip ti garantisce
- che i dati spediti arrivino;
- che arrivino nell'ordine giusto;
Tuttavia, non ti garantisce che arrivino tutti insieme! Potresti spedire "Hello, world" e potrebbe arrivare "Hello," immediatamente, ma " world" potrebbe arrivare con il pacchetto successivo.
In questo caso la tua receiveData() avrebbe dei problemi: ritornerebbe un pezzo del valore che ti aspetti (quindi non lo riconosceresti nei comandi che hai implementato); poi arriverebbe l'altro pezzo e non riconosceresti nemmeno quello!
Infine, come dicevo: se fai una read() BLOCCANTE (i.e. resti sulla socket fintanto che ricevi qualcosa), riconoscerai immediatamente la situazione di chiusura della socket da parte del client, anche nel caso che il client vada in crash e non abbia modo di chiudere la socket.
Normalmente restare bloccati su una read non e' un buon stile di programmazione in un server, ma puo' essere utile in questa fase del progetto, per avere immediatamente la notifica di errore a disposizione.
stefanoxjx
09-12-2016, 18:47
Io di solito uso il C su Linux comunque visto che nel codice C++ che hai scritto vedo solo C praticamente...
Effettivamente nel codice che ho scritto di C++ c'è solo la struttura.
Sto cercando di cambiare mentalità e passare dal procedurale agli oggetti ma dopo anni di programmazione procedurale non è per niente facile :(
Devo dire però che nella struttura del C++ ho già trovato alcuni vantaggi.
Per esempio riesco a gestire meglio il codice, nel senso che lo trovo più ordinato.
Altra cosa che mi piace è il fatto di avere delle variabili che possiamo definire globali nella classe senza obbligatoriamente doverle passare come parametro ad ogni metodo/funzione.
quando la connessione è chiusa dal client la read() dovrebbe ritornarti con 0 (attento non leggi il byte '0x00', ma il valore di ritorno è proprio 0 che nel modo perverso di Linux significa End of File ovvero "Caduta Connessione") a quel punto tu chiudi la connessione dal tuo lato e ritorni in attesa di una nuova connessione.
Ora mi metti in crisi (o forse no).
Avevo già provato questa soluzione ma a mio avviso non funzionava.
Forse avevo interpretato male lo zero di ritorno.
Mi ci ributto e faccio ulteriori prove.
Altro consiglio usa la funzione select() così potrai ascoltare eventuali input da tastiera (per esempio per chiudere il server con CONTROL+C), gestire i dati in input dal / dai client e una nuova connessione da un client (aggiungendo ai socket gestiti da select() il socket di listen appunto) tutto "contemporaneamente".
La funzione select() non la conosco proprio, vedrò di approfondire.
In molti casi comunque per rilevare velocemente lo scollegamento almeno su Linux dove SO_KEEPALIVE non sembra fare proprio nulla è avere un comando di ping da inviare di continuo a tutti i client per vedere se sono ancora vivi... è triste, ma alla fine è il modo più sicuro :D
Questa è una soluzione che avevo pensato anch'io, ma non saprei come generare un pacchetto di ping (beata ignoranza :muro:).
Hai per caso qualche link o due righe di codice da passarmi?
Grazie.
stefanoxjx
09-12-2016, 19:06
In realta' la listen serve solo per marcare la socket come passiva, ed il numero seguente e' il numero di connessioni client pendenti.
Infatti se la esegui con il debugger, ti accorgerai che l'esecuzione non si ferma li', ma la listen() viene eseguita subito e si prosegue fino alla accept().
L'esecuzione verra' fermata alla accept() fintanto che si rende disponibile un client e la socket con il client viene creata.
Si si, con debug avevo già visto questa cosa.
In effetti, cosi' come hai scritto il codice, il tuo server effettuera' solo una accept(); una volta accettata la connessione con il client, vai ad operare con il client connesso e non accetterai piu' ulteriori client, almeno fino a quando il client attuale non chiudera' la connessione. Il tuo server pertanto puo' interagire con un solo client alla volta. Un po' poco, vero? :D
Comunque non disperare, questa cosa la puoi vedere sicuramente dopo.
Si lo so, ma per iniziare ho usato questo approccio per incasinarmi meno.
Al momento mi basta la comunicazione con un solo client.
Volevo farti notare una cosa:
string ServerTelnet::receiveData(void)
{
char clientMessage[BUFFERSIZE];
int16_t readSize;
//cout << clientSocket << endl;
//perror("boh");
//cout << errno << endl;
checkConn();
if(clientSocket==-1) return("");
//Legge i dati
readSize=recv(clientSocket,clientMessage, BUFFERSIZE , 0);
//Termina il buffer togliend gli ultimi due caratteri \r\n
if(readSize > 2) clientMessage[readSize-2]=0;
else clientMessage[0]=0;
//Ritorna il dato letto convertito in string
return(string(clientMessage));
}
Stai leggendo i dati da uno stream (tcp/ip = stream). Tcp/ip ti garantisce
- che i dati spediti arrivino;
- che arrivino nell'ordine giusto;
Tuttavia, non ti garantisce che arrivino tutti insieme! Potresti spedire "Hello, world" e potrebbe arrivare "Hello," immediatamente, ma " world" potrebbe arrivare con il pacchetto successivo.
In questo caso la tua receiveData() avrebbe dei problemi: ritornerebbe un pezzo del valore che ti aspetti (quindi non lo riconosceresti nei comandi che hai implementato); poi arriverebbe l'altro pezzo e non riconosceresti nemmeno quello!
Ah, questo non lo sapevo.
Ma quindi, peggiorando ulteriormente la cosa se invio "Hello world", potrebbe arrivare anche "He", poi "llo" e "world"!?!?
Quindi mi conviene sempre controllare che sul pacchetto arrivato ci sia un "\r\n" finale per capire se è completo?
Infine, come dicevo: se fai una read() BLOCCANTE (i.e. resti sulla socket fintanto che ricevi qualcosa), riconoscerai immediatamente la situazione di chiusura della socket da parte del client, anche nel caso che il client vada in crash e non abbia modo di chiudere la socket.
Normalmente restare bloccati su una read non e' un buon stile di programmazione in un server, ma puo' essere utile in questa fase del progetto, per avere immediatamente la notifica di errore a disposizione.
Capito, ma io sto effettivamente facendo un read bloccante.
Nel frattempo che rispondevo a te ho fatto la prova sul parametro di ritorno della funzione recv() come spiegato da FANO e chiarita la cosa come me l'ha spiegata lui sembra che funzioni :)
Sicuramente salterà fuori qualche altro problema, vi prego di non mandarmi a quel paese se vi romperò un po' le scatole :banned:
Grazie.
Stefano
In realta' il server dovrebbe accorgersi immediatamente anche se la socket e' chiusa in maniera irregolare (per esempio se il client e' killato) se e' in attesa su una read. Ripeto: se il client e' killato il sistema operativo si occupa comunque di inviare i pacchetti TCP necessari a chiudere la connessione, ma nella casistica "mia sorella e' inciampata sul cavo" non esiste modo di avere una notifica immediata. L'unica sono i pacchetti di keepalive.
sottovento
09-12-2016, 20:43
Ripeto: se il client e' killato il sistema operativo si occupa comunque di inviare i pacchetti TCP necessari a chiudere la connessione, ma nella casistica "mia sorella e' inciampata sul cavo" non esiste modo di avere una notifica immediata. L'unica sono i pacchetti di keepalive.
Beh si, stavamo dicendo la stessa cosa... ;)
sottovento
09-12-2016, 20:47
Ah, questo non lo sapevo.
Ma quindi, peggiorando ulteriormente la cosa se invio "Hello world", potrebbe arrivare anche "He", poi "llo" e "world"!?!?
Esatto!
Quindi mi conviene sempre controllare che sul pacchetto arrivato ci sia un "\r\n" finale per capire se è completo?
Si. Oppure all'inizio potresti implementare un protocollo piu' semplice (per esempio, messaggi tutti della stessa lunghezza, ecc.). Comunque hai ragione, dovresti controllare se hai ricevuto tutto il comando fino ad arrivare al new line.
sottovento
09-12-2016, 20:51
Ripeto: se il client e' killato il sistema operativo si occupa comunque di inviare i pacchetti TCP necessari a chiudere la connessione, ma nella casistica "mia sorella e' inciampata sul cavo" non esiste modo di avere una notifica immediata. L'unica sono i pacchetti di keepalive.
Senti un po': so che hai sempre delle buone idee.... hai controllato il mio thread? Magari hai un buon suggerimento per me :rolleyes:
Senti un po': so che hai sempre delle buone idee.... hai controllato il mio thread? Magari hai un buon suggerimento per me :rolleyes: Wat...? :stordita:
A 'sto punto siamo, mi vengono a cercare negli altri thread. :D
Se parli di questo (http://www.hwupgrade.it/forum/showthread.php?t=2796138), non ho la piu' pallida idea di che roba sia. La parentesi "Microsoft" della mita e' stata completamente chiusa qualche anno fa.
sottovento
10-12-2016, 00:02
Wat...? :stordita:
A 'sto punto siamo, mi vengono a cercare negli altri thread. :D
Se parli di questo (http://www.hwupgrade.it/forum/showthread.php?t=2796138), non ho la piu' pallida idea di che roba sia. La parentesi "Microsoft" della mita e' stata completamente chiusa qualche anno fa.
Fortunato te! Pazienza, ci ho provato.
Chiedo scusa a tutti per l'off topic
Esatto!
Si. Oppure all'inizio potresti implementare un protocollo piu' semplice (per esempio, messaggi tutti della stessa lunghezza, ecc.). Comunque hai ragione, dovresti controllare se hai ricevuto tutto il comando fino ad arrivare al new line.
Se vuoi una cosa più seria potresti fare in modo che i tuoi messaggi siano fatti così con un header anche molto semplice:
typedef struct {
msg_type_t msg_type;
size_t msg_len;
} header_t;
msg_type_t sarà un enum che per esempio ha i valori "GREETING, PING, ..." se il messaggio prevede un body sarà diverso da 0.
Quindi la tua routine di lettura:
1. Legge sempre quanti byte sono un header_t
2. Analizzi il valore di ritorno di read se è 0, la connessione è caduta, se è -1 hai avuto un errore ---> sei fritto!
3. Se vuoi essere paranoico controlla anche che rc sia uguale alla sizeof di header_t così da proteggerti dalla short read
4. OK l'header è corretto analizza msg_type e decidi cosa fare in base a quello
5. Se il msg_type prevede un body procedi a leggerlo è lungo header.msg_len :D
Il messaggio di ping è appunto banale è un semplice header con valore PING dipende da te cosa il client risponde potrebbe essere anche l'echo del messaggio inviato... lo invii ad intervalli regolari ed attendi la risposta (per dire ogni 3 secondi lo invii, ne aspetti 2 per la risposta e se non la ottieni: connessione caduta!)
A quel punto - però - telnet non andrà più bene come client ovviamente visto che di quell'header binario da me proposto se ne frega altamente :Prrr:
vBulletin® v3.6.4, Copyright ©2000-2026, Jelsoft Enterprises Ltd.