ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

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

2021-08-21 21:01:43  阅读:184  来源: 互联网

标签:addr REUSEADDR server REUSEPORT int SO include port


一、SO_REUSEADDR

  目前为止我见到的设置SO_REUSEADDR的使用场景:server端在调用bind函数时

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

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

  注意:SO_REUSEADDR只有针对time-wait链接(linux系统time-wait连接持续时间为1min),确保server重启成功的这一个作用,至于网上有文章说:如果有socket绑定了0.0.0.0:port;设置该参数后,其他socket可以绑定本机ip:port。本人经过试验后均提示“Address already in use”错误,绑定失败。

举个例子:

  server监听9980端口,由于主动关闭链接,产生了一个time-wait状态的链接

  如果此时server重启,并且server没有设置SO_REUSEADDR参数,server重启失败,报错:“Address already in use”

  如果设置SO_REUSEADDR,重启ok;

二、SO_REUSEPORT

2.1 简介

  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 (安全性考虑)

2.2 使用实例

代码示例:server_128.c

 1 #include <stdio.h>                                                                                                               
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <netinet/in.h>
 5 #include <sys/socket.h>
 6 #include <arpa/inet.h>
 7 #include <sys/types.h>
 8 #include <errno.h>
 9 #include <time.h>
10 #include <unistd.h>
11 #include <sys/wait.h>
12 void work () {
13         int listenfd = socket(AF_INET, SOCK_STREAM, 0);
14         if (listenfd < 0) {
15                 perror("listen socket");
16                 _exit(-1);
17         }
18         int ret = 0;
19         int reuse = 1;
20         ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));
21         if (ret < 0) {
22                 perror("setsockopt");
23                 _exit(-1);
24         }
25         ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT,(const void *)&reuse , sizeof(int));
26         if (ret < 0) {
27             perror("setsockopt");
28             _exit(-1);
29         }
30         struct sockaddr_in addr;
31         memset(&addr, 0, sizeof(addr));
32         addr.sin_family = AF_INET;
33         //addr.sin_addr.s_addr = inet_addr("10.95.118.221");
34         addr.sin_addr.s_addr = inet_addr("0.0.0.0");                                                                             
35         addr.sin_port = htons(9980);
36         ret = bind(listenfd, (struct sockaddr *)&addr, sizeof(addr));
37         if (ret < 0) {
38                 perror("bind addr");
39                 _exit(-1);
40        }
41         printf("bind success\n");
42         ret = listen(listenfd,10);
43         if (ret < 0) {
44                 perror("listen");
45                 _exit(-1);
46         }
47         printf("listen success\n");
48         struct sockaddr clientaddr;
49         int len = 0;
50         while(1) {
51                 printf("process:%d accept...\n", getpid());
52                 int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &len);
53                 if (clientfd < 0) {
54                         printf("accept:%d %s", getpid(),strerror(errno));
55                         _exit(-1);
56                 }
57                 close(clientfd);
58                 printf("process:%d close socket\n", getpid());
59         }
60 }
61 int main(){
62         printf("uid:%d euid:%d\n", getuid(),geteuid());
63         int i = 0;
64         for (i = 0; i< 6; i++) {
65                 pid_t pid = fork();
66                 if (pid == 0) {
67                         work();
68                 }
69                 if(pid < 0) {
70                         perror("fork");
71                         continue;
72                 }
73         }
74         int status,id;
75         while((id=waitpid(-1, &status, 0)) > 0) {
76                 printf("%d exit\n", id);
77         }
78         if(errno == ECHILD) {
79                 printf("all child exit\n");
80         }
81         return 0;
82 }                     
View Code

  上述示例程序,启动了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,提示错误:

 

 三、参考文章

https://zhuanlan.zhihu.com/p/37278278

标签:addr,REUSEADDR,server,REUSEPORT,int,SO,include,port
来源: https://www.cnblogs.com/MrLiuZF/p/15170546.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有