Erstellen von Peer2Peer Netzwerk



  • Hallo zusammen,

    ich arbeite gerade an Klassen mit denen ich ein Peer2Peer Netzwerk aufbaun will (~2-6 spieler sind genug). Das heißt, dass jeder Spieler in diesem Netzwerk eine Verbindung zu jedem anderen Spieler haben soll. Ich habe gerade weniger schwierigkeiten mit winsocks o.ä., für die low-level sachen benutze ich SFML.
    Es geht mir eher um das konzeptionelle, wie ich es hinkriege ein Peer2Peer Netzwerk aufzubaun, deswegen denke ich forumskategorie so passend ist.

    Ich habe mir überlegt, dass ich trotz peer2peer ansatz einen server habe, der das aufbauen des netzwerks steuert.
    Will ein client mitspielen, so connected er erstmal zum server und erhält von diesem ein liste aller spieler die bereits connected sind (id, nickname und ip-adresse).
    Zur ID: Der server weist jedem spieler eine eindeutige ID zu (habe geplant einfach hochzuzählen).
    Dann connected der client zu allen clients aus der erhaltenen liste und schickt dem server eine bestätigung, sobald er erfolgreich mit allen anderen verbunden ist.

    Ich habe im Moment beim server eine pendingSockets liste, die die verbindungen zu clients speichert, die sich verbunden haben, aber noch nicht mit dem ganzen peer2peer netzwerk verbunden sind.
    Erst wenn die bestätigung kommt, dass er mit allen verbunden ist zählt er als dem spiel beigetreten.

    Verbindet sich also ein client zum server, wird diese Methode aufgerufen:

    void TcpServer::OnNewClientConnected(const std::shared_ptr<sf::TcpSocket>& socket)
    {
    	sf::Packet playerListPacket;
    	playerListPacket << peers_.size();
    	for_each(begin(peers_), end(peers_), [&](const PeerData& peer)
    	{
    		playerListPacket << peer.info;
    	});
    	sf::Socket::Status status = socket->Send(playerListPacket);
    	switch(status)
    	{
    	case sf::Socket::Disconnected:
    		//drop client (dont add him to the pending list)
    		break;
    	case sf::Socket::Done:
    		pendingSockets_.push_back(socket);
    		break;
    	case sf::Socket::NotReady:
    	case sf::Socket::Error:
    		throw std::runtime_error("Failed to send to client");
    	default:
    		throw std::runtime_error("Unhandled status");
    	}
    }
    

    Regelmäßig wird dann das hier vom server aufgerufen:

    void TcpServer::DispatchPendingSockets()
    {
    	//check if pending sockets received something
    	auto iter = begin(pendingSockets_);
    	while(iter != end(pendingSockets_))
    	{
    		sf::Packet toReceive;
    		sf::Socket::Status status = (*iter)->Receive(toReceive);
    		switch(status)
    		{
    		case sf::Socket::Disconnected:	
    			//löschen während iterieren... ist das richtig so?
    			pendingSockets_.erase(iter++);
    			continue;
    		case sf::Socket::Done:
    		{
    			std::string msg;
    			toReceive >> msg;
    			if(msg == AckString)
    			{
    				auto socket = *iter;
    				pendingSockets_.erase(iter++);
    				OnPlayerJoined(socket); //erst jetzt ist der spieler wirklich dem spiel beigetreten
    				continue;
    			}
    			break;
    		}
    		case sf::Socket::NotReady:
    			break;
    		case sf::Socket::Error:
    			throw std::runtime_error("Failed to receive data");
    		default:
    			throw std::runtime_error("Unhandled status");
    		}
    		++iter;
    	};
    }
    

    Das problem tritt bei folgendem Szenario auf;
    Client A connected und wird in die "pending" list aufgenommen und bekommt eine Spielerliste zugeschickt.
    Client B connected und wird in die "pending" list aufgenommen und bekommt eine Spielerliste zugeschickt. In dieser Liste ist Client A aber nocht nicht enthalten, weil dieser auch noch pending ist und noch nicht wirklich im spiel ist und jederzeit rausfliegen könnte. Würde ich Client A direkt mitschicken bei der liste, dann würde es evtl. ein Problem geben, falls Client A dann doch rausfliegt.

    Außerdem denke ich das mein gesamter Lösungsansatz irgendwie nicht so besonders gut ist.

    Hat jemand Verbesserungsvorschläge?



  • Also ich verstehe dein Problem im Moment nicht. Du musst doch andere Clients sowieso über neue Spieler informieren, kannst du da nicht einfach dem neuen Client alle aktuellen senden und andere neue später?



  • Q schrieb:

    Regelmäßig wird dann das hier vom server aufgerufen:

    du solltest das so aendern, das beim eintreffen von daten am server, an die clients weiterverteilt wird. pollen ist nicht so toll. das haengt naetuerlich davon ab, wie du die netzwerkkommunikation geloest hast.
    weiters wuerde ich eine weitere clientliste einfuehren, wo die jenigen drinnen sein, die noch mit daten versorgt werden muessen.

    es koennte nun folgend ablaufen:
    (o) Client verbindet sich zum server (wird in die pendingsliste eingetragen)
    (o) es wird ueberprueft ob der client zugelassen wird
    (o) wenn nicht, verbindung trennen. aus
    (o) client kommt in die liste der sich verbindenen clients (er kann noch nichts senden)
    (o) jedem der schon verbundenen clients den neuen mitteilen
    (o) dem neuen client die aktuelle clientliste uebermitteln und was sonst noch noetig ist
    (o) den neuen client in die liste der verbundenen clients eintragen.ab nun kann der neue client auch daten senden

    Meep Meep



  • Was ich vielleicht noch nicht so richtig erwähnt habe:
    Ich hatte das so gedacht, dass jeder Spieler der neu betritt selbst eine Verbindung zu allen anderen Spielern aufbauen muss (die ips kriegt er aus der playerliste).

    Ich glaub ich muss das ganze system nochmal ein wenig überarbeiten...
    Ich überlege, ob es sinnvoll ist, dass der server abspeichert, wer mit wem verbunden ist und dann bei bedarf weitere verbindungen bei den clients anfordern kann oder soetwas.

    Und zum Polling: So funktionieren asynchrone sockets bei sfml nunmal^^ Ich hatte auch schon überlegt auf boost::asio umzusteigen, aber fürs erste lass ich das mal so, weils recht einfach zu verwenden ist und ich nicht denke das das polling besonders schlimme auswirkungen haben wird.



  • Q schrieb:

    Was ich vielleicht noch nicht so richtig erwähnt habe:
    Ich hatte das so gedacht, dass jeder Spieler der neu betritt selbst eine Verbindung zu allen anderen Spielern aufbauen muss (die ips kriegt er aus der playerliste).

    Dein ursprüngliches Problem wird evtl. einfacher erschlagen, wenn Du das umgekehrt machst:
    Der Server benachrichtigt alle verbundenen Clients, wenn ein neuer kommt, und diese bauen eine Verbindung zu dem Neuen auf, schicken eine Bestätigung an den Server und fertig. So würde Client A sich am Server anmelden, alle bereits vorhandenen Clients bekommen eine Nachricht und connecten zu Client A - währenddessen kommt Client B, alle anderen Clients, inklusive Client A bekommen eine Nachricht und connecten zu Client B, usw ....



  • Belli schrieb:

    Q schrieb:

    Was ich vielleicht noch nicht so richtig erwähnt habe:
    Ich hatte das so gedacht, dass jeder Spieler der neu betritt selbst eine Verbindung zu allen anderen Spielern aufbauen muss (die ips kriegt er aus der playerliste).

    Dein ursprüngliches Problem wird evtl. einfacher erschlagen, wenn Du das umgekehrt machst:
    Der Server benachrichtigt alle verbundenen Clients, wenn ein neuer kommt, und diese bauen eine Verbindung zu dem Neuen auf, schicken eine Bestätigung an den Server und fertig. So würde Client A sich am Server anmelden, alle bereits vorhandenen Clients bekommen eine Nachricht und connecten zu Client A - währenddessen kommt Client B, alle anderen Clients, inklusive Client A bekommen eine Nachricht und connecten zu Client B, usw ....

    ehrlich geasgt, seh ich hierbei nicht, warum die probleme weg sein sollten ,wenn er es umgekehrt macht. ich wuerde es auch eher so machen, das der neue client sich mit den schon vorhandenen verbindet

    Q schrieb:

    Ich überlege, ob es sinnvoll ist, dass der server abspeichert, wer mit wem verbunden ist und dann bei bedarf weitere verbindungen bei den clients anfordern kann oder soetwas.

    im normalfall ist doch jeder mit jedem verbunden. also brauchst du dazu eigendlich nichts speichern.
    was auf jeden fall zu bedenken ist, ist das eine verbindung auch mal abbrechen kann. das ist natuerlich eine andere situation als wenn ein client beendet wird.
    deshalb finde ich es auch geschickter, wenn der neue client sich mit den anderen verbindet. bei einem ausfall eines clients wissen die anderen natuerlich nicht, ob das beabsichtigt war oder was los ist.
    der client muss sich daher zu erst am server abmelden und dann die verbindungen zu den anderen kappen. wenn es nur ein temporaerer disconnect ist, kann er sich dann ja wieder mit den anderen verbinden. schliesslich wissen die nicht, wann er wieder online ist um sich mit ihm zu verbinden

    Meep Meep



  • Meep Meep schrieb:

    ehrlich geasgt, seh ich hierbei nicht, warum die probleme weg sein sollten ,wenn er es umgekehrt macht.

    Weil:

    Q schrieb:

    Das problem tritt bei folgendem Szenario auf;
    Client A connected und wird in die "pending" list aufgenommen und bekommt eine Spielerliste zugeschickt.
    Client B connected und wird in die "pending" list aufgenommen und bekommt eine Spielerliste zugeschickt. In dieser Liste ist Client A aber nocht nicht enthalten


Anmelden zum Antworten