[轉帖]Linux 最新SO_REUSEPORT特性

济南小老虎發表於2024-06-12

1、前言

  昨天總結了一下Linux下網路程式設計“驚群”現象,給出Nginx處理驚群的方法,使用互斥鎖。為例發揮多核的優勢,目前常見的網路程式設計模型就是多程序或多執行緒,根據accpet的位置,分為如下場景:

  (1)單程序或執行緒建立socket,並進行listen和accept,接收到連線後建立程序和執行緒處理連線

  (2)單程序或執行緒建立socket,並進行listen,預先建立好多個工作程序或執行緒accept()在同一個伺服器套接字、

這兩種模型解充分發揮了多核CPU的優勢,雖然可以做到執行緒和CPU核繫結,但都會存在:

  • 單一listener工作程序胡執行緒在高速的連線接入處理時會成為瓶頸
  • 多個執行緒之間競爭獲取服務套接字
  • 快取行跳躍
  • 很難做到CPU之間的負載均衡
  • 隨著核數的擴充套件,效能並沒有隨著提升

參考:http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

Linux kernel 3.9帶來了SO_REUSEPORT特性,可以解決以上大部分問題。

2、SO_REUSEPORT解決了什麼問題

SO_REUSEPORT支援多個程序或者執行緒繫結到同一埠,提高伺服器程式的效能,解決的問題:

  • 允許多個套接字 bind()/listen() 同一個TCP/UDP埠
    • 每一個執行緒擁有自己的伺服器套接字
    • 在伺服器套接字上沒有了鎖的競爭
  • 核心層面實現負載均衡
  • 安全層面,監聽同一個埠的套接字只能位於同一個使用者下面

其核心的實現主要有三點:

  • 擴充套件 socket option,增加 SO_REUSEPORT 選項,用來設定 reuseport。
  • 修改 bind 系統呼叫實現,以便支援可以繫結到相同的 IP 和埠
  • 修改處理新建連線的實現,查詢 listener 的時候,能夠支援在監聽相同 IP 和埠的多個 sock 之間均衡選擇。

有了SO_RESUEPORT後,每個程序可以自己建立socket、bind、listen、accept相同的地址和埠,各自是獨立平等的。讓多程序監聽同一個埠,各個程序中accept socket fd不一樣,有新連線建立時,核心只會喚醒一個程序來accept,並且保證喚醒的均衡性。

3、測試程式碼

複製程式碼
 1 include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>  
 4 #include <sys/socket.h>  
 5 #include <netinet/in.h>  
 6 #include <arpa/inet.h>  
 7 #include <assert.h>  
 8 #include <sys/wait.h>
 9 #include <string.h>
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <fcntl.h>
13 
14 #define IP   "127.0.0.1"
15 #define PORT  8888
16 #define WORKER 4
17 #define MAXLINE   4096
18 
19 int worker(int i)
20 {
21     struct sockaddr_in address;  
22     bzero(&address, sizeof(address));  
23     address.sin_family = AF_INET;  
24     inet_pton( AF_INET, IP, &address.sin_addr);  
25     address.sin_port = htons(PORT);  
26 
27     int listenfd = socket(PF_INET, SOCK_STREAM, 0);  
28     assert(listenfd >= 0);  
29 
30     int val =1;
31     /*set SO_REUSEPORT*/
32     if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val))<0) {
33         perror("setsockopt()");         
34     }    
35     int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));  
36     assert(ret != -1);  
37 
38     ret = listen(listenfd, 5);  
39     assert(ret != -1);  
40     while (1) {
41         printf("I am worker %d, begin to accept connection.\n", i);
42         struct sockaddr_in client_addr;  
43         socklen_t client_addrlen = sizeof( client_addr );  
44         int connfd = accept( listenfd, ( struct sockaddr* )&client_addr, &client_addrlen );  
45         if (connfd != -1) {
46             printf("worker %d accept a connection success. ip:%s, prot:%d\n", i, inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
47         } else {
48             printf("worker %d accept a connection failed,error:%s", i, strerror(errno));
49         }
50         char buffer[MAXLINE];
51         int nbytes = read(connfd, buffer, MAXLINE);
52         printf("read from client is:%s\n", buffer);
53         write(connfd, buffer, nbytes);
54         close(connfd);
55     }
56     return 0;
57 }
58 
59 int main()
60 {
61     int i = 0;
62     for (i = 0; i < WORKER; i++) {
63         printf("Create worker %d\n", i);
64         pid_t pid = fork();
65         /*child  process */
66         if (pid == 0) {
67             worker(i);
68         }
69         if (pid < 0) {
70             printf("fork error");
71         }
72     }
73     /*wait child process*/
74     while (wait(NULL) != 0)
75         ;
76     if (errno == ECHILD) {
77         fprintf(stderr, "wait error:%s\n", strerror(errno));
78     }
79     return 0;
80 }
複製程式碼

我的測試機器核心版本為:

測試結果如下所示:

從結果可以看出,四個程序監聽相同的IP和port。

4、參考資料

http://lists.dragonflybsd.org/pipermail/users/2013-July/053632.html

http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html

http://m.blog.chinaunix.net/uid-10167808-id-3807060.html

冷靜思考,勇敢面對,把握未來!

相關文章