前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >提高服务端性能的几个socket选项

提高服务端性能的几个socket选项

作者头像
charlieroro
发布2020-12-18 10:59:30
1.3K0
发布2020-12-18 10:59:30
举报
文章被收录于专栏:charlierorocharlieroro

提高服务端性能的几个socket选项

在之前的一篇文章中,作者在配置了SO_REUSEPORT选项之后,使得应用的性能提高了数十倍。现在介绍socket选项中如下几个可以提升服务端性能的选项:

  • SO_REUSEADDR
  • SO_REUSEPORT
  • SO_ATTACH_REUSEPORT_CBPF/EBPF

验证环境:OS:centos 7.8;内核:5.9.0-1.el7.elrepo.x86_64

默认行为

TCP/UDP连接主要靠五元组来区分一条链接。只要五元组不同,则视为不同的连接。

{protocol, src addr, src port, dest addr, dest port}

默认情况下,两个sockets不能绑定相同的源地址和源端口。运行如下服务端代码,然后使用nc 127.0.0.1 9999连接服务端,通过crtl+c中断服务之后,此时可以在系统上看到到9999端口有一条连接处于TIME-WAIT状态,再启动服务端就可以看到Address already in use错误。

代码语言:javascript
复制
# ss -nta|grep TIME-WAIT
TIME-WAIT  0      0      127.0.0.1:9999               127.0.0.1:49040
代码语言:javascript
复制
//例1
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[]) {
    int lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (lfd == -1) {
        perror("socket: ");
        return -1;
    }

    struct sockaddr_in sockaddr;
    memset(&sockaddr, 0, sizeof(struct sockaddr_in));
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons(9999);
    inet_pton(AF_INET, "127.0.0.1", &sockaddr.sin_addr);
    
    if (bind(lfd, (struct sockaddr*)&sockaddr, sizeof(sockaddr)) == -1) {
        perror("bind: ");
        return -1;
    }

    if (listen(lfd, 128) == -1) {
        perror("listen: ");
        return -1;
    }

    struct sockaddr_storage claddr;
    socklen_t addrlen = sizeof(struct sockaddr_storage);
    int cfd = accept(lfd, (struct sockaddr*)&claddr, &addrlen);
    if (cfd == -1) {
        perror("accept: ");
        return -1;
    }
    printf("client connected: %d\n", cfd);

    char buff[100];
    for (;;) {
        ssize_t num = read(cfd, buff, 100);
        if (num == 0) {
            printf("client close: %d\n", cfd);
            close(cfd);
            break;
        } else if (num == -1) {
            int no = errno;
            if (no != EINTR && no != EAGAIN && no != EWOULDBLOCK) {
                printf("client error: %d\n", cfd);
                close(cfd);
            }
        } else {
            if (write(cfd, buff, num) != num) {
                printf("client error: %d\n", cfd);
                close(cfd);
            }
        }
    }
    return 0;
}

只要源端口不同,源地址实际上就无关紧要。假设将socketA绑定到A:X,socketB绑定到B:Y,其中A和B为地址,X和Y为端口。只要X!=Y(端口不同),这两个socket都能绑定成功。如果X==Y,只要A!=B(地址不同),这两个socket也能绑定成功。如果一个socket绑定到了0.0.0.0:21,则表示该socket绑定了所有现有的本地地址,此时,其他socket不能绑定到任何本地地址的21端口上。否则同样会出现Address already in use错误。

测试场景为:创建两个绑定地址分别为0.0.0.0127.0.0.1的服务app1和app2。启动app1-->nc连接app1-->ctrl+c断开app1-->启动app2,此时就会出现Address already in use错误。

TCP客户端通常不会绑定IP地址,内核会根据路由表选择连接需要的源地址;而服务端通常会绑定一个地址,如果绑定了INADDR_ANY,则内核会使用接收到的报文的目的地址作为服务端的源地址。IPv4地址绑定的规则如下: IP Address IP Port Result INADDR_ANY 0 Kernel chooses IP address and port INADDR_ANY non zero Kernel chooses IP address, process specifies port Local IP address 0 Process specifies IP address, kernel chooses port Local IP address non zero Process specifies IP address and port

SO_REUSEADDR

在启用SO_REUSEADDR 选项之后,就可以在TCP_LISTEN状态复用本地地址,当然,主要是为了在TIME_WAIT状态复用本地地址(如支持服务端快速重启)。需要注意的是Linux中对该选项的实现与BSD不同:前者要求复用者和被复用者都必须设置SO_REUSEADDR 选项,而后者仅要求复用者设置SO_REUSEADDR 选项即可。参见Linux socket帮助文档。

启用SO_REUSEADDR 选项后,在例1中的bind前添加如下代码,然后运行,此时不会再报错:

代码语言:javascript
复制
    int optval = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

SO_REUSEPORT

使用SO_REUSEPORT选项之后,就可以完全复用端口(无论被复用者处理任何状态)。SO_REUSEPORT的目的主要是为多核多线程环境提供并行处理能力。如可以启用多个worker线程,这些worker线程绑定相同的地址和端口。当新接入一条流时,内核会使用流哈希算法选择使用哪个socket。

SO_REUSEADDR 选项类似,使用SO_REUSEPORT选项时,同样要求复用者和被复用者同时设置该选项,如果被复用者没有设置,即使复用者设置了该选项,最终绑定还是失败的。

使用SO_REUSEPORT选项时可以不使用SO_REUSEADDR 选项。设置方式为:

代码语言:javascript
复制
setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));

SO_ATTACH_REUSEPORT_CBPF/EBPF

BPF相关的socket选项介绍

socket选项中,与bpf相关的有的有如下四个选项

  • SO_ATTACH_FILTER(since Linux 2.2):给socket附加一个cBPF,用于过滤接收到的报文。
  • SO_ATTACH_BPF(since Linux 3.19) :给socket附加一个eBPF,用于过滤接收到的报文。其参数为bpf(2)返回的指向类型为BPF_PROG_TYPE_SOCKET_FILTER的程序的文件描述符
  • SO_ATTACH_REUSEPORT_CBPF:与SO_REUSEPORT 配合使用,用于将报文分给reuseport组(即配置了SO_REUSEPORT选项,且使用相同的本地地址接收报文 )中的socket。如果BPF程序返回了无效的值,则回退为SO_REUSEPORT 机制,与SO_ATTACH_FILTER使用相同的参数。 socket按添加到组的顺序进行编号(即UDP socket使用bind(2)的顺序,或TCP socket使用listen(2)的顺序),当一个reuseport组新增一个socket后,该socket会集成该组中的BPF程序。当一个reuseport组(通过close(2))移除一个socket时,组中的最后一个socket会转移到closed位置。
  • SO_ATTACH_REUSEPORT_EBPF:与SO_ATTACH_BPF使用相同的参数。
  • SO_DETACH_FILTER(since Linux 2.2)/SO_DETACH_BPF(since Linux 3.19) :用于移除使用SO_ATTACH_FILTER/SO_ATTACH_BPF附加到socket的cBPF/eBPF。
  • SO_LOCK_FILTER :用于防止附加的过滤器被意外detach掉。 Linux 4.5添加了对UDP的支持,Linux 4.6添加了对TCP的支持。
如何使用BPF socket选项
如何编写BPF程序

Note:建议借用xdp-tutorial中的Makefile编译bpf内核态和用户态的程序。

将socket与BPF程序关联

有了上述知识,其实将socket与BPF程序关联其实就是将BPF过滤出来的报文传递给这个关联的socket。

提高UDP交互性能一文中,提高流量的一个方式就是使用BPF程序将socket与CPU核关联起来,实际就是将一个socket与这个核上的流进行了关联,防止因为哈希算法导致多条流争用同一个socket导致性能下降,也提升了CPU缓存的命中率。

还有一点需要注意的是,使用BPF将socket与CPU核进行关联之前,需要确保该socket所在的流不会漂移到其他核上,在提高UDP交互性能中使用了irqbalance的-h exact选项,防止冲突核漂移。

拓展

  • 系统参数net.ipv4.tcp_tw_reuse可以用于快速回收TIME_WAIT状态的端口,但只适用于客户端,且只在客户端执行connect时才会生效。调用链如下: tcp_v4_pre_connect->inet_hash_connect->__inet_check_established->tcp_twsk_unique->sysctl_tcp_tw_reuse(内核参数值)
  • How do SO_REUSEADDR and SO_REUSEPORT differ? 这篇文章中对SO_REUSEADDR有如下描述,原意是说启用SO_REUSEADDR之后,系统会将泛地址和非泛地址分开,如当一个socket绑定0.0.0.0:port时,另外一个socket可以成功绑定本地地址192.168.0.1:port ,但在本次测试中发现这种情况下也会失败。 With SO_REUSEADDR it will succeed, since 0.0.0.0 and 192.168.0.1 are not exactly the same address, one is a wildcard for all local addresses and the other one is a very specific local address
  • 当前cBPF格式用于在32位架构上执行JIT编译;而eBPF指令集用于在x86-64, aarch64, s390x, powerpc64, sparc64, arm32, riscv64, riscv32 架构上执行JIT编译。

参考

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-15 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 提高服务端性能的几个socket选项
    • 默认行为
      • SO_REUSEADDR
        • SO_REUSEPORT
          • SO_ATTACH_REUSEPORT_CBPF/EBPF
            • BPF相关的socket选项介绍
            • 如何使用BPF socket选项
          • 拓展
            • 参考
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档