- 一、为什么用户层需要设计网络缓冲区
- 1)设计读缓冲区的必要
- 2)设计写缓冲区的必要
- 3)普通buf+pos的问题
- 4)ringbuffer(只解决了普通buf移动频繁的问题)
- (1)头文件
- (2).c文件
- 二、ringbuffer以及多线程换将下数据安全思考
- 1)1读1写->内存屏障
- 2) 多线程(多读多写->需要加锁且加内存屏障)
- 3)不需要加内存屏障的情况(加锁加了pthread_spinlock,自旋锁就不加)
- 三、libevent中event buffer的设计
- 1)libevent的chainbuffer同时解决了固定内存分配所要考虑的问题(要求:生产速度要小于消费速度)和数据移动频繁的问题
- 2)libevent怎么解决的数据移动频繁
- 使用场景举例
kFIFO、 无锁队列、网络缓冲区设计
因为TCP无法判定从内核缓冲区读出来的数据包是一个完整的数据包,得用特殊字符来分隔数据包
2)设计写缓冲区的必要如果写缓冲区数据满了(linux内核发送数据比我们往缓冲区写数据慢),写失败,那就要把数据在用户层缓存起来
3)普通buf+pos的问题1)数据移动比较频繁
2)用户层缓冲区设置的是16k(固定分配的内存,基本要求:消费者速度必须得快于生产者)
#ifndef _RINGBUFFER_H__ #define _RINGBUFFER_H__ #include(2).c文件#include #include #include #define min(lth , rth) ((lth) < (rth) ? (lth): (rth)) typedef struct { uint8_t* buf; //buffer uint32_t size; //大小 uint32_t read_pos; //读的位置,也是循环的缓冲区,从0到2^32-1 uint32_t write_pos; //写的位置 }ringbuffer_t; //接口总结:1.初始化,2.判断是否为空,3.判断满,4.释放内存 //基础接口 void rb_init(ringbuffer_t* t ,uint32_t sz); int32_t rb_is_empty(ringbuffer_t* t); int32_t rb_is_full(ringbuffer_t* t); void rb_free(ringbuffer_t* t); uint32_t rb_length(ringbuffer_t* t); uint32_t rb_reamin(ringbuffer_t* t); //读写数据接口 uint32_t rb_write(ringbuffer_t* dst_t,void* src_buf,uint32_t src_sz); uint32_t rb_read(ringbuffer_t* src_t,void* dst_buf,uint32_t dst_sz); //转换成2的n次幂 static inline uint32_t roundup_power_of_two(uint32_t sz) { if(sz < 2) return sz = 2; //5 -> 8 101 -> 1000 int i = 0; for(i = 0;sz != 0;++i) sz>>1; return 1 << i; } //判断是否是2的n次幂 static inline int32_t is_power_of_two(uint32_t sz) { if(sz < 2) return -1; //m%n = m & (n-1) //m%n = m - n * floor(m/n) return (sz & (sz - 1)) == 0; } #endif
#include "ringbuffer.h"
int32_t rb_is_empty(ringbuffer_t* t)
{
return t->read_pos == t->write_pos;
}
int32_t rb_is_full(ringbuffer_t* t)
{
//优化前
//return (t->read_pos+t->size)%t->size == t->write_pos;
//优化前提:
//1)缓冲区的大小必须是2的n次幂 (UInt3_t大小为2^32-1,范围是0到2^32 -1)
return t->size == t->write_pos - t->read_pos;
}
void rb_init(ringbuffer_t* t ,uint32_t sz)
{
t->buf = (uint8_t*)malloc(sz*sizeof(uint8_t*));
t->size = sz;
t->read_pos = t->write_pos = UINT32_MAX -2;
return;
}
void rb_free(ringbuffer_t* t)
{
if(t->buf)
{
free(t->buf);
t->buf = NULL;
}
t->read_pos = t->write_pos = t->size = 0;
return;
}
uint32_t rb_length(ringbuffer_t* t)
{
return t->write_pos - t->read_pos;
}
uint32_t rb_reamin(ringbuffer_t* t)
{
return t->size - rb_length(t);
}
uint32_t rb_write(ringbuffer_t* dst_t,void* src_buf,uint32_t src_sz)
{
if(src_sz > rb_reamin(dst_t))
return -1;
//dst_t->write_pos & (dst_t->size - 1) 把写坐标弄到这个圆环的范围之内
//看哪个剩余空间最小
//安全的拷贝
uint32_t i = min(src_sz,dst_t->size - dst_t->write_pos & (dst_t->size - 1));
//先拷贝后面的,再拷贝前面的
memcpy(dst_t->buf + (dst_t->write_pos & (dst_t->size - 1)), src_buf,i);
memcpy(dst_t->buf,src_buf + i,src_sz - i);//这里src_sz - i是负数是不会拷贝的
dst_t->write_pos += src_sz;
return src_sz;
}
uint32_t rb_read(ringbuffer_t* src_t,void* dst_buf,uint32_t dst_sz)
{
if(rb_is_empty(src_t))
return -1;
uint32_t i = 0;
dst_sz = min(dst_sz,rb_length(src_t) );
//rb_length(src_t)为实际的大小
//dst_sz为预期的大小
i = min(dst_sz,(src_t->size - src_t->read_pos & (src_t->size - 1)));
//src_t->read_pos & (src_t->size - 1))表示read_pos在size的位置
//src_t->size - src_t->read_pos & (src_t->size - 1)表示求出右边空余位置的大小
memcpy(dst_buf,src_t->buf + (src_t->read_pos & (src_t->size - 1)),i);
memcpy(dst_buf + i,src_t->buf,dst_sz - i);
src_t->read_pos +=dst_sz;
return dst_sz;
}
int main()
{
ringbuffer_t rb;
rb_init(&rb,9);
uint32_t put1 = rb_write(&rb,(void*)("mark"),4);
printf("put1 = %u,wr = %u,rd = %u,length = %un",put1,rb.write_pos,rb.read_pos,rb_length(&rb));
return 0;
}
二、ringbuffer以及多线程换将下数据安全思考
1)1读1写->内存屏障
uint32_t rb_write(ringbuffer_t* dst_t,void* src_buf,uint32_t src_sz)
{
if(src_sz > rb_reamin(dst_t))
return -1;
//内存屏障
#ifdef USE_MEMPROTECT
//确保开始移动buffer的时候,其他线程可以读到最新的read_pos
smp_mb(); //atomic_thread_fence()
#endif
//dst_t->write_pos & (dst_t->size - 1) 把写坐标弄到这个圆环的范围之内
//看哪个剩余空间最小
//安全的拷贝
uint32_t i = min(src_sz,dst_t->size - dst_t->write_pos & (dst_t->size - 1));
//先拷贝后面的,再拷贝前面的
memcpy(dst_t->buf + (dst_t->write_pos & (dst_t->size - 1)), src_buf,i);
memcpy(dst_t->buf,src_buf + i,src_sz - i);//这里src_sz - i是负数是不会拷贝的
#ifdef USE_MEMPROTECT
//确保write_pos不会被优化到前面去
smp_wmb();//放在dst_t->write_pos += src_sz;之前,确保memcpy正确拷贝
#endif
dst_t->write_pos += src_sz;
return src_sz;
}
uint32_t rb_read(ringbuffer_t* src_t,void* dst_buf,uint32_t dst_sz)
{
if(rb_is_empty(src_t))
return -1;
#ifdef USE_MEMPROTECT
smp_mb(); //atomic_thread_fence()
#endif
uint32_t i = 0;
dst_sz = min(dst_sz,rb_length(src_t) );
//rb_length(src_t)为实际的大小
//dst_sz为预期的大小
i = min(dst_sz,(src_t->size - src_t->read_pos & (src_t->size - 1)));
//src_t->read_pos & (src_t->size - 1))表示read_pos在size的位置
//src_t->size - src_t->read_pos & (src_t->size - 1)表示求出右边空余位置的大小
memcpy(dst_buf,src_t->buf + (src_t->read_pos & (src_t->size - 1)),i);
memcpy(dst_buf + i,src_t->buf,dst_sz - i);
#ifdef USE_MEMPROTECT
smp_wmb();//放在dst_t->write_pos += src_sz;之前,确保memcpy正确拷贝
#endif
src_t->read_pos +=dst_sz;
return dst_sz;
}
2) 多线程(多读多写->需要加锁且加内存屏障)
3)不需要加内存屏障的情况(加锁加了pthread_spinlock,自旋锁就不加)
三、libevent中event buffer的设计
1:24:13
https://www.bilibili.com/video/BV1ZL411M7Qz?spm_id_from=333.337.search-card.all.click
misalign表示缓冲区已读的字段,后面的数据不用移动到前面去



