协议栈,指的是TCP/IP协议栈。linux系统中,协议栈是内核实现的。
Client发送数据给server,数据首先到达网卡,经过两步到达应用程序
1)将数据从网卡的内存copy到内核协议栈,内核协议栈对数据包进行解析;
2)应用程序通过调用recv函数,将数据从内核copy进用户空间,得到应用层的数据包。
网卡的作用,接收的时候,是将光电信号转换成数字信号;发送的时候,将数字信号转换成光电信号。
什么是用户态协议栈呢?就是将协议栈,做到应用程序。为什么要这么做呢?减少了一次数据copy的过程,绕过内核,数据可以直接从网卡copy到应用程序,对于性能会有很大的提升。
为什么要有用户态协议栈呢?是为了解决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直接接管网卡数据,可以绕过内核协议栈。我们直接在应用程序中实现协议栈,对协议进行解析,就可以获取到网络数据了。
netmap可以在github上下载,按照上面的readme编译安装,使用比较方便。
https://github.com/luigirizzo/netmap
使用netmap实现了一个简单的udp server, 运行的时候,注意要使用两块网卡,不然eth0的网卡被我们的程序接管了,ssh就无法登陆了。
#include <stdio.h>
#include <sys/poll.h>
#define NETMAP_WITH_LIBS
#include <net/netmap_user.h>
#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;
}