Socket-Port freigeben

snoopysalive

Mitglied
Hallo!

Ich hab hier ein Problem mit Sockets für C++ unter Linux. Ich habe mal das Code-Beispiel von http://www.zotteljedi.de/doc/socket-tipps/code_server.html nachimplementiert.

Zwar läuft das Programm dann auch einwandfrei, nur habe ich ein Problem mit dem Programmabbruch, sobald ich auf den Server per Telnet zugegriffen habe. Breche ich den Server nämlich mit STRG-C ab, wenn Telnet entweder gerade darauf zugreift oder eine Telnet-Verbindung abgebaut wurde (also sobald Telnet zu irgendeinem Zeitpunkt während der Serverausführung darauf zugreift), lässt sich das Programm mindestens eine Minute lang nicht mehr starten, weil bind() nicht auf den von mir benutzten Port zugreifen kann, da dieser noch immer in Verwendung ist.

Das Problem tritt allerdings nicht auf, wenn man nicht mit Telnet auf den Server zugreift. Jetzt stellt sich also die Frage, wie man nach einem Programmabbruch (STRG-C) den benutzten Port umgehend wieder freigeben kann. Kann mir da jemand auf die Sprünge helfen?

Danke,
Matthias
 
Hallo,

ich bin mir zwar nicht sicher aber gehe Mal von folgendem Sachverhalt aus:

Code:
close(s);
return 0;

wird bei dir niemals ausgeführt. Mit for(;;) schaffst du eine endlosschleife. Das ist soweit auch korrekt, jedoch müsstest du eine Tastaturabfrage nach STRG-C mit einbauen, damit DU und nicht das Betriebssystem das Programm beendet. STRG-C in deinem Fall sorgt nämlich dafür, dass Linux das Programm beendet. Nun bleibt das Socket offen und das Betriebssystem wartet halt noch eine Schonzeit vor dem Hammerschlag. Versuche doch mal, das STRG-C abzufangen:

dazu folgendes:

Code:
#include <csignal>
#include <unistd>

void strgc_handler()
{
     close(c);
     close(s);
}

int main()
{
     signal(SIGINT, strgc_handler);

     // hier der Rest von deinem Programm //
}
 
Hallo!

Danke für die Antwort, aber ebendieses Signalabfangen und -behandeln habe ich schon implementiert. Meine Client-IDs befinden sich zwar in einem Vector, aber das sollte ja kein Problem sein.

Dennoch bekomme ich nach dem Abbruch nach wie vor die Fehlermeldung: "bind() failed: Address already in use", wobei der String "bind() failed" von mir stammt.

Ich bringe gerade den Server-Code auf Vordermann und stelle ihn euch hier dann zur Verfügung, damit etwas klarer wird, was ich da denn so fabriziere.

Gruß,
Matthias
 
So, hier mal meine Quelltexte:

main.cpp:
C++:
#include "Server.h"

Server * server;

void interrupt(int);

int main(int argc, const char ** argv) {
    signal(SIGINT, interrupt);

    server = new Server(19999);
    server->open();
    
    delete server;
    
    return 0;
}

void interrupt(int signum) {
    server->interrupt(signum);
    delete server;
    exit(0);
}

Server.h:
C++:
#ifndef SERVER_H
#define SERVER_H

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <vector>
#include <csignal>

using namespace std;
using std::string;

class Server {

public:
    /**
     * Dem Konstruktor muss ein Port übergeben werden, der für die
     * Server-Verbindung benutzt werden soll.
     */
    Server(int);
    
    virtual ~Server();
    
    /**
     * Behandelt den Abbruch des Servers
     */
    void interrupt(int);
    
    /**
     * Öffnet den Server-Socket
     */
    int open();
    
    /**
     * Fährt den Server herunter und kappt zuvor alle noch bestehenden
     * Client-Verbindungen
     */
    int quit();
    
private:
    
    /*
     * Speichert die eingehenden Client-Verbindungsnummern
     */
    vector<int> clients;
    
    /*
     * Speichert die Server-Verbindungsnummer
     */
    int server;
    
    /*
     * Speichert den Port, der im Konstruktor übergeben werden muss
     */
    int port;
    
    /*
     * Speichert die Länge der Adresse (?)
     */
    socklen_t addr_len;
    
    /*
     * Speichert die Adressdaten
     */ 
    struct sockaddr_in addr;
    
    /*
     * Speicher die Menge an Datenpaketen, die sich maximal in der
     * Warteschlange befinden dürfen.
     */
    static const short int MAX_QUEUE_LEN = 5;

};

#endif

Server.cpp:
C++:
#include "Server.h"

Server::Server(int port) {
    this->port = port;
}

Server::~Server() {
    clients.clear();
}

void Server::interrupt(int signum) {
    this->quit();
}

int Server::open() {
    this->server = socket(PF_INET, SOCK_STREAM, 0);
    if (this->server == -1) {
       perror("socket() failed");
       return 1;
    }
    
    this->addr.sin_addr.s_addr = INADDR_ANY;
    this->addr.sin_port        = htons(this->port);
    this->addr.sin_family      = PF_INET;
    if (bind(this->server, (struct sockaddr*)&this->addr, sizeof(this->addr)) == -1) {
       perror("bind() failed");
       return 2;
    }
    
    if (listen(this->server, MAX_QUEUE_LEN) == -1) {
        perror("listen() failed");
        return 3;
    }
    
    /*
     * Die accept-Endlosschleife
     */
    for (;;) {
        this->addr_len = sizeof(this->addr);
        int client = accept(this->server, (struct sockaddr*)&this->addr, &this->addr_len);
        if (client == -1) {
           perror("accept() failed");
           continue;
        }
        this->clients.push_back(client);
      
        cout << "Client from " << inet_ntoa(this->addr.sin_addr) << endl;
        
        /*
         * Hier den Client behandeln
         */
        
        close(client);
    }
    
    return this->quit();
}

int Server::quit() {
    cout << "Kappe alle bestehenden Verbindungen..." << flush;
    for (int i = 0; i < clients.size(); ++i)
        close(clients.at(i));
    cout << "fertig." << endl;
    close(this->server);
    return 0;
}

Ich möchte an dieser Stelle aber noch erwähnen, dass ich Neuling in der Socket-Programmierung unter C++ bin. Bisher habe ich das nur unter Java und Ruby gemacht.

Ach ja, der clients-vector wird noch recht stiefmütterlich behandelt, denn jede akzeptierte Client-Verbindug wird da ja reingeschmissen, was bei genügend langer Server-Laufzeit den Arbeitsspeicher natürlich komplett zumüllt. Das kommt dann, wenn mein Problem gelöst ist und ich den Server für mehrere Zugriffe zur gleichen Zeit fitt mache. Dann werden geschlossene Client-Verbindungen auch gleich wieder aus dem Vector gelöscht.

Nochmal kurz das Problem: Wenn ich diesen Code compiliere, starte und abbreche, lässt sich der Server auch gleich wieder starten, weil der Lauschport auch sofort wieder freigegeben wird. Greife ich während seiner Laufzeit allerdings per Telnet darauf zu, lässt sich der Server nach dem Abbruch nicht wieder sofort starten, weil die Serveradresse (?) ca. eine Minute lang nicht freigegeben wird: "bind() failed: Address already in use".

Ich probiere jetzt noch aus, was geschieht, wenn ich mit was anderem als Telnet darauf zugreife. Evtl. ist's ja ein Telnet-spezifisches Problem. Dennoch schadet es nicht, zu wissen, wie ich meinen Code verbessern kann, damit alle Socket-Ressourcen umgehend nach dem Programmabbruch über STRG-C wieder freigegeben werden.

Danke und Gruß,
Matthias
 
Ich glaube, ich habe jetzt herausgefunden, woran's lag. Über das Implementieren derselben Anwendung in Ada bin ich auf die Funktion setsockopt() gestoßen, die man direkt nach der socket()-Funktion aufruft und der man das Argument SO_REUSEADDR übergeben kann. Damit wird die Socketadresse anscheinend einfach wiederverwertet, falls sie noch nicht freigegeben wurde. Das ist zwar sicherlich nicht die sauberste Lösung, aber es ist eine. Wer trotzdem eine bessere Lösung hat, möge sie mir mitteilen, damit meine C++-Kenntnisse den Stand des Quick'n'Dirty verlassen.

Danke,
Matthias
 
Zurück