用户态协议栈的实现

协议栈,指的是TCP/IP协议栈。linux系统中,协议栈是内核实现的。

Client发送数据给server,数据首先到达网卡,经过两步到达应用程序
1)将数据从网卡的内存copy到内核协议栈,内核协议栈对数据包进行解析;
2)应用程序通过调用recv函数,将数据从内核copy进用户空间,得到应用层的数据包。
网卡的作用,接收的时候,是将光电信号转换成数字信号;发送的时候,将数字信号转换成光电信号。

什么是用户态协议栈呢?就是将协议栈,做到应用程序。为什么要这么做呢?减少了一次数据copy的过程,绕过内核,数据可以直接从网卡copy到应用程序,对于性能会有很大的提升。


image.png

为什么要有用户态协议栈呢?是为了解决C10M的问题。

之前说过C10K的问题,使用epoll可以解决C10K的问题。现在epoll已经可以支持两三百万的并发了。
什么是C10M问题?
实现10M(即1千万)的并发连接挑战意味着什么:(网上找的)
1)1千万的并发连接数;
2)100万个连接/秒:每个连接以这个速率持续约10秒;
3)10GB/秒的连接:快速连接到互联网;
4)1千万个数据包/秒:据估计目前的服务器每秒处理50K数据包,以后会更多;
5)10微秒的延迟:可扩展服务器也许可以处理这个规模(但延迟可能会飙升);
6)10微秒的抖动:限制最大延迟;
7)并发10核技术:软件应支持更多核的服务器(通常情况下,软件能轻松扩展到四核,服务器可以扩展到更多核,因此需要重写软件,以支持更多核的服务器).

我们来计算一下,单机承载1000万连接,需要的硬件资源:
内存:1个连接,大概需要4k recvbuffer,4k sendbuffer,一共需要10M * 8k = 80G
CPU:10M 除以 50K = 200核
只是支持这么多连接,还没有做其他事情,就需要这么多的资源,如果在加上其他的限制,加上业务的处理,资源肯定会更多。使用用户态协议栈,可以减少一次数据的copy,可以节省很大一部分资源。

要实现用户态协议栈,很关键的一个问题,是网络数据怎么才能绕过内核,直接到达用户空间?netmap、dpdk为用户态协议栈的实现,提供了可能。

这次我们使用了netmap实现用户态协议栈,后面会介绍dpdk。

netmap主要利用了mmap,将网卡中数据,直接映射到内存。netmap直接接管网卡数据,可以绕过内核协议栈。我们直接在应用程序中实现协议栈,对协议进行解析,就可以获取到网络数据了。


image.png

netmap可以在github上下载,按照上面的readme编译安装,使用比较方便。
https://github.com/luigirizzo/netmap

使用netmap实现了一个简单的udp server, 运行的时候,注意要使用两块网卡,不然eth0的网卡被我们的程序接管了,ssh就无法登陆了。


#include 
#include 


#define NETMAP_WITH_LIBS

#include 


#pragma pack(1)

#define PROTO_IP        0x0800
#define PROTO_UDP       0x11


#define MAC_LEN         6

struct ethhdr {

    unsigned char h_dest[MAC_LEN]; //mac
    unsigned char h_src[MAC_LEN];
    unsigned short h_proto;

};
// sizeof(struct ethhdr) == 14

struct iphdr {

    unsigned char version:4,
                  hdrlen:4;

    unsigned char tos; //
    unsigned short length;

    unsigned short id;

    unsigned short flag:3,
                   offset:13;

    unsigned char ttl;
    unsigned char proto;

    unsigned short check;

    unsigned int sip;
    unsigned int dip;

};

// sizeof(struct ip) == 20

struct udphdr {

    unsigned short sport;
    unsigned short dport;
    unsigned short length;
    unsigned short check;

};

// sizeof(udphdr)  8


struct udppkt {

    struct ethhdr eh; // 14
    struct iphdr ip;  // 20
    struct udphdr udp; // 8

    unsigned char body[0]; // sizeof(body)=0;
    

};

// sizeof(udppkt) = 44


// netmap:eth0
// eth0
int main() {

    // eth0  --> ens33
    struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
    if (nmr == NULL) {
        return -1;
    }

    struct pollfd pfd = {0};
    pfd.fd = nmr->fd; //
    pfd.events = POLLIN;
// select/poll  or epoll
// poll --> select
    while (1) {

        int ret = poll(&pfd, 1, -1);
        if (ret < 0) continue;

        if (pfd.revents & POLLIN) {
            struct nm_pkthdr h;
            unsigned char *stream = nm_nextpkt(nmr, &h); // read

            struct ethhdr *eh = (struct ethhdr*)stream;

            // 0x0800
            if (ntohs(eh->h_proto) == PROTO_IP) {

                struct udppkt *udp = (struct udppkt *)stream;

                if (udp->ip.proto == PROTO_UDP) {

                    //
                    int udp_length = ntohs(udp->udp.length);

                    udp->body[udp_length-8] = '\0';

                    printf("udp --> %s\n", udp->body);
                    
                }

            }

        }

    }

    return 0;
}

你可能感兴趣的:(用户态协议栈的实现)