網絡編程中的SO_REUSEADDR和SO_REUSEPORT參數詳解


一、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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM