UDP Receiver...verschluckt Nachtrichten



  • Hallo liebe Gemeinde. Ich komme nicht mehr weiter und hoffe, dass Ihr mir einen Tip geben könnt!

    Ich habe einen UDP Server, der sowohl Broadcasts, als auch direkt an ihn gerichtete UDP datagrams empfangen soll. Das ansich ist kein Problem.

    Leider verschuckt er ab und zu ankommende datagrams (meist JSON). Das passiert, wie soll es auch anders sein, wenn diese kurz aufeinander eintreffen und ich noch bei der Verarbeitung des Letzten bin. Hier mein Code:

    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <stdlib.h>
    #include <errno.h>
    
    int main( int argc, char *argv[])
    {
        int udp_sock;
        struct sockaddr_in server_address, client_address;
        char recvString[1024];
        unsigned int clientLength;
        int checkCall, recvStringLen;
        char ip[16];
    
        /*Create socket */
        udp_sock=socket(AF_INET, SOCK_DGRAM, 0);
        if(udp_sock == -1)
            perror("Error: socket failed");
    
        bzero((char*) &server_address, sizeof(server_address));
    
        /* server's sockaddr_in*/
        server_address.sin_family=AF_INET;
        server_address.sin_addr.s_addr=htonl(INADDR_ANY);
        server_address.sin_port=htons(7090);
    
        /* bind server socket and listen for incoming clients */
        checkCall = bind(udp_sock, (struct sockaddr *) &server_address, sizeof(struct sockaddr));
        if(checkCall == -1)
            perror("Error: bind call failed");
    
        while(1)
        {
            printf("SERVER: waiting for data from client\n");
    
            clientLength = sizeof(client_address);
            recvStringLen = recvfrom(udp_sock, recvString, sizeof(recvString), 0, (struct sockaddr*) &client_address, &clientLength);
            if(recvStringLen == -1)
                perror("Error: recvfrom call failed");
    
            recvString[recvStringLen] = '\0';
    
            /* springe zu einer anderen funktion, die die den JSON String verarbeitet. Dieser zieht die Werte heraus und packt sie in eine global gültige Struktur */ 
            /* ausgabe mit printf nur als demo hier */
            printf("%s\n", recvString);
    
         }
        close(udp_sock);
    
    }
    

    Wie man schnell sieht, wartet er auf ein ankommendes datagram, schließt den recvBuffer ab und übergibt es einer anderen funktion, die aus dem empfangenden JSON String die Variablen holt. Das dauert aber zu lange und so verschluckt sich der Empfänger und die Anwendung stürzt einfach ab. Kommen die datagrams sachte nacheinander, dann funktioniert es auch.

    Was kann ich tun? Ein fork wäre doch unglücklich, da ich die inhalte der variablen des gerade geparsten JSON strings, nach beendigung eines forks, verlieren würde.

    Hat jemand eine Idee?

    VG
    Nico



  • Liegt vermutlich an UDP - da ist nix garantiert - Mit TCP ist das anders - da sollte sowas nicht passieren.
    Hab ich mal in beejs Network-programming gelesen.



  • Abstürzen tut es ganz sicher nicht wenn es vernünftig geschrieben ist. Zeig mal dein ganzer Code zwischen Zeile 37-52.



  • skynet74 schrieb:

    Ich habe einen UDP Server, der sowohl Broadcasts, als auch direkt an ihn gerichtete UDP datagrams empfangen soll. Das ansich ist kein Problem.

    Doch, genau da liegt dein Problem.
    Der Server kann die per UDP an ihn gerichteten Nachrichten vollständig und in der richtigen Reihenfolge empfangen, das muss aber nicht so sein.
    Das ist eine grundsätzliche Eigenschaft solcher verbindungslosen Protokolle, wenn du die ignorierst, bist du selbst schuld.



  • nachdem ich am code nichts gefunden habe was aussieht als würde es die Probleme hervorrufen, habe ich dein Programm mit einem haufen gleichzeitig gestarteter clients getestet und es ist nichts schlimmes passiert.
    Client:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    int main(int argc, char **argv)
    {
    	if(argc < 2)
    		return EXIT_FAILURE;
    	struct sockaddr_in server_adresse;
    	memset(&server_adresse, 0, sizeof(server_adresse));
    	server_adresse.sin_family = AF_INET;
    	server_adresse.sin_addr.s_addr = INADDR_ANY;
    	server_adresse.sin_port = htons(7090);
    	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    	if(sockfd == -1) {
    		perror("Fehler bei socket()");
    		return EXIT_FAILURE;
    	}
    	ssize_t sendto_status = sendto(sockfd, argv[1] , strlen(argv[1])+1 , 0,
    	                        (struct sockaddr*) &server_adresse, sizeof(server_adresse));
    	close(sockfd);
    	if(sendto_status == -1) {
    		perror("Fehler beim senden");
    		return EXIT_FAILURE;
    	}
    	puts("Nachricht wurde gesendet");
    	return EXIT_SUCCESS;
    }
    

    der fehler hat wahrscheinlich nichts damit zu tun, wie lange du zum verarbeiten brauchst. Das Betriebsystem hat genug puffer für ein paar packete und wenn nicht, dann werden die packete einfach verworfen.
    Vll gibt es einen fehler beim verarbeiten von zu langen Packeten, den die werden bei deinem code einfach gekürzt.
    Was ich nicht verstehe ist, wie dein code gleichzeitig an einer Broadcast Adresse lauscht.



  • Hallo Gary,

    mit meinem Code bekomme ich alle an 192.168.10.255 oder an mich selbst (192.168.10.11) gerichteten Pakete. Ist da vllt. der Fehler?

    Die Pakete sind max. 256 Zeichen lang, daher besteht kein Problem bei den [1024]. Aber ich werde es dennoch abändern, falls von einem anderen Client mal auf .255 was > 1024 Byte gesendet wird. Da ist die Auswertung nach der Sender-IP ja schon zu spät 🙂

    Im normalen Verarbeitungscode wird das JSON geparst und Du hast natürlich recht, das dauert etwas.

    Ist es wirklich so, dass das Betriebssystem alle eingehenden UDP datagrams puffert und mit den Inhalt dann nach jedem recvfrom gibt? Sicher?

    Ich habe nämlich ein wenig umgeschrieben und prthreads eingesetzt. Ein Stresstest war noch nicht möglich.

    while(1)
        {
            recvString[0] = '\0';
            clientLength = sizeof(udp_client_address);
            recvStringLen = recvfrom(udp_sock, recvString, sizeof(recvString), 0, (struct sockaddr*) &udp_client_address, &clientLength);
            if(recvStringLen == -1) {
                log_message(LOG_FILE,"UDP server: recvfrom call failed");
            }
            else {
                /* complete the string */
                recvString[recvStringLen] = '\0';
                /* integrated into a structure */
                sprintf(thread_udp_data.rcvString, "%s",recvString);
                sprintf(thread_udp_data.rcvIP,"%s",inet_ntoa(udp_client_address.sin_addr));
                thread_udp_data.rcvLen = recvStringLen;
                /* service request in threads */
                t++;
                if (t == 6) t = 1;
                thread_udp_data.tid = t;
                pthread_create(&udpthreads[t], NULL, &UdpServiceRequest, &thread_udp_data);
                pthread_detach(udpthreads[t]);
            }
        }
    


  • jetzt verschluckt er nichts mehr. pthreads war wirklich die lösung. forks wäre vllt. einfacher gewesen, aber dann ist die rückführung der fork based variablen ein problem. habe jetzt meinen client mit ellen langen JSONS beschickt, klappt.



  • UDP ist das falsche Protokoll, wenn alle Nachrichten ankommen müssen. Das es jetzt so funktioniert ist keine Garantie. Stell auf TCP/IP um oder lebe mit den Konsequenzen.



  • Wie der Vorposter schon sagte, hast du bei UDP keine Garantie, dass die Daten ankommen. D.h. du solltest dich bewusst für UDP entschieden haben. Nötigenfalls musst du eigene Kontrollmechanismen einbauen, und über eine zweite Leitung einzelne verlorene Pakete beim Sender nachfordern oder so...
    Ansonsten lieber gleich TCP 😉

    UDP macht nur dann Sinn, wenn du die gleichen Daten an viele Clients schnell liefern musst...



  • @skynet74
    Noch besser wäre die empfangenen Pakete einfach in eine Producer-Consumer Queue zu stellen.
    Empfangs-Thread steckt rein, ein oder mehrere Verarbeite-Threads holen raus und verarbeiten.

    Das sollte erstens besser skalieren, und zweitens verhinderst du dadurch dass man deinen Server einfach DOSen kann. Bzw. "definierst" das Verhalten im Fall dass mehr Anfragen/Zeiteinheit ankommen als der Server verarbeiten kann.

    Die Producer-Consumer Queue muss eine max. länge haben und beim Insert blockieren falls die Länge überschritten wird. Oder alternativ kannst du statt zu blockieren das nicht-mehr-reinpassende Fragment auch einfach verwerfen -- das hätte den Vorteil dass du mitbekommst wie viel du wegen Überlastung verwerfen musst.

    Für jedes empfangene Fragment nen eigenen Thread rausstarten ist auf jeden Fall nicht sehr effizient.


Log in to reply