一、linux 接受发送数据
1.接受网络数据的流程
a.网卡收到数据包,通过DMA将数据包写入内存(ringbuffer结构)
b.网卡向cpu发起硬中断,cpu收到硬中断请求,根据中断表查找中断处理函数,调用中断处理函数
c.中断处理函数将屏蔽中断,发起软中断(避免cpu频繁被网卡中断,使用软中断处理耗时操作,避免执行时间过长,导致cpu没法响应其他硬件中断)
d.内核ksoftirqd线程负责软中断处理,该线程从ringbuffer中逐个取出数据帧到sk_buff
e.从帧头取出IP协议,判断是ipv4还是ipv6,去掉帧头帧尾
f.从IP头看上一个协议是tcp还是udp,根据五元组找到socket,并将数据提取出来放到socket的接受缓冲区
g.应用程序通过系统调用将socket的接受缓冲区的数据拷贝到应用层缓冲区
2.发送网络数据的流程(tcp)
a.应用程序通过系统调用将用户数据拷贝sk_buff并放到socket的发送缓冲区(udp没有发送缓冲区)
b.网络协议栈从socket的发送缓冲区取出sk_buff,并克隆一个新的sk_buf(tcp支持丢失重传)
c.向下传递依次增加TCP/UDP头部、IP头部,帧头(MAC头部)、帧尾
d.触发软中断通知网卡驱动程序,有新的网络包需要发送
e.网卡驱动程序从发送队列依次取出sk_buff写ringbuffer(内存DMA区域,网卡读到)
f.触发网卡发送,发送成功,触发硬中断,释放sk_buff和ringbuffer内存(tcp对应的是克隆而来的,udp对应的是原始的)
g.当收到tcp报文的ack应答时,将释放原始的sk_buff
二、内核态网络缓冲区(tcp每个连接都有一个发送接受缓冲区,udp没有发送缓冲区)
1.接受缓冲区:
接受客户端的数据(生产者)
用户内核取出数据(消费者)
2.发送缓冲区:
业务逻辑产生的数据(生产者)
协议栈从缓冲区取出数据并发送(消费者)
使用缓冲区解决生产速度大于消费者的问题
read/write (tcp)
recv/send(tcp/udp都可以使用,如果第四个参数为0,与read/write一样)
WSASend/WASrecv(异步io) :先注册读写事件(每次都需要调用一次函数),准备一个用户缓冲区,向内核发送一个请求,有数据的时候内核把数据拷贝到用户缓冲区,内核发送一个通知(事件),用户可以从用户缓冲区取出数据
网络编程处理4件事
1.连接的建立
2.连接的断开
3.数据的接受
4.数据的发送
三、为什么要用户态网络缓冲区
接收时,如果收到的不是完整的数据包(拆包粘包),需要对数据进行缓存。
发送时,如果缓冲区内存不够,需要对未发送的数据进行缓存。
四、如何设计?
1.定长buffer设计
char m_cbRecvBuf[16384];
INT nResultCode=recv(m_hSocketHandle,(char *)m_cbRecvBuf+m_wRecvSize,sizeof(m_cbRecvBuf)-m_wRecvSize,0);
结构简单,易于实现
缺点:需要频繁腾挪数据 需要实现扩容缩容机制
使用场景:发送的数据小,发送频率不高
2.ringbuffer设计(逻辑上的环结构,通过取余)
逻辑上的环形 通过头尾指针实现
优点:不要腾挪数据
缺点:需要扩容缩容机制,可能造成不连续空间
//结构体定义
struct ringbuffer_s {
uint32_t size;
uint32_t tail;
uint32_t head;
uint8_t * buf;
};
static uint32_t
rb_remain(buffer_t *r) {
return r->size - r->tail + r->head;
}
int buffer_add(buffer_t *r, const void *data, uint32_t sz) {
if (sz > rb_remain(r)) {
return -1;
}
uint32_t i;
i = min(sz, r->size - (r->tail & (r->size - 1)));
memcpy(r->buf + (r->tail & (r->size - 1)), data, i);
memcpy(r->buf, data+i, sz-i);
r->tail += sz;
return 0;
}
int buffer_remove(buffer_t *r, void *data, uint32_t sz) {
assert(!rb_isempty(r));
uint32_t i;
sz = min(sz, r->tail - r->head);
i = min(sz, r->size - (r->head & (r->size - 1)));
memcpy(data, r->buf+(r->head & (r->size - 1)), i);
memcpy(data+i, r->buf, sz-i);
r->head += sz;
return sz;
}
3.chainbuffer
解决动态扩容 不挪数据