Können Sie beide Enden einer UDP-Verbindung binden () und verbinden ().

Lesezeit: 10 Minuten

Ich schreibe ein Punkt-zu-Punkt-Nachrichtenwarteschlangensystem, und es muss in der Lage sein, über UDP zu arbeiten. Ich könnte willkürlich die eine oder andere Seite als “Server” auswählen, aber es scheint nicht ganz richtig zu sein, da beide Enden die gleiche Art von Daten von der anderen senden und empfangen.

Ist es möglich, beide Enden zu bind() und connect() zu verbinden, sodass sie nur voneinander senden/empfangen? Das scheint ein schön symmetrischer Weg zu sein.

  • Scheint ein bisschen seltsam, aber ich verstehe nicht, warum nicht. connect() setzt nur die Standard-Zieladresse/Port für den Socket. (Haben Sie es versucht? Wenn es aus irgendeinem Grund nicht funktioniert, verwenden Sie es einfach sendto().) Ich persönlich würde einfach verwenden sendto() da Sie sonst verwirrt werden, wenn sich mehrere Clients mit Ihrem Server verbinden.

    – mpontillo

    16. März 2012 um 17:37 Uhr

Benutzeravatar von ximaera
Ximaera

Hallo aus der fernen Zukunft, die das Jahr 2018 ist, in das Jahr 2012.

Es gibt tatsächlich einen Grund dahinter connect()ing einen UDP-Socket in der Praxis (obwohl gesegnet POSIX und seine Ausführungen theoretisch nicht von Ihnen verlangen).

Ein gewöhnlicher UDP-Socket weiß also nichts über seine zukünftigen Ziele es führt jedes Mal eine Routensuche durch sendmsg() wird genannt.

jedoch, wenn connect() zuvor mit der IP und dem Port eines bestimmten entfernten Empfängers aufgerufen wird, kann der Betriebssystemkernel dies tun Notieren Sie sich die Referenz auf die Route und weisen Sie sie der Steckdose zuwodurch das Senden einer Nachricht erheblich schneller wird, falls später sendmsg() Anrufe Geben Sie keinen Empfänger an (andernfalls würde die vorherige Einstellung ignoriert), indem Sie stattdessen die Standardeinstellung auswählen.

Schaue auf die Linien 1070 durch 1171:

if (connected)
    rt = (struct rtable *)sk_dst_check(sk, 0);

if (!rt) {
    [..skip..]

    rt = ip_route_output_flow(net, fl4, sk);

    [..skip..]
}

Bis zum Linux-Kernel 4.18 war diese Funktion hauptsächlich auf die IPv4-Adressfamilie beschränkt. Seit 4.18-rc4 (und hoffentlich auch der Linux-Kernel-Version 4.18) Es ist auch mit IPv6-Sockets voll funktionsfähig.

Es kann eine Quelle von sein ein gravierender Leistungsvorteil, obwohl es stark von dem Betriebssystem abhängt, das Sie verwenden. Wenn Sie Linux verwenden und den Socket nicht für mehrere Remote-Handler verwenden, sollten Sie es zumindest versuchen.

  • Können Sie bitte erklären, was eine Routensuche ist? Sind es nur CPU-Zyklen, um zu bestimmen, welches Ethernet-Gerät das Datagramm verwendet werden muss? Oder wird eine Art I/O verursacht, was viel Zeit in Anspruch nehmen könnte?

    – Sekto Kia

    19. Februar 2019 um 11:22 Uhr

  • @SectoKia nein, das ist nur eine Hashtable-Suche. Die Hashtabelle bleibt im RAM, es handelt sich also nur um CPU-Zyklen und RAM-Lookups.

    – Ximaera

    19. Februar 2019 um 12:40 Uhr

  • Ich schaue gerade wieder auf ein ähnliches Problem, also hallo aus der fernen Zukunft von 2019!

    – gct

    14. Mai 2019 um 21:39 Uhr

Benutzeravatar von datenwolf
Datenwolf

UDP ist verbindungslos, daher macht es für das Betriebssystem wenig Sinn, tatsächlich eine Art Verbindung herzustellen.

In BSD-Sockets kann man a connect auf einem UDP-Socket, aber dies legt im Grunde nur die Standardzieladresse für fest send (anstatt explizit zu geben send_to).

Binden an einen UDP-Socket teilt dem Betriebssystem mit, für welche eingehenden, lokale Schnittstelle Adresse, um Pakete tatsächlich zu akzeptieren (alle Pakete an andere Adressen werden verworfen), unabhängig von der Art des Sockets.

Nach Erhalt müssen Sie verwenden recvfrom um festzustellen, von welcher Quelle das Paket stammt. Beachten Sie, dass, wenn Sie eine Art Authentifizierung wünschen, die Verwendung nur der beteiligten Adressen so unsicher ist wie überhaupt keine Sperre. TCP-Verbindungen können gekapert werden, und Naked UDP hat IP-Spoofing buchstäblich in den Kopf geschrieben. Sie müssen eine Art HMAC hinzufügen

  • Nun, connect () auf einem SOCK_DGRAM-Socket legt die Standard-/Sende-Empfangsadresse fest, sodass Sie einfach send und recv verwenden können. Ich schreibe es so, dass es auch über TCP funktioniert, so dass am Ende ein anderer Code entsteht, der beiden Protokollen gemeinsam ist.

    – gct

    16. März 2012 um 18:18 Uhr

  • @gct: In der Tat. Ich war mir zuerst nicht so sicher, musste zuerst die Connect-Manpage der BSD-Sockets nachschlagen (hatte hier nur die Linux-Manpage und ich halte dies nicht für alle Betriebssysteme für maßgeblich).

    – Datenwolf

    16. März 2012 um 18:59 Uhr

  • @nhed: Nein, was ich meine ist, dass Linux a unterstützt super Satz von BSD-Sockets mit mehreren Erweiterungen. Und ich musste eine zufällige Referenzseite nachschlagen, um sicherzugehen, dass ich nicht über Linux-spezifische Erweiterungen schreibe.

    – Datenwolf

    1. Juni 2013 um 14:49 Uhr

  • @ user1511417: Nein. Du kannst es immer noch verwenden recvfrom auf einem UDP-Socket auf dem bind() aufgerufen wurde und weiterhin Pakete akzeptiert aus beliebige Adressen.

    – Datenwolf

    6. Dezember 2016 um 10:45 Uhr


  • @datenwolf Ah: Ich habe “Eingangsadresse” als Adresse der Remote-Maschine gelesen. Vielleicht wäre “lokale Adresse” klarer?

    – Daniel Griscom

    27. November 2021 um 14:20 Uhr

Benutzeravatar von Conrad Gomes
Konrad Gömes

Hier ist ein Programm, das demonstriert, wie man bind() und connect() auf demselben UDP-Socket mit einem bestimmten Satz von Quell- bzw. Zielports verbindet. Das Programm kann auf jedem Linux-Rechner kompiliert werden und hat folgende Verwendung:

usage: ./<program_name> dst-hostname dst-udpport src-udpport

Ich habe diesen Code getestet, indem ich zwei Terminals geöffnet habe. Sie sollten in der Lage sein, eine Nachricht an den Zielknoten zu senden und Nachrichten von ihm zu empfangen.

In Klemme 1 laufen

./<program_name> 127.0.0.1 5555 5556

In Klemme 2 laufen

./<program_name> 127.0.0.1 5556 5555

Obwohl ich es auf einem einzelnen Computer getestet habe, denke ich, dass es auch auf zwei verschiedenen Computern funktionieren sollte, sobald Sie die richtigen Firewall-Einstellungen eingerichtet haben

Hier ist eine Beschreibung des Ablaufs:

  1. Setup-Hinweise zeigten den Typ der Zieladresse als den einer UDP-Verbindung an
  2. Verwenden Sie getaddrinfo, um die Adressinfostruktur abzurufen dstinfo basierend auf Argument 1, das die Zieladresse ist, und Argument 2, das der Zielport ist
  3. Erstellen Sie einen Socket mit dem ersten gültigen Eintrag in dstinfo
  4. Verwenden Sie getaddrinfo, um die Adressinfostruktur abzurufen srcinfo hauptsächlich für die Details des Quellports
  5. Verwenden srcinfo an den erhaltenen Socket zu binden
  6. Verbinden Sie sich nun mit dem ersten gültigen Eintrag von dstinfo
  7. Wenn alles in Ordnung ist, betreten Sie die Schleife
  8. Die Schleife verwendet eine Auswahl zum Blockieren einer Lesedeskriptorliste, die aus dem erstellten STDIN- und sockfd-Socket besteht
  9. Wenn STDIN eine Eingabe hat, wird sie mit der Sendall-Funktion an die Ziel-UDP-Verbindung gesendet
  10. Wenn EOM empfangen wird, wird die Schleife verlassen.
  11. Wenn sockfd einige Daten hat, werden sie durch recv gelesen
  12. Wenn recv -1 zurückgibt, handelt es sich um einen Fehler, den wir mit perror zu entschlüsseln versuchen
  13. Wenn recv 0 zurückgibt, bedeutet dies, dass der entfernte Knoten die Verbindung geschlossen hat. Aber ich glaube das hat bei UDP keine Auswirkung was verbindungslos ist.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define STDIN 0

int sendall(int s, char *buf, int *len)
{
    int total = 0;        // how many bytes we've sent
    int bytesleft = *len; // how many we have left to send
    int n;

    while(total < *len) {
        n = send(s, buf+total, bytesleft, 0);
        fprintf(stdout,"Sendall: %s\n",buf+total);
        if (n == -1) { break; }
        total += n;
        bytesleft -= n;
    }

    *len = total; // return number actually sent here

    return n==-1?-1:0; // return -1 on failure, 0 on success
} 

int main(int argc, char *argv[])
{
   int sockfd;
   struct addrinfo hints, *dstinfo = NULL, *srcinfo = NULL, *p = NULL;
   int rv = -1, ret = -1, len = -1,  numbytes = 0;
   struct timeval tv;
   char buffer[256] = {0};
   fd_set readfds;

   // don't care about writefds and exceptfds:
   //     select(STDIN+1, &readfds, NULL, NULL, &tv);

   if (argc != 4) {
      fprintf(stderr,"usage: %s dst-hostname dst-udpport src-udpport\n");
      ret = -1;
      goto LBL_RET;
   }


   memset(&hints, 0, sizeof hints);
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_DGRAM;        //UDP communication

   /*For destination address*/
   if ((rv = getaddrinfo(argv[1], argv[2], &hints, &dstinfo)) != 0) {
      fprintf(stderr, "getaddrinfo for dest address: %s\n", gai_strerror(rv));
      ret = 1;
      goto LBL_RET;
   }

   // loop through all the results and make a socket
   for(p = dstinfo; p != NULL; p = p->ai_next) {

      if ((sockfd = socket(p->ai_family, p->ai_socktype,
                  p->ai_protocol)) == -1) {
         perror("socket");
         continue;
      }
      /*Taking first entry from getaddrinfo*/
      break;
   }

   /*Failed to get socket to all entries*/
   if (p == NULL) {
      fprintf(stderr, "%s: Failed to get socket\n");
      ret = 2;
      goto LBL_RET;
   }

   /*For source address*/
   memset(&hints, 0, sizeof hints);
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_DGRAM;        //UDP communication
   hints.ai_flags = AI_PASSIVE;     // fill in my IP for me
   /*For source address*/
   if ((rv = getaddrinfo(NULL, argv[3], &hints, &srcinfo)) != 0) {
      fprintf(stderr, "getaddrinfo for src address: %s\n", gai_strerror(rv));
      ret = 3;
      goto LBL_RET;
   }

   /*Bind this datagram socket to source address info */
   if((rv = bind(sockfd, srcinfo->ai_addr, srcinfo->ai_addrlen)) != 0) {
      fprintf(stderr, "bind: %s\n", gai_strerror(rv));
      ret = 3;
      goto LBL_RET;
   }

   /*Connect this datagram socket to destination address info */
   if((rv= connect(sockfd, p->ai_addr, p->ai_addrlen)) != 0) {
      fprintf(stderr, "connect: %s\n", gai_strerror(rv));
      ret = 3;
      goto LBL_RET;
   }

   while(1){
      FD_ZERO(&readfds);
      FD_SET(STDIN, &readfds);
      FD_SET(sockfd, &readfds);

      /*Select timeout at 10s*/
      tv.tv_sec = 10;
      tv.tv_usec = 0;
      select(sockfd + 1, &readfds, NULL, NULL, &tv);

      /*Obey your user, take his inputs*/
      if (FD_ISSET(STDIN, &readfds))
      {
         memset(buffer, 0, sizeof(buffer));
         len = 0;
         printf("A key was pressed!\n");
         if(0 >= (len = read(STDIN, buffer, sizeof(buffer))))
         {
            perror("read STDIN");
            ret = 4;
            goto LBL_RET;
         }

         fprintf(stdout, ">>%s\n", buffer);

         /*EOM\n implies user wants to exit*/
         if(!strcmp(buffer,"EOM\n")){
            printf("Received EOM closing\n");
            break;
         }

         /*Sendall will use send to transfer to bound sockfd*/
         if (sendall(sockfd, buffer, &len) == -1) {
            perror("sendall");
            fprintf(stderr,"%s: We only sent %d bytes because of the error!\n", argv[0], len);
            ret = 5;
            goto LBL_RET;
         }  
      }

      /*We've got something on our socket to read */
      if(FD_ISSET(sockfd, &readfds))
      {
         memset(buffer, 0, sizeof(buffer));
         printf("Received something!\n");
         /*recv will use receive to connected sockfd */
         numbytes = recv(sockfd, buffer, sizeof(buffer), 0);
         if(0 == numbytes){
            printf("Destination closed\n");
            break;
         }else if(-1 == numbytes){
            /*Could be an ICMP error from remote end*/
            perror("recv");
            printf("Receive error check your firewall settings\n");
            ret = 5;
            goto LBL_RET;
         }
         fprintf(stdout, "<<Number of bytes %d Message: %s\n", numbytes, buffer);
      }

      /*Heartbeat*/
      printf(".\n");
   }

   ret = 0;
LBL_RET:

   if(dstinfo)
      freeaddrinfo(dstinfo);

   if(srcinfo)
      freeaddrinfo(srcinfo);

   close(sockfd);

   return ret;
}

Wirklich der Schlüssel ist connect():

Wenn der Socket sockfd vom Typ SOCK_DGRAM ist, dann ist addr die Adresse, an die Datagramme standardmäßig gesendet werden, und die einzige Adresse, von der Datagramme empfangen werden.

Es gibt ein Problem in Ihrem Code:

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;        //UDP communication

/*For destination address*/
if ((rv = getaddrinfo(argv[1], argv[2], &hints, &dstinfo)) 

Wenn Sie nur AF_UNSPEC und SOCK_DGRAM verwenden, erhalten Sie eine Liste aller möglichen Adressen. Wenn Sie also socket aufrufen, ist die von Ihnen verwendete Adresse möglicherweise nicht die erwartete UDP-Adresse. Du solltest benutzen

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE;

Stellen Sie stattdessen sicher, dass die von Ihnen abgerufene addrinfo Ihren Wünschen entspricht.

Mit anderen Worten, der von Ihnen erstellte Socket ist möglicherweise kein UDP-Socket, und das ist der Grund, warum er nicht funktioniert.

Benutzeravatar von konrad
Konrad

Diese Seite enthält einige großartige Informationen über verbundene und nicht verbundene Steckdosen:
http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch08lev1sec11.html

Dieses Zitat beantwortet Ihre Frage:

Normalerweise ist es ein UDP-Client, der Connect aufruft, aber es gibt Anwendungen, bei denen der UDP-Server über einen längeren Zeitraum mit einem einzelnen Client kommuniziert (z. B. TFTP); In diesem Fall können sowohl der Client als auch der Server connect aufrufen.

Benutzeravatar von Nathan Tuggy
Nathan Tuggy

Ich habe connect() unter UDP nicht verwendet. Ich habe das Gefühl, dass connect () für zwei völlig unterschiedliche Zwecke unter UDP vs. TCP entwickelt wurde.

Die Manpage enthält einige kurze Details zur Verwendung von connect() unter UDP:

Im Allgemeinen können verbindungsbasierte Protokoll-Sockets (wie TCP) nur einmal erfolgreich connect() herstellen; Sockets für verbindungslose Protokolle (wie UDP) können connect() mehrmals verwenden, um ihre Zuordnung zu ändern.

1409670cookie-checkKönnen Sie beide Enden einer UDP-Verbindung binden () und verbinden ().

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy