完整代码详见GitHub CyclicBuffer。
什么是循环缓冲区
循环缓冲区通常应用在模块与模块之间的通信,可以减少程序挂起的时间,节省内存空间。
如图所示,蓝色箭头表示读取指针,红色表示写入指针。写入指针可以在缓冲区有剩余空间时不中断地写入数据,读取指针可以在循环缓冲区有数据时不停读取。
如何设计循环缓冲区
为了方便两个进程之间的通信,我们在共享内存中创建循环缓冲区。基本原理如图:
结构体定义
typedef struct CyclicBuffer
{
uint8_t buf[CYCBUFFSIZ]; //缓冲区
uint8_t read; //读指针
uint8_t write; //写指针
uint32_t valid_size; //已写入数据数
} CyCBuf;
写入数据
void cycbuff_write(CyCBuf *cycbuff, uint8_t ch)
{
while (cycbuff_isfull(cycbuff))
;
cycbuff->buf[cycbuff->write] = ch;
cycbuff->write++;
cycbuff->write %= CYCBUFFSIZ;
cycbuff->valid_size++;
}
写入数据前,要检查缓冲区是否已满,如果已满就得挂起等待。直到缓冲区有空间再进行写入。
写入指针每次写完向后偏移一位,valid_size
记录当前缓冲区中有效数据个数。
读取数据
uint8_t cycbuff_read(CyCBuf *cycbuff)
{
uint8_t ch;
while (cycbuff_isempty(cycbuff))
;
ch = cycbuff->buf[cycbuff->read];
cycbuff->read++;
cycbuff->read %= CYCBUFFSIZ;
cycbuff->valid_size--;
return ch;
}
读取数据前,要检查缓冲区是否为空,如果为空就要挂起等待。
判断空
bool cycbuff_isempty(CyCBuf *cycbuff)
{
if (cycbuff->valid_size == 0)
return true;
return false;
}
判断满
bool cycbuff_isfull(CyCBuf *cycbuff)
{
if (cycbuff->valid_size == CYCBUFFSIZ)
return true;
return false;
}
本次实验中,为了方便期间,用valid_size
保存有效数据个数,没有用读写指针是否重合来判断,这就无需再考虑读写指针重合时,是空还是满。
数据收发流程
服务端 - 写入
void server(CyCBuf *cycbuff, SHMS *shms)
{
cycbuff_init(cycbuff);
while (1)
{
puts("Enter Message: ");
uint8_t ch[BUFFERSIZE];
fgets(ch, BUFFERSIZE, stdin);
for (size_t i = 0; ch[i] != '\n' && i < BUFFERSIZE; i++)
{
cycbuff_write(cycbuff, ch[i]);
}
cycbuff_write(cycbuff, '\n');
}
exit(0);
}
SHMS *shms
为共享内存相关数据,有关共享内存的使用可以参考进程间通信(IPC)之共享内存(SharedMemory)。
客户端 - 读取
void client(CyCBuf *cycbuff, SHMS *shms)
{
printf("Server operational: shm id is %d\n", shms->shmid);
while (1)
{
uint8_t ch;
puts("Recv Message: ");
while (1)
{
ch = cycbuff_read(cycbuff);
if (ch == '\n')
{
printf("\n");
break;
}
fflush(stdout);
printf("%c", ch);
}
}
}
读取数据以回车符为分界,当读到回车符时进行换行处理,并等待接收下一波数据。