Ich betreibe meinen eigenen HTTP-Server auf einem Himbeer-Pi. Das Problem ist, wenn ich das Programm stoppe und neu starte, ist der Port nicht mehr verfügbar. Manchmal bekomme ich das gleiche Problem, wenn ich viele Anfragen erhalte.
Ich möchte SO_REUSEADDR verwenden, damit ich den Port auch dann weiter verwenden kann, wenn der Fehler auftritt, aber kein Glück hatte, ihn einzurichten. Unten ist mein Code.
Der Fehler, den ich erhalte, ist “FEHLER beim Binden: Adresse wird bereits verwendet”.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
void error(const char *msg)
{
perror(msg);
exit(1);
}
int main(int argc, char *argv[])
{
printf("Starting Listener\n");
int sockfd, newsockfd, portno;
socklen_t clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
if (argc < 2) {
fprintf(stderr,"ERROR, no port provided\n");
exit(1);
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = atoi(argv[1]);
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
if (bind(sockfd, (struct sockaddr *) &serv_addr,
sizeof(serv_addr)) < 0)
error("ERROR on binding");
printf("about to listen\n");
listen(sockfd,5);
printf("finished listening\n");
clilen = sizeof(cli_addr);
printf("About to accept\n");
int i;
for(i=0; i<100; i++){
newsockfd = accept(sockfd,
(struct sockaddr *) &cli_addr,
&clilen);
if (newsockfd < 0)
error("ERROR on accept");
bzero(buffer,256);
n = read(newsockfd,buffer,255);
if (n < 0) error("ERROR reading from socket");
printf("Here is the message: %s\n",buffer);
n = write(newsockfd,"I got your message",18);
if (n < 0) error("ERROR writing to socket");
close(newsockfd);
}
close(sockfd);
return 0;
}
Chnossos
Setzen Sie die Option, nachdem der Socket erfolgreich initialisiert wurde. So danach:
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0)
error("setsockopt(SO_REUSEADDR) failed");
Oder:
const int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
error("setsockopt(SO_REUSEADDR) failed");
Beachten Sie als zusätzlich zu SO_REUSEADDRmüssen Sie möglicherweise festlegen SO_REUSEPORT um das gewünschte Verhalten zu erreichen. Dies geschieht für beide Optionen genau gleich.
Ich habe beides ausprobiert und das gleiche Ergebnis erhalten, nachdem ich das Programm beendet und neu gestartet hatte. Dies ist der Fehler: Starting Listener FEHLER beim Binden: Adresse wird bereits verwendet about to listen beendet listening About to accept
– Benutzer3735849
14. Juni 2014 um 2:00 Uhr
Der Zweck von SO_REUSEADDR/SO_REUSEPORT besteht darin, die Wiederverwendung des Ports zu ermöglichen, selbst wenn der Prozess abstürzt oder beendet wurde.
– mpromonet
8. August 2014 um 22:07 Uhr
@Chnossos Ich teste gerade auf einem Himbeer-Pi mit Kernel 3.12.25, die Bindung funktioniert gut, auch wenn der Port noch von TIME_WAIT-Sockets verwendet wird (was bei einem Kill SIGKILL auftritt). Hast du den Setsockopt vor dem Bind-Aufruf eingefügt?
– mpromonet
9. August 2014 um 17:16 Uhr
mpromonet
Abhängig von der libc-Version kann es erforderlich sein, die Socket-Optionen SO_REUSEADDR und SO_REUSEPORT zu setzen, wie in erläutert Steckdose(7) Dokumentation:
SO_REUSEPORT (since Linux 3.9)
Permits multiple AF_INET or AF_INET6 sockets to be bound to an
identical socket address. This option must be set on each
socket (including the first socket) prior to calling bind(2)
on the socket. To prevent port hijacking, all of the
processes binding to the same address must have the same
effective UID. This option can be employed with both TCP and
UDP sockets.
Da diese Socket-Option mit Kernel 3.9 und Raspberry 3.12.x erscheint, muss SO_REUSEPORT gesetzt werden.
Sie können diese beiden Optionen festlegen, bevor Sie bind wie folgt aufrufen:
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0)
perror("setsockopt(SO_REUSEADDR) failed");
#ifdef SO_REUSEPORT
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0)
perror("setsockopt(SO_REUSEPORT) failed");
#endif
Ich denke, Sie sollten SO_LINGER-Optionen verwenden (mit Timeout 0). In diesem Fall wird Ihre Verbindung sofort nach dem Schließen Ihres Programms geschlossen; und beim nächsten Neustart kann wieder gebunden werden.
SO_LINGER
Sets or gets the SO_LINGER option. The argument is a linger
structure.
struct linger {
int l_onoff; /* linger active */
int l_linger; /* how many seconds to linger for */
};
When enabled, a close(2) or shutdown(2) will not return until
all queued messages for the socket have been successfully sent
or the linger timeout has been reached. Otherwise, the call
returns immediately and the closing is done in the background.
When the socket is closed as part of exit(2), it always
lingers in the background.
Mehr über SO_LINGER: TCP-Option SO_LINGER (Null) – wenn es erforderlich ist
Ich glaube, es sollte sagen lin.l_onoff = 1;. Andernfalls deaktivieren Sie die Option vollständig, was bedeutet, dass sie im Hintergrund verweilen würde.
– Drachenwurzel
8. März 2019 um 22:00 Uhr
Entweder so, wie es in der Antwort oder im Kommentar steht, bekomme ich keine Bindung, wenn ich mein Programm stoppe und sofort starte. Wenn ich SO_REUSEADDR setze, dann funktioniert es super.
– xaxxon
22. Mai 2019 um 23:06 Uhr
14174100cookie-checkWie verwende ich setsockopt(SO_REUSEADDR)?yes