https://oms.inspur.com/cwbase/web/gsprtf/main.aspx
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