一、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 }
上述示例程序,啟動了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