网络编程中的SO_REUSEADDR和SO_REUSEPORT参数详解

293 篇文章 16 订阅

1、SO_REUSEADDR:

在BSD中,SO_REUSEADDR选项有两个用户:

  1. 如果有socket绑定了0.0.0.0:port;设置该参数后,其他socket可以绑定本机ip:port。(该功能在linux上不支持)
  2. 设置SO_REUSEADDR选项,对应TCP套接字处于TIME_WAIT状态下的socket可以重复绑定实用

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));

目的:当服务端出现timewait状态的链接时,确保server能够重启成功。

注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用。

举个time_wait的连接通过so_reuseaddr选项重用的例子:

服务端:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#define MAXLINE 4096

int main()
{
   int listenfd,acceptfd,n;
   socklen_t  clilen;
   char recvbuf[100]={0};
   struct sockaddr_in cliaddr,servaddr;

   listenfd=socket(AF_INET,SOCK_STREAM,0);
   servaddr.sin_family=AF_INET;
   servaddr.sin_port=htons(8888);
   servaddr.sin_addr.s_addr = INADDR_ANY; 

   int iOpt = 1;
   setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &iOpt, sizeof(iOpt));

   bind(listenfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr_in));
   listen(listenfd,5);

   clilen=sizeof(cliaddr);
   acceptfd=accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);


   getchar();
   close(acceptfd);
   close(listenfd);
   return 0;
}

客户端:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netinet/tcp.h>
#define MAXLINE 4096

int main()
{
   int sockfd,ret;
   struct sockaddr_in servaddr;
   char sendbuf[1000]={0};

   sockfd=socket(AF_INET,SOCK_STREAM,0);
   bzero(&servaddr,sizeof(servaddr));
   servaddr.sin_family=AF_INET;
   servaddr.sin_port=htons(8888);
   servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");


   ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
   printf("ret=%d\n",ret);

   getchar();
   close(sockfd);
   return 0;
}

编译,先启动服务端,再启动客户端。然后先关闭服务端,再关闭客户端,让服务端处理TIME_WAIT状态。

此时,在启动服务端,启动客户端,可以看到connect函数成功返回。如果没有设置SO_REUSEADDR选项,则connect函数会返回-1,不信可以试试。 

看,客户端返回成功了,很简单,测试一下就知道了。 

https://blog.51cto.com/u_15144024/2860168

2、SO_REUSEPORT:

SO_REUSEPORT使用场景:linux kernel 3.9 引入了最新的SO_REUSEPORT选项,使得多进程或者多线程创建多个绑定同一个ip:port的监听socket,提高服务器的接收链接的并发能力,程序的扩展性更好;此时需要设置SO_REUSEPORT(注意所有进程都要设置才生效)。

setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));

目的:每一个进程有一个独立的监听socket,并且bind相同的ip:port,独立的listen()和accept();提高接收连接的能力。(例如nginx多进程同时监听同一个ip:port)

解决的问题:

(1)避免了应用层多线程或者进程监听同一ip:port的“惊群效应”。

(2)内核层面实现负载均衡,保证每个进程或者线程接收均衡的连接数。

(3)只有effective-user-id相同的服务器进程才能监听同一ip:port (安全性考虑)

代码示例:server_128.c

#include <stdio.h>                                                                                                               
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
void work () {
        int listenfd = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd < 0) {
                perror("listen socket");
                _exit(-1);
        }
        int ret = 0;
        int reuse = 1;
        ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
        if (ret < 0) {
                perror("setsockopt");
                _exit(-1);
        }
        ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
        if (ret < 0) {
            perror("setsockopt");
            _exit(-1);
        }
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        //addr.sin_addr.s_addr = inet_addr("10.95.118.221");
        addr.sin_addr.s_addr = inet_addr("0.0.0.0");                                                                             
        addr.sin_port = htons(9980);
        ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
        if (ret < 0) {
                perror("bind addr");
                _exit(-1);
       }
        printf("bind success\n");
        ret = listen(listenfd,10);
        if (ret < 0) {
                perror("listen");
                _exit(-1);
        }
        printf("listen success\n");
        struct sockaddr clientaddr;
        int len = 0;
        while(1) {
                printf("process:%d accept...\n", getpid());
                int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
                if (clientfd < 0) {
                        printf("accept:%d %s", getpid(),strerror(errno));
                        _exit(-1);
                }
                close(clientfd);
                printf("process:%d close socket\n", getpid());
        }
}
int main(){
        printf("uid:%d euid:%d\n", getuid(),geteuid());
        int i = 0;
        for (i = 0; i< 6; i++) {
                pid_t pid = fork();
                if (pid == 0) {
                        work();
                }
                if(pid < 0) {
                        perror("fork");
                        continue;
                }
        }
        int status,id;
        while((id=waitpid(-1, &status, 0)) > 0) {
                printf("%d exit\n", id);
        }
        if(errno == ECHILD) {
                printf("all child exit\n");
        }
        return 0;
}                              

上述示例程序,启动了6个子进程,每个子进程创建自己的监听socket,bind相同的ip:port;独立的listen(),accept();如果每个子进程不设置SO_REUSEADDR选项,会提示“Address already in use”错误。

安全性:是不是只要设置了SO_REUSEPORT选项的服务器程序,就能够监听相同的ip:port;并获取报文呢?当然不是,当前linux内核要求后来启动的服务器程序与前面启动的服务器程序的effective-user-id要相同。

举个例子:

上图中的server_127和server128两个服务器程序都设置了SO_REUSEPORT,监听同一ip:port,用root用户启动两个服务器程序,因为effective-user-id相等,都为0,所以启动没问题。

修改server127的权限chmod u+s server_127

启动完server_128后,在启动server_127,提示错误:

网络编程中的SO_REUSEADDR和SO_REUSEPORT参数详解 - 知乎

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值