如何使用setsockopt(SO_REUSEADDR)?

78

我正在树莓派上运行我的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; 
}
4个回答

111

在套接字成功初始化之后设置选项。因此,在以下操作之后:

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才能获得所需的行为。对于这两个选项,设置方式完全相同。

1
我尝试了两种方法,在杀死程序并重新启动后得到了相同的结果。这是错误信息: 开始监听 绑定错误:地址已在使用中 即将监听 完成监听 即将接受 - user3735849
11
SO_REUSEADDR/SO_REUSEPORT 的目的是允许在进程崩溃或被杀死的情况下重用端口。 - mpromonet
@Chnossos 我刚在一个内核为3.12.25的树莓派上进行了测试,即使端口仍由TIME_WAIT套接字使用(这在使用SIGKILL命令杀死进程时会发生),绑定操作也能正常工作。您是否在bind调用之前插入了setsockopt函数? - mpromonet
你能解释一下 &(int){1} 吗?我有点理解不了。 - Daniel Walker
1
@DanielWalker 当然,可以查看我回答中关于C99复合字面量的链接。一旦你理解了它是什么,就会发现我只是将其地址作为参数传递。 - Chnossos

50
根据libc版本的不同,可能需要设置SO_REUSEADDR和SO_REUSEPORT套接字选项,如socket(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

5
我认为你应该使用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(零) - 何时需要


3
我认为应该写成 lin.l_onoff = 1;。否则你会完全禁用该选项,这意味着它将在后台挂起。 - dragonroot
2
无论是答案还是评论中的方式,如果我停止并立即启动程序,则会出现绑定失败。如果添加SO_REUSEADDR选项,则可以解决这个问题。 - xaxxon

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接