如何使用 setsockopt (SO_REUSEADDR) ?

我在一个树莓派上运行我自己的 http 服务器。问题是,当我停止程序并重新启动它时,端口不再可用。有时候,当我收到很多请求时,我也会遇到同样的问题。
我希望使用 SO _ REUSEADDR,这样即使发生错误但还没有设置好端口,我也可以继续使用该端口。下面是我的代码。
我得到的错误是“ ERROR on binding: Address already in use”。

#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;
}
172583 次浏览

在套接字成功初始化后设置选项。因此,在:

sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
error("ERROR opening socket");

您可以添加(使用标准 C99复合文字支持) :

if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)) < 0)
error("setsockopt(SO_REUSEADDR) failed");

或者:

const int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0)
error("setsockopt(SO_REUSEADDR) failed");

注意,除了 SO_REUSEADDR之外,您可能还需要设置 SO_REUSEPORT来获得所需的行为。这两个选项的操作方式完全相同。

根据 libc 版本的不同,可能需要设置 SO _ REUSEADDR 和 SO _ REUSEPORT 套接字选项,如 插座(7)文档所述:

   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.

由于这个套接字选项出现在内核3.9中,而覆盆子使用3.12.x,因此需要设置 SO _ REUSEPORT。

在调用 bind 之前,可以设置以下两个选项:

    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

我认为应该使用 SO _ LINGER 选项(带有超时0)。 在这种情况下,您的连接将在关闭程序后立即关闭; 下一次重新启动将能够再次绑定。

例如:

linger lin;
lin.l_onoff = 0;
lin.l_linger = 0;
setsockopt(fd, SOL_SOCKET, SO_LINGER, (const char *)&lin, sizeof(int));

见定义: http://man7.org/linux/man-pages/man7/socket.7.html

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.

更多关于 SO _ LINGER: TCP 选项 SO _ LINGER (zero)-当需要时

这是如何在 windows 操作系统中使用:

要避免在 WindowsPC 中的其他应用程序中使用端口重用: 独家报道

参考文献: Https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse