select() Server und 13 Clients gleichzeitig - Scheiß Übertragungsraten!



  • Hi,

    ich habe für einen Netzwerktest ein Tool geschrieben, was physikalisch verfügbare Server in einem bestimmten Netzwerkbereich sucht und dann entsprechend mit Boost Multithread Clients erstellt, um non stop Daten zu verschicken. (Peer2Peer)

    Heute Nacht habe ich den Test dann 14 Stunden laufen lassen und die Werte sind sehr ernüchternd. Ich hab im Schnitt eine Gesamt-Übertragungsrate von rund 10KByte/s (!!), statt um die 100MByte/s. Mit 2-3 Servern klappt das wunderbar, aber irgendwie ist das nicht skalierbar. (Im Test waren 13 IBM Bladeserver)

    Kann es daran liegen, weil zuviele Clients parallel auf die Netzwerkschnittstelle zugreifen? Oder kann es am select() Server liegen, der damit nicht fertig wird?

    Ich würde mich über Antworten freuen. Das ist nämlich alles sehr frustrierend!! 😞

    Gruß, Pascal



  • Ich denke mal, dass der Flaschenhals irgend wo in deinem Test steckt :). Aber select ist sicher nicht die idealste Funktion. Wenn du mehr Geschwindigkeit haben willst, dann nimm lieber epoll.



  • Mein Test ist ansich nix komplexes. Ich hab nur 5 verschiedene Datenpackete die in einem Array stecken. Die werden dann jeweils abgerufen und gesendet. Ganz normal. Ich glaube nicht dass das das Problem ist.

    Würde es vllt helfen, wenn ich den select() Server mit einem fork ersetzen würde?



  • Also was mir auch aufgefallen ist:

    Dieser Fehler ist sehr oft aufgetreten!

    "Send failed: Resource temporarily unavailable"

    Das habe ich mit 3 Servern nur ab und zu gehabt, vllt 2 mal die Minute. Mit 13 Servern hab ich das wesentlich öfter.

    Liegt es also doch am select()? Ist der Server nicht für sowas geeignet? Solte ich auf epoll umsteigen?



  • es ist vollkommen unmöglich, herauszufinden, was dein problem ist, solange man keinen code gesehen hat oder zumindest erfährt, bei welche funktion diese fehlermeldung kommt.
    bitte poste aber nicht den kompletten code sondern nur den für dein problem ausschlaggebenden.

    ...was physikalisch verfügbare Server in einem bestimmten Netzwerkbereich sucht...

    machst du das per broadcast?

    du hast also 13 programme laufen - auf jedem server eines. das heißt, dass jedes programm von zwölf anderen programmen daten empfängt und zu ihnen schickt? es ist also klar, dass der durchsatz pro programm geringer sein wird. du meinst aber, dass die summe aller übertragungen 10 kib/s ausmacht? vielleicht vertust du dich bei der berechnung des gesamtdurchsatzes.

    nur zur sicherheit: du hast ein geswitchtes gigabit netzwerk?

    ich denke im allgemeinen, dass select das problem mit zwölf netzwerkverbindungen nicht sein kann.



  • 1.) Die Fehlermeldung kommt beim Senden (bei Socket->send())
    2.) Die Gesamtrate liegt bei ca. 114Kbyte/s
    3.) Das Netzwerk durchsuche ich, indem ich versuche zu jeder IP zu connecten. Wenn ich connecten kann, sende ich eine Info, um herauszufinden ob die Gegenstelle an dem Test teilnimmt.

    Also es sieht so aus. Pro physikalischen Server wird folgendes erstellt (13 physikalische Server sind vorhanden):

    1 Server für Authentifizierung (select())
    1 Server für eingehende Verbindungen (select())
    12 Clients zum Senden von Daten

    Jeder Client stellt eine Verbindung zu einem anderen Server her und schickt Daten zu dem.

    Somit sieht es dann so aus, dass jeder mit dem anderen Kommuniziert, sodass die Netzwerklast sehr hoch ausfällt. Die Switche sind Cisco Switche, daran kann es nicht liegen. Es ist auch so, dass alle Bladeserver im selben Bladecenter sitzen, also nicht irgendwo im Netzwerk verteilt.

    Ich poste jetzt mal den Code für den Client und für den Server für eingehende Verbindungen:

    void Server::serverThread()
    {
            logger->debug(3, "ServerThread is runnig!");
            struct timeval tv;
            tv.tv_sec = 3;
            string networkip, s;
                                                            // for stats
            bool marker[FD_SETSIZE];
            unsigned int rc;
            unsigned long long ttlbytes[FD_SETSIZE], mytime[FD_SETSIZE];
            int i, ready, sock_max, max=-1, tmp;
            int client_sock[FD_SETSIZE];
            fd_set total_sock, read_sock;
            size_t iter = 0;
    
      while(logger->continueRun())
      {
            serverSocket1 = new Socket();
            serverSocket2 = new Socket();
            serverSocket3 = new Socket();
            serverSocket1->create();
            serverSocket1->bind(serverport);
            serverSocket1->listen();
            sock_max = serverSocket1->get_m_sock();
    
            logger->debug(3, "Waiting for connections...");
            for(i=0; i < FD_SETSIZE; i++)
            {
              client_sock[i] = -1;
              mytime[i] = 0;
              ttlbytes[i] = 0;
              marker[i] = true;
            }
    
            FD_ZERO(&total_sock);
            FD_SET(serverSocket1->get_m_sock(), &total_sock);
            while(!get_exitflag())
            {
              read_sock = total_sock;
              ready = select(sock_max+1, &read_sock, NULL, NULL, &tv);
            if(FD_ISSET(serverSocket1->get_m_sock(), &read_sock))
            {
              if(!serverSocket1->accept(*serverSocket2))
              {
                continue;
              }
    
              for(i=0; i < FD_SETSIZE; i++)
              {
                if(client_sock[i] < 0)
                {
                  client_sock[i] = serverSocket2->get_m_sock();
                  break;
                }
              }
              if(i == FD_SETSIZE)
              {
                logger->debug(1, "To much Clients - No more free Sockets");
                break;
              }
              FD_SET(serverSocket2->get_m_sock(), &total_sock);
              if(serverSocket2->get_m_sock() > sock_max)
              {
                sock_max = serverSocket2->get_m_sock();
              }
              if(i > max)
              {
                max = i;
              }
              if(--ready <= 0)
              {
                continue;
              }
            }
    
            for(i=0; i <= max; i++)
            {
              serverSocket3->set_m_sock(client_sock[i]);
              if((serverSocket3->get_m_sock()) < 0)
              {
                continue;
              }
              if(FD_ISSET(serverSocket3->get_m_sock(), &read_sock))
              {
                rc = serverSocket3->recv(s);
                ttlbytes[i]+=rc;
                if(marker[i])
                {
                  marker[i] = false;
                  mytime[i] = stopwatch();
                }
    
                if(rc == 0)
                {
                  mytime[i] = stopwatch() - mytime[i];
                  networkip = serverSocket1->networkip;
                  serverSocket3->close();
                  for(size_t a = 0; a < statlogger->stat.size(); ++a)
                  {
                    if(statlogger->stat[a].get_ip() == networkip)
                    {
                      iter = a;
                      break;
                    }
                  }
    
                  tmp = ttlbytes[i]*1000000/mytime[i];                      // average rate
                  statlogger->stat[iter].set_avrgr_l(tmp);
                  if(tmp > statlogger->stat[iter].get_maxr())
                  {
                    statlogger->stat[iter].set_maxr(tmp);
                  }
                  if((tmp < statlogger->stat[iter].get_minr()) || statlogger->stat[iter].get_minr() == 0)
                  {
                    statlogger->stat[iter].set_minr(tmp);
                  }
    
                  logger->debug(4, "Connection @ " + networkip + " closed");
                  logger->debug(3, "Data received: " + data(ttlbytes[i])+ " " + conv.unit);
                  logger->debug(3, "Transferrate: " + data(tmp) + " " + conv.unit + "/s");
    
                  client_sock[i] = -1;
                  marker[i] = true;
                  statlogger->stat[iter].set_data(ttlbytes[i], mytime[i]);
                  ttlbytes[i] = 0;
                }
                if(--ready <= 0 || get_exitflag())
                {
                  break;
                }
              }
            }
         }
    

    So, jetzt der Client:

    (Testdata ist das Array wo die Datenpackete drinsitzen. Die jenigen Packete werden rein zufällig ausgesucht und dann an send() übergeben)

    void Client::clientThread()
    {
            logger->debug(3, "ClientThread is runnig!");
            bool error;
            int data, iterations;
            while(1)
            {
              clientSocket = new Socket();
              if(!clientSocket->create())
              {
                logger->debug(3, "Failed creating Socket for the client!");
                break;
              }
              srand(time(NULL));
              iterations = rand() % (100-1)+1;
              data = rand() % (4-0+1);
              if(!clientSocket->connect(clientaddr, clientport))
              {
                logger->debug(3, "Failed connecting to a client!");
                break;
              }
              logger->debug(3, "Transmitting packets...");
              error = clientSocket->send(*(testdata+data), iterations);
              clientSocket->close();
              logger->debug(3, "Finished sending packets");
              if(get_exitflag())
              {
                break;
              }
              if(clientSocket != NULL)
              {
                delete clientSocket;
                clientSocket = NULL;
              }
              sleep(1);
            }
    }
    

    So sieht die Senderoutine aus in der Klasse Socket:

    bool Socket::send(const string s, const unsigned int iterations)
    {
      errno = 0;
      int send_return = 0;
      int size = s.size();
      unsigned int counter = 0;
      char *buf = new char[size];
      if(buf == NULL)
      {
        perror("Error allocating memory for send");
      }
      strncpy(buf, s.c_str(), size+1);
      do                                                                            
      {
        errno = 0;
        send_return = ::send(m_sock, buf, size, 0);
        ++counter;
      }while(counter < iterations && send_return != -1);
    
      delete[] buf;
      if(send_return == -1)
      {
        perror("Send failed");
        return false;
      }
      else
      {
        return true;
      }
    }
    

    Lasst euch von diesem "logger" nicht verwirren. Der ist dafür da, damit bei bestimmten debug Optionen die Fehler ausgegeben werden.

    Ich danke euch!



  • du verwendest, wie es in verbindung mit select üblich ist, non-blocking sockets. gibt send eagain zurück, so ist der puffer im kernel voll. das passiert dann, wenn deine anwendung mehr daten an den kernel zum senden übergibt, als er in der gleichen zeit tatsächlich losschicken kann.

    wenn du linux verwendest, schau dir bitte epoll an. es funktioniert um vieles einfacher als select. ich wage sogar zu behautpen, dass es besser ist, auf select selbst in plattformunabhängigen programmen zu verzichten und für jede erdenkliche plattform die passende api zu verwenden. freebsd, solaris und linux haben je eine einfacher und/oder performantere alternative zu select.

    unabhängig der api sollte man so vorgehen:
    du hast pro file descriptor eine verwaltungsstruktur, in der drin steht, was zu senden ist und wie weit schon gesendet wurde. mit allen apis kann man nicht nur warten, bis verbindung aufgebaut werden oder daten zum lesen vorhanden sind, sondern auch, ob man weiter senden kann (dh, der kernel puffer hat weider etwas platz).
    sollte die api dir also einen file desctiptor zurückgeben, musst du die struktur finden und nachsehen, was zu tun ist. sollten daten gesendet werden, tust du das solange, bis send dir EAGAIN zurückgibt. tut es das, gibst du diesen client fd wieder in die select-artige-api zurück und wartest, bis du wieder senden kannst.

    halbwegs klar? möglicherweise geht es dann schneller, ich glaube aber nicht, dass das der grund für die schwache leistung ist.



  • War quark.



  • @Lord

    wie "war quark"? stimmt das jetzt was mir "besserwisser" da geschrieben hat oder nicht?

    ich habe auch herausgefunden, dass es auch am senden liegt. ich glaube die senderoutine braucht bei mir zu lange. vom kopieren der daten in die senderoutine, bis zum endgüligen versenden vergeht zuviel zeit. ich werde das mal nacharbeiten.

    ich wüsste trotzdem gerne von euch, ob es sinnvoll ist von select() auf epoll() umzusteigen. ich habe leider noch keine codebeispiele für einen epoll server gefunden.

    wisst ihr zufällig eine seite?

    danke! 😉


Anmelden zum Antworten