|
|||||||
|
|
|
![]() |
|
|
Strumenti |
|
|
#1 |
|
Senior Member
Iscritto dal: Jul 2002
Città: Padova
Messaggi: 4245
|
[c++]Come capire se un socket è attivo...
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: Codice:
int error;
socklen_t errCodeSize = sizeof(error);
getsockopt(clientSocket, SOL_SOCKET, SO_ERROR, &error, &errCodeSize);
Inutile che chieda se esiste una soluzione, sono sicuro che c'è, ma a questo punto chiedo aiuto ai più esperti. Grazie. Stefano |
|
|
|
|
|
#2 |
|
Bannato
Iscritto dal: Nov 2014
Messaggi: 292
|
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. |
|
|
|
|
|
#3 |
|
Bannato
Iscritto dal: Nov 2014
Messaggi: 292
|
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". |
|
|
|
|
|
#4 |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
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?
__________________
In God we trust; all others bring data |
|
|
|
|
|
#5 |
|
Senior Member
Iscritto dal: Jul 2002
Città: Padova
Messaggi: 4245
|
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: Codice:
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? 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 Codice:
#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);
}
servertelnet.h Codice:
#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
Codice:
#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");
}
}
}
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 |
|
|
|
|
|
#6 |
|
Senior Member
Iscritto dal: Nov 2005
Messaggi: 2095
|
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
__________________
Cosmos C# Open Source Managed Operating System Cosmos Thread Ufficiale Cosmos Official Site Vuoi collaborare allo sviluppo? Unisciti alla chat! |
|
|
|
|
|
#7 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Quote:
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? Comunque non disperare, questa cosa la puoi vedere sicuramente dopo. Volevo farti notare una cosa: Codice:
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));
}
- 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.
__________________
In God we trust; all others bring data |
|
|
|
|
|
|
#8 | ||||
|
Senior Member
Iscritto dal: Jul 2002
Città: Padova
Messaggi: 4245
|
Quote:
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. Quote:
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. Quote:
Quote:
Hai per caso qualche link o due righe di codice da passarmi? Grazie. |
||||
|
|
|
|
|
#9 | ||||
|
Senior Member
Iscritto dal: Jul 2002
Città: Padova
Messaggi: 4245
|
Quote:
Quote:
Al momento mi basta la comunicazione con un solo client. Quote:
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? Quote:
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 Grazie. Stefano |
||||
|
|
|
|
|
#10 |
|
Bannato
Iscritto dal: Nov 2014
Messaggi: 292
|
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.
|
|
|
|
|
|
#11 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Quote:
__________________
In God we trust; all others bring data |
|
|
|
|
|
|
#12 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Quote:
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.
__________________
In God we trust; all others bring data |
|
|
|
|
|
|
#13 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Quote:
__________________
In God we trust; all others bring data |
|
|
|
|
|
|
#14 | |
|
Bannato
Iscritto dal: Nov 2014
Messaggi: 292
|
Quote:
![]() A 'sto punto siamo, mi vengono a cercare negli altri thread. Se parli di questo, non ho la piu' pallida idea di che roba sia. La parentesi "Microsoft" della mita e' stata completamente chiusa qualche anno fa. |
|
|
|
|
|
|
#15 | |
|
Senior Member
Iscritto dal: Nov 2005
Città: Texas
Messaggi: 1722
|
Quote:
Chiedo scusa a tutti per l'off topic
__________________
In God we trust; all others bring data |
|
|
|
|
|
|
#16 | |
|
Senior Member
Iscritto dal: Nov 2005
Messaggi: 2095
|
Quote:
Codice:
typedef struct {
msg_type_t msg_type;
size_t msg_len;
} header_t;
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 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
__________________
Cosmos C# Open Source Managed Operating System Cosmos Thread Ufficiale Cosmos Official Site Vuoi collaborare allo sviluppo? Unisciti alla chat! Ultima modifica di fano : 10-12-2016 alle 15:05. |
|
|
|
|
|
| Strumenti | |
|
|
Tutti gli orari sono GMT +1. Ora sono le: 22:37.




















