Linux 最新SO_REUSEPORT特性

 

1、前言

昨天总结了一下Linux下网络编程“惊群”现象,给出Nginx处理惊群的方法,使用互斥锁。为例发挥多核的优势,目前常见的网络编程模型就是多进程或多线程,根据accpet的位置,分为如下场景:

(1)单进程或线程创建socket,并进行listen和accept,接收到连接后创建进程和线程处理连接

(2)单进程或线程创建socket,并进行listen,预先创建好多个工作进程或线程accept()在同一个服务器套接字、

Linux 最新SO_REUSEPORT特性                      Linux 最新SO_REUSEPORT特性

这两种模型解充分发挥了多核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 }

我的测试机器内核版本为:

Linux 最新SO_REUSEPORT特性

测试结果如下所示:

Linux 最新SO_REUSEPORT特性Linux 最新SO_REUSEPORT特性

Linux 最新SO_REUSEPORT特性

从结果可以看出,四个进程监听相同的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

原文链接:https://www.cnblogs.com/Anker/p/7076537.html

原创文章,作者:优速盾-小U,如若转载,请注明出处:https://www.cdnb.net/bbs/archives/32634

(0)
优速盾-小U的头像优速盾-小U
上一篇 2025年5月3日 20:14
下一篇 2025年5月4日 04:23

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

优速盾注册领取大礼包www.cdnb.net
/sitemap.xml