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
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).
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
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
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:
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:
Setup-Hinweise zeigten den Typ der Zieladresse als den einer UDP-Verbindung an
Verwenden Sie getaddrinfo, um die Adressinfostruktur abzurufen dstinfo basierend auf Argument 1, das die Zieladresse ist, und Argument 2, das der Zielport ist
Erstellen Sie einen Socket mit dem ersten gültigen Eintrag in dstinfo
Verwenden Sie getaddrinfo, um die Adressinfostruktur abzurufen srcinfo hauptsächlich für die Details des Quellports
Verwenden srcinfo an den erhaltenen Socket zu binden
Verbinden Sie sich nun mit dem ersten gültigen Eintrag von dstinfo
Wenn alles in Ordnung ist, betreten Sie die Schleife
Die Schleife verwendet eine Auswahl zum Blockieren einer Lesedeskriptorliste, die aus dem erstellten STDIN- und sockfd-Socket besteht
Wenn STDIN eine Eingabe hat, wird sie mit der Sendall-Funktion an die Ziel-UDP-Verbindung gesendet
Wenn EOM empfangen wird, wird die Schleife verlassen.
Wenn sockfd einige Daten hat, werden sie durch recv gelesen
Wenn recv -1 zurückgibt, handelt es sich um einen Fehler, den wir mit perror zu entschlüsseln versuchen
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.
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
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.
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.
14096700cookie-checkKönnen Sie beide Enden einer UDP-Verbindung binden () und verbinden ().yes
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 einfachsendto()
.) Ich persönlich würde einfach verwendensendto()
da Sie sonst verwirrt werden, wenn sich mehrere Clients mit Ihrem Server verbinden.– mpontillo
16. März 2012 um 17:37 Uhr