linux网络编程--Circular Buffer(Ring Buffer) 环形缓冲区的设计与实现【转】
转自:https://blog.csdn.net/yusiguyuan/article/details/18368095
1. 应用场景
网络编程中有这样一种场景:需要应用程序代码一边从TCP/IP协议栈接收数据(reading data from socket),一边解析接收的数据。具体场景例如:用户点击Youtube或优酷网站上的视频内容,这时用户PC上的播放软件就是一边接收数据一边对数据进行解码并播放的。这样的场景的存在如下约束:
1. 必须边接收数据,边对数据进行解析,不能等待到数据全部接收完整后才解析(用户等待的时间与体验成反比)。
2. 数据为流式数据(如TCP承载),需对接收到的数据进行定界分析,将数据转化为可被应用程序解析的结构化数据。
3. 数据的解析需要兼顾性能和内存空间的利用效率(如果减少内存拷贝,分配适当大小的缓存空间)。
本文将设计一个适合上述场景的环形缓冲组件,提供方便的数据缓存与读取接口,让编码专注于数据解析的逻辑,而不是将过多的精力消耗在缓冲区本身的处理上。本文讨论POSIX的一种优化的环形缓冲实现方式,并提出了进一步优化:
1. 高效的数据写入与读取接口,如应用程序可能对某段数据不感兴趣,则可将其直接忽略掉。
2. 封装了常见的整形数据读取接口,解析程序可以直接读数1~4字节的整形数据。
- #ifndef _CIRCULAR_BUFFER_H
- #define _CIRCULAR_BUFFER_H
- typedef struct CircularBuffer {
- void *ptr;
- /* 必须为整数倍内存页面大小*/
- unsigned long count;
- unsigned long read_offset;
- unsigned long write_offset;
- } CircularBuffer;
- /* 创建环形缓冲区 */
- CircularBuffer *cbCreate(unsigned long order);
- /* 销毁环形缓冲区 */
- void cbFree(CircularBuffer *cb);
- /* 重置缓冲区,使之可用于新的业务数据缓存 */
- void cbClear(CircularBuffer *cb);
- int cbIsEmpty(CircularBuffer *cb);
- unsigned long cbUsedSpaceSize(CircularBuffer *cb);
- unsigned long cbFreeSpaceSize(CircularBuffer *cb);
- /* 向环形缓冲写入len 字节数据 */
- unsigned long cbPushBuffer(CircularBuffer *cb, void *buffer, unsigned long len);
- /* 从环形缓冲读取len字节存放到buffer中,
- buffer可以为NULL,忽略len字节的数据*/
- void *cbReadBuffer(CircularBuffer *cb, void *buffer, unsigned long len);
- /* 从环形缓冲区读取1个字节 */
- unsigned char cbReadUINT8(CircularBuffer *cb);
- /* 从环形缓冲区读取1个短整形数 */
- unsigned short cbReadUINT16(CircularBuffer *cb);
- short cbReadSINT16(CircularBuffer *cb);
- unsigned int cbReadUINT24(CircularBuffer *cb);
- int cbReadSINT24(CircularBuffer *cb);
- unsigned int cbReadUINT32(CircularBuffer *cb);
- int cbReadSINT32(CircularBuffer *cb);
- #endif
cbCreate接口创建并初始化一个环形缓冲区,实现如下:
- CircularBuffer *cbCreate(unsigned long order)
- {
- int fd = 0, status = 0;
- void *address = NULL;
- char path[] = "/dev/shm/circular_buffer_XXXXXX";
- CircularBuffer *cb = (CircularBuffer *)malloc(sizeof(CircularBuffer));
- if (NULL == cb) {
- return NULL;
- }
- order = (order <= 12 ? 12 : order);
- cb->count = 1UL << order;
- cb->read_offset = 0;
- cb->write_offset = 0;
- /* 分配2倍指定的缓冲空间 */
- cb->ptr = mmap(NULL, cb->count << 1, PROT_NONE, MAP_ANONYMOUS |MAP_PRIVATE, -1, 0);
- if (MAP_FAILED == cb->ptr) {
- abort(); |
- }
- /* 根据path模块创建一个唯一的临时文件 */
- fd = mkstemp(path);
- if (0 > fd) {
- abort();
- }
- /* 删除文件访问的目录入口,进程仍可使用该文件 */
- status = unlink(path);
- if (0 != status) {
- abort();
- }
- /* 将文件大小精确指定为count字节 */
- status = ftruncate(fd, cb->count);
- if (0 != status) {
- abort();
- }
- /* 将[ cb->ptr, cb->ptr + cb->count)地址空间映射到临时文件*/
- address = mmap(cb->ptr, cb->count, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0);
- if (address != cb->ptr) {
- abort();
- }
- /* 将[ cb->ptr + cb->count, cb->ptr + 2 * cb->count)地址空间映射到临时文件*/
- address = mmap(cb->ptr + cb->count, cb->count, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0);
- if (address != cb->ptr + cb->count) {
- abort();
- }
- status = close(fd);
- if (0 != status) {
- abort();
- }
- return cb;
- }
该实现采用了一种精妙的处理方式,用2倍的缓存空间简化数据的读写操作。
第1个mmap采用私有匿名的方式分配了一块为指定缓冲区大小2倍的内存空间;第2个mmap将mkstemp创建的临时文件映射到[ptr,
ptr + count)地址,第3个mmap将mkstemp创建的临时文件映射到[ptr + count, ptr + 2 *
count)地址,这样对ptr[i]的读写操作将等同于对ptr[i + count]的读写操作,从而达到简化了环形缓冲区对于数据回绕的逻辑。
如下代码为读写环形缓冲区及计算缓冲区已使用空间大小的例程。cbUsedSpaceSize函数可用于cbIsEmpty及cbFreeSpaceSize函数的实现。cbReadBuffer函数则可用于实现cbReadUINT8、cbReadUINT16、cbReadSINT16、cbReadUINT24、cbReadSINT24、cbReadUINT32及cbReadSINT32。cbReadBuffer函数的buffer参数若传人为空,则忽略len指定长度字节的数据。
- unsigned long cbPushBuffer(CircularBuffer *cb, void *buffer, unsigned long len)
- {
- unsigned long write_offset = cb->write_offset;
- cb->write_offset += len;
- memmove(cb->ptr + write_offset, buffer, len);
- return len;
- }
- void *cbReadBuffer(CircularBuffer *cb, void *buffer, unsigned long len)
- {
- void *address = NULL;
- /* 忽略len字节数据 */
- if (NULL != buffer) {
- address = memmove(buffer, cb->ptr + cb->read_offset, len);
- }
- cb->read_offset += len;
- if (cb->read_offset > cb->count) {
- cb->read_offset -= cb->count;
- cb->write_offset -= cb->count;
- }
- return address;
- }
- unsigned long cbUsedSpaceSize(CircularBuffer *cb)
- {
- return cb->write_offset - cb->read_offset;
- }
3. 分析与讨论
1. 环形缓冲区特别适合于FIFO类型数据的处理,利用它可以不拷贝内存完成缓冲上数据的解析,提高数据解析效率。
2. 若数据读取函数采用单字节读、取模数计算偏移的方式,则可能带来性能上的损耗,该问题可以通过增加判断或以做位运算等机制来解决,但同时也增加了实现逻辑的复杂度。
3. 其不足之处在于需要预先估计数据缓冲的大小,并分配比预估大小大一个数量级的缓存空间。一种可能的解决办法是增加检测机制,若发现缓冲太小,则动态调大缓冲的大小,但这同时又可能导致频繁的调整内存大小,带来性能的下降。
(总结:根绝这样写的ringbuf 确实有点麻烦,暂时还没有体会其中的要义,自己也尝试着写一个简单的ringbuf,就是往这里放东西,然后取定长数据)
linux网络编程--Circular Buffer(Ring Buffer) 环形缓冲区的设计与实现【转】的更多相关文章
- Linux 网络编程(IO模型)
针对linux 操作系统的5类IO模型,阻塞式.非阻塞式.多路复用.信号驱动和异步IO进行整理,参考<linux网络编程>及相关网络资料. 阻塞模式 在socket编程(如下图)中调用如下 ...
- linux网络编程_1
本文属于转载,稍有改动,以利于学习. (一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个 ...
- Linux网络编程入门 (转载)
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- [转] - Linux网络编程 -- 网络知识介绍
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- 【转】Linux网络编程入门
(一)Linux网络编程--网络知识介绍 Linux网络编程--网络知识介绍客户端和服务端 网络程序和普通的程序有一个最大的区别是网络程序是由两个部分组成的--客户端和服务器端. 客户 ...
- 《转》Linux网络编程入门
原地址:http://www.cnblogs.com/duzouzhe/archive/2009/06/19/1506699.html (一)Linux网络编程--网络知识介绍 Linux网络编程-- ...
- Linux 高性能服务器编程——Linux网络编程基础API
问题聚焦: 这节介绍的不仅是网络编程的几个API 更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系. 这节主要介绍三个方面的内容:套接字(so ...
- linux网络编程基础--(转自网络)
转自 http://www.cnblogs.com/MyLove-Summer/p/5215287.html Linux下的网络编程指的是socket套接字编程,入门比较简单. 1. socket套接 ...
- Linux网络编程socket选项之SO_LINGER,SO_REUSEADDR
from http://blog.csdn.net/feiyinzilgd/article/details/5894300 Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO ...
随机推荐
- 11th 本周工作量及进度统计
本周PSP: C(类别) C(内容) S(开始时间) ST(结束时间) I(中断时间) T(实际时间) 文档 11月30日 回顾5个问题 13:00 13:50 2 48 11月30日 如果重新来过 ...
- 转 kvm、qemu-kvm、ibvirt及openstack,之间的关系
KVM是最底层的hypervisor,它是用来模拟CPU的运行,它缺少了对network和周边I/O的支持,所以我们是没法直接用它的. QEMU-KVM就是一个完整的模拟器,它是构建基于KVM上面的, ...
- USACO 2012 December ZQUOJ 24122 Scrambled Letters(二分)
题意:有一个字典序名单,现在把这些名单的顺序和名字的字符顺序扰乱了,要输出原先的名字在原来的名单中的最低和最高位置. 分析:先将所有的名字串按字典序从小到大和从大到小分别排序smin[]和smax[] ...
- 【bzoj1089】严格n元树
Description 如果一棵树的所有非叶节点都恰好有n个儿子,那么我们称它为严格n元树.如果该树中最底层的节点深度为d(根的深度为0),那么我们称它为一棵深度为d的严格n元树.例如,深度为2的严格 ...
- 循环取月的三位英语名 Jan Feb
CultureInfo ci = new CultureInfo("en-US"); DateTime now = DateTime.Now; for (int i = 0; i ...
- 结合NTLM中继和Kerberos委派攻击AD
0x00 前言 在上个月我深入演讲了无约束委派之后,本文将讨论一种不同类型的Kerberos委派:基于资源的约束委派.本文的内容基于Elad Shamir的Kerberos研究,并结合我自己的NTLM ...
- IO编程(3)-序列化
序列化 在程序运行的过程中,所有的变量都是在内存中,比如,定义一个dict: d = dict(name='Bob', age=20, score=88) 可以随时修改变量,比如把name改成'Bil ...
- PostgreSQL(一)教程 -----SQL语言
一.概念 PostgreSQL是一种关系型数据库管理系统 (RDBMS).这意味着它是一种用于管理存储在关系中的数据的系统.关系实际上是表的数学术语. 今天,把数据存储在表里的概念已经快成了固有的常识 ...
- 【UVA1401】Remember the Word Trie+dp
题目大意:给定一个字符串和一个字符串集合,问从集合中选出若干个串组成给定母串的不同方案数. 题解:有些类似于背包问题.状态很好表示,为:\(dp[i]\) 表示母串前 i 个字符的不同方案数,因此,有 ...
- Oracle 11g DRCP配置与使用
Oracle 11g DRCP配置与使用Oracle 11g推出了驻留连接池(Database Resident Connection Pool)特性,提供了数据库层面上的连接池管理机制,为应对高并发 ...