協(xié)議棧,指的是TCP/IP協(xié)議棧。linux系統(tǒng)中,協(xié)議棧是內(nèi)核實現(xiàn)的。
Client發(fā)送數(shù)據(jù)給server,數(shù)據(jù)首先到達網(wǎng)卡,經(jīng)過兩步到達應用程序
1)將數(shù)據(jù)從網(wǎng)卡的內(nèi)存copy到內(nèi)核協(xié)議棧,內(nèi)核協(xié)議棧對數(shù)據(jù)包進行解析;
2)應用程序通過調(diào)用recv函數(shù),將數(shù)據(jù)從內(nèi)核copy進用戶空間,得到應用層的數(shù)據(jù)包。
網(wǎng)卡的作用,接收的時候,是將光電信號轉(zhuǎn)換成數(shù)字信號;發(fā)送的時候,將數(shù)字信號轉(zhuǎn)換成光電信號。
什么是用戶態(tài)協(xié)議棧呢?就是將協(xié)議棧,做到應用程序。為什么要這么做呢?減少了一次數(shù)據(jù)copy的過程,繞過內(nèi)核,數(shù)據(jù)可以直接從網(wǎng)卡copy到應用程序,對于性能會有很大的提升。

為什么要有用戶態(tài)協(xié)議棧呢?是為了解決C10M的問題。
之前說過C10K的問題,使用epoll可以解決C10K的問題?,F(xiàn)在epoll已經(jīng)可以支持兩三百萬的并發(fā)了。
什么是C10M問題?
實現(xiàn)10M(即1千萬)的并發(fā)連接挑戰(zhàn)意味著什么:(網(wǎng)上找的)
1)1千萬的并發(fā)連接數(shù);
2)100萬個連接/秒:每個連接以這個速率持續(xù)約10秒;
3)10GB/秒的連接:快速連接到互聯(lián)網(wǎng);
4)1千萬個數(shù)據(jù)包/秒:據(jù)估計目前的服務器每秒處理50K數(shù)據(jù)包,以后會更多;
5)10微秒的延遲:可擴展服務器也許可以處理這個規(guī)模(但延遲可能會飆升);
6)10微秒的抖動:限制最大延遲;
7)并發(fā)10核技術:軟件應支持更多核的服務器(通常情況下,軟件能輕松擴展到四核,服務器可以擴展到更多核,因此需要重寫軟件,以支持更多核的服務器).
我們來計算一下,單機承載1000萬連接,需要的硬件資源:
內(nèi)存:1個連接,大概需要4k recvbuffer,4k sendbuffer,一共需要10M * 8k = 80G
CPU:10M 除以 50K = 200核
只是支持這么多連接,還沒有做其他事情,就需要這么多的資源,如果在加上其他的限制,加上業(yè)務的處理,資源肯定會更多。使用用戶態(tài)協(xié)議棧,可以減少一次數(shù)據(jù)的copy,可以節(jié)省很大一部分資源。
要實現(xiàn)用戶態(tài)協(xié)議棧,很關鍵的一個問題,是網(wǎng)絡數(shù)據(jù)怎么才能繞過內(nèi)核,直接到達用戶空間?netmap、dpdk為用戶態(tài)協(xié)議棧的實現(xiàn),提供了可能。
這次我們使用了netmap實現(xiàn)用戶態(tài)協(xié)議棧,后面會介紹dpdk。
netmap主要利用了mmap,將網(wǎng)卡中數(shù)據(jù),直接映射到內(nèi)存。netmap直接接管網(wǎng)卡數(shù)據(jù),可以繞過內(nèi)核協(xié)議棧。我們直接在應用程序中實現(xiàn)協(xié)議棧,對協(xié)議進行解析,就可以獲取到網(wǎng)絡數(shù)據(jù)了。

netmap可以在github上下載,按照上面的readme編譯安裝,使用比較方便。
https://github.com/luigirizzo/netmap
使用netmap實現(xiàn)了一個簡單的udp server, 運行的時候,注意要使用兩塊網(wǎng)卡,不然eth0的網(wǎng)卡被我們的程序接管了,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;
}