SDLNet: Peer-to-Peer

Schnelle objektorientierte, kompilierende Programmiersprache.
Antworten
nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

SDLNet: Peer-to-Peer

Beitrag von nufan » Fr Jun 19, 2009 7:06 pm

Das mit der Schleife statt dem Thread hat doch nicht so geklappt wie ich mir vorgestellt hatte ^^
Ich glaube aber nicht, dass ein zusätzlicher Thread helfen würde.

Also zum Programm:
Ich wollte einen kleinen Messenger schreiben. Nicht aufwendig nur Texte und vielleicht Dateien hin- und herschicken. Wenns geht mit nur 1 offenen Port.
Die Ausgabe erfolgte zuerst nur in der Konsole, dann wegen der Events Konsole mit SDL-Fenster (Ausgabe jedoch noch in Konsole). Das ganze soll im lokalen Netzwerk (LAN, WLAN) funktionieren, aber denke Internet wäre nicht schwer hinzuzufügen (wenn es überhaupt einer Umstellung bedarf). Nur möchte ich das gerne ohne echten Server machen, also Peer-to-Peer. Man gibt die IP ein und es wird versucht zu ihr zu verbinden. Der Informationsaustausch soll durch vorhergehende Signale (in einem enum) erfolgen. Also es wird z.B. das Signal "s_message" geschickt und der Gegenüber weiß, dass er eine Nachricht zu erwarten hat.
Wird im SDL-Fenster eine Taste gedrückt, kann man eine Nachricht eingeben. Die Geschwindigkeit der Überprüfungen ist noch nicht geregelt.

Nun zu den Problemen dabei:
Ich kann zu 127.0.0.1 verbinden, aber keine Daten empfangen. Verbinde ich über LAN zu einem anderen Rechner hat der andere einen SegFault, gefolgt von einem SegFautl auf dem ersten.
Byte-Order habe ich zurzeit noch vernachlässigt. Wie soll das überhaupt gehen? Die SDL-Funktionen funktionieren nur mit Uint16 und Uint32. Wenn ich ein char schicken will? Ein Uint16 nehmen und die Hälfte verschwenden? Außerdem läuft es zurzeit nur auf 2 Rechnern mit Intel-CPU.



Hier die wichtigsten Code-Auszüge:

main.cpp:

Code: Alles auswählen

#include "Messenger.h"


int main (int argc, char *argv[])
{

  Messenger m;
  char ip[16];
  SDL_Event event;

  cout << "IP: ";
  cin >> ip;

  if (m.Connect (ip) != 0)
  {

    cout << "Connection failed." << endl;

    return 1;

  }

  while (!m.quit)
  {

    m.Listen ();

    while (SDL_PollEvent (&event))
    {

      switch (event.type)
      {

        case SDL_KEYDOWN: m.SendMessage ();
                           break;

        case SDL_QUIT: exit (0);
                       break;

      }

    }

  }

  return 0;

}
Messenger.h:

Code: Alles auswählen

#ifndef MESSENGER_H
#define MESSENGER_H

#include <SDL/SDL.h>
#include <SDL/SDL_net.h>
#include <SDL/SDL_ttf.h>
#include <fstream>
#include "User.h"


typedef enum
{
  s_signal = 0,
  s_message = 1,
  s_typing = 2,
  s_file = 3,
  s_clientinfo = 4
} Signal;


const int maxclients = 20;


class Messenger
{

  private:
    string message, incoming_message, stringip;  // Nachricht die gesendet/ empfangen werden soll; Uint32-hex-IP als string
    SDLNet_SocketSet sockets;  // SocketSet für Clienten
    User clients[maxclients];  // Klasse für Clienten, enthält IP, Socket, Name
    SDL_Surface *screen;  // SDL-Fenster
    int numclients;  // Anzahl der aktuell verbunden Clienten
    ClientInfo local;  // IP, Socket, Name des lokalen Benutzers

  public:
    Messenger ();
    ~Messenger ();
    void InputMessage ();
    void SendMessage ();
    void ReceiveMessage (int index);
    void PrintMessage (int index);
    void ReceiveClientInfo (int index);
    void SendFile ();
    void ReceiveFile (int i);
    void SendLocalInfo ();
    void PrintClientInfo (ClientInfo info);
    int Connect (char *host, Uint16 port = 1337);
    int Listen (void *data = NULL);
    string& ConvertIP (Uint32 hexip);
    char SDLtoASCII (SDL_Event event);
    bool quit;

};

#endif
Ich denke es ist nicht nötig alle Methoden zu posten, z.B. konvertiert "ConvertIP" eine hexadezimale IP in einen String, dieser wird jedoch nur in einer Ausgabe verwendet, hat also keinen Einfluss auf dem Programmablauf.
Messenger.cpp:

Code: Alles auswählen

Messenger::Messenger ()   // Konstruktor
{

  if (SDL_WasInit (SDL_INIT_EVERYTHING) == 0)      // prüfen ob SDL bereits initialisiert ist
  {

    if (SDL_Init (SDL_INIT_EVERYTHING) != 0)       // SDL initialisieren
    {

      cout << "Could not initialize SDL: " << SDL_GetError () << endl;

      exit (0);

    }

    atexit (&SDL_Quit);

  }


  screen = SDL_SetVideoMode (320, 240, 16, SDL_ANYFORMAT);  // Fenster erstellen

  if (screen == NULL)            // prüfen ob Fenster erfolgreich erstellt wurde
  {

    cout << "Could not set video mode: " << SDL_GetError () << endl;

    exit (0);

  }


  if (SDLNet_Init () != 0)    // SDLNet initialisieren
  {

    cout << "Could not initialize SDL_net: " << SDLNet_GetError () << endl;

    exit (0);

  }

  atexit (&SDLNet_Quit);


  if (TTF_Init () != 0)     // TTF initialisieren
  {

    cout << "Could not initialize SDL_ttf: " << TTF_GetError () << endl;

    exit (0);

  }

  atexit (&TTF_Quit);


  if ((SDLNet_ResolveHost (&(local.ip), NULL, 1337)) != 0)   // "Server" starten
    cout << "resolve error" << endl;

  local.socket = SDLNet_TCP_Open (&(local.ip));     // TCP-Port öffnen

  if (local.socket == NULL)  // auf Fehler prüfen
    cout << "tcp open error" << endl;

  sockets = SDLNet_AllocSocketSet (maxclients);  // socketset erstellen

  if (sockets == NULL)     // auf Fehler prüfen
    cout << "socketset error" << endl;

  numclients = 0;      // Anzahl der Verbunden Clienten auf 0 setzen

  local.name.resize (20);  // maximale Länge des Namens

  cout << "Input name: ";
  getline (cin, local.name);  // Namen einlesen - cin nur vorübergehend

  quit = false;   // Variable zum Stoppen des Programms

}



Messenger::~Messenger ()   // Desktruktor
{

  SDLNet_FreeSocketSet (sockets);   // Speicher der Sockets freigeben

}



void Messenger::SendMessage ()   // Textnachricht senden
{

  Signal s = s_message;
  char buffer [message.size ()];

  message.clear ();

  InputMessage ();   // Eingabe der Nachricht

  if (message == "quit")
    quit = true;

    else if (message.size () > 0)
    {

      cout << "Message sent: \"" << message.c_str () << "\"" << endl;

      for (int i = 0; i < numclients; i++)   // an alle clienten schicken
      {

        SDLNet_TCP_Send (clients[i].info.socket, &s, sizeof (Signal));  // Signal schicken

        strcpy (buffer, message.c_str ());

        SDLNet_TCP_Send (clients[i].info.socket, buffer, message.size ());  // Nachricht schicken

        cout << "to: " << clients[i].info.name << endl;  // Name des Clienten ausgeben

      }

    }

}



void Messenger::ReceiveMessage (int index)   // Textnachricht empfangen
{

  int length;
  char buffer[32];

  incoming_message.clear ();

  do
  {

    length = SDLNet_TCP_Recv (clients[index].info.socket, (void *) buffer, 32);  // 32 Zeichen lesen

    if (length > 0)
      incoming_message += buffer;

  } while (length == 32 && length != -1);

  PrintMessage (index);  // Nachricht ausgeben

}



void Messenger::SendLocalInfo ()   // Informationen zum lokalen System schicken
{

  Signal s = s_clientinfo;
  int length;
  char buffer[20];

  if ((SDLNet_TCP_Send (clients[numclients].info.socket, &s, sizeof (Signal)) != sizeof (Signal)))  // Signal schicken
    cout << "Couldn't send signal" << endl;

  if ((SDLNet_TCP_Send (clients[numclients].info.socket, &(local.ip), sizeof (IPaddress))) != sizeof (IPaddress))  // IP schicken
    cout << "Couldn't send host IP" << endl;

  if ((SDLNet_TCP_Send (clients[numclients].info.socket, &(local.socket), sizeof (TCPsocket))) != sizeof (TCPsocket)) // TCPsocket schicken
    cout << "Couldn't send host socket" << endl;

  strcpy (buffer, local.name.c_str ());

  length =  SDLNet_TCP_Send (clients[numclients].info.socket, buffer, 20);  // Namen schicken

  if (length == -1)
    cout << "Couldn't send host name" << endl;

}



void Messenger::ReceiveClientInfo (int index)  // Informationen des entfernten Systems empfangen
{

  int length;
  char buffer[20];

  SDLNet_TCP_Recv (clients[index].info.socket, (void *) &clients[index].info.ip, sizeof (IPaddress));  // IP empfangen

  SDLNet_TCP_Recv (clients[index].info.socket, (void *) &clients[index].info.socket, sizeof (TCPsocket));  // TCPsocket empfangen

  length = SDLNet_TCP_Recv (clients[index].info.socket, (void *) buffer, 20);  // Namen empfangen --> CRASH

  clients[index].info.name = buffer;

}



int Messenger::Connect (char *host, Uint16 port)   // zu einer IP connecten
{

  if (SDLNet_ResolveHost (&(clients[numclients].info.ip), host, port) != 0)  // Hostnamen auflösen
  {

    cout << SDLNet_GetError () << endl;

    return 1;

  }

  clients[numclients].info.socket = SDLNet_TCP_Open (&(clients[numclients].info.ip)); // Port öffnen

  if (clients[numclients].info.socket == NULL) // auf Fehler prüfen
  {

    cout << SDLNet_GetError () << endl;

    return 1;

  }

    else
      cout << "Now connected to " << host << endl;

  SDLNet_TCP_AddSocket (sockets, clients[numclients].info.socket);  // Socket zum SocketSet hinzufügen

  SendLocalInfo ();   // Informationen des lokalen Rechners schicken

  numclients++;

  return 0;

}



int Messenger::Listen (void *data)   // auf Signale warten, data ist per default NULL
{

  Signal buffer;
  int ready;
  IPaddress *remoteip;

  if ((clients[numclients].info.socket = SDLNet_TCP_Accept (local.socket)) != NULL)  // auf Anfragen prüfen
  {

    if ((remoteip = SDLNet_TCP_GetPeerAddress (clients[numclients].info.socket)))  // Adresse des Sockets herausfinden
	  	cout << "New incoming connection: " << ConvertIP (remoteip -> host) << endl;  // IP ausgeben

    clients[numclients].info.ip = *remoteip;   // IP speichern

    SDLNet_TCP_AddSocket (sockets, clients[numclients].info.socket);  // Socket zum SocketSet hinzufügen

    numclients++;

  }

  ready = SDLNet_CheckSockets (sockets, 125);  // Anzahl der aktiven Sockets auslesen

  for (int i = 0; i < numclients; i++)   // alle Sockets prüfen
  {

    if (SDLNet_SocketReady (clients[i].info.socket))  // prüfen ob Socket aktiv
    {

      SDLNet_TCP_Recv (clients[i].info.socket, &buffer, sizeof (Signal));  // Signal empfangen

      switch (buffer)
      {

        case s_signal:
                       break;

        case s_message: ReceiveMessage (i);
                        break;

        case s_typing:
                       break;

        case s_file: ReceiveFile (i);
                     break;

        case s_clientinfo: ReceiveClientInfo (i);
                           break;

      }

    }

      else
      {

        // remove socket

      }

  }

  return 0;

}
Da sind noch ziemlich viele Debug-Ausgaben dabei. Verbinde ich zu einem anderen Rechner, crasht das Programm in der "ReceiveLocalInfo"-Methode beim Empfangen des Namens.
Die cin's sind nur der Einfachheit halber, später werden sie durch SDL-Events ersetzt.

Mache ich einen rein programmiertechnischen Fehler oder ist es ein Fehler im Konzept?

Wäre nett wenn sich das mal jemand ansehen würde :)

PS: Wenn das Programm eher Konzept-Fehler hat, bitte verschieben.

nufan
Wiki-Moderator
Beiträge: 2558
Registriert: Sa Jul 05, 2008 3:21 pm

Re: SDLNet: Peer-to-Peer

Beitrag von nufan » Fr Jun 19, 2009 11:27 pm

Problem gelöst :)
Der Fehler: "TCPsocket" ist bereits ein Pointer, deshalb darf kein Adressoperator verwendet werden. Dadurch geriet die ganze Speicherberechnung durcheinander und beim Namen war es dann endgültig vorbei.
Was ich aber nicht ganz verstehe: Warum funktioniert dann sizeof (TCPsocket) richtig? Das ist bei mir 4, weils ja ein Pointer ist. Die Größe von _TCPsocket kann ich nicht ausgeben, da der Compiler einen unvollständigen Datentyp meldet ("Ungültige Anwendung von »sizeof« auf unvollständigen Typen »_TCPsocket«"). Und wenn die Struktur durch Zufall auch 4 Byte groß ist, funktioniert das doch auf 64-Bit-Systemen nicht?! *verwirrt*
Am besten lass ich das mit dem Übertragen der Sockets, die sind sowieso überflüssig für den Empfänger, da er seinen eigenen hat.

Antworten