【2.skynet c gate服务中databuffer解析(解包)】

本文根据云风博客为思路来解读databuffer在skynet老版本gate服务的应用。
源码为databuffer.h和service_gate.c
首先解释一下什么是ringbuffer
它是一个环(首尾相接的环),你可以把它用做在不同上下文(线程)间传递数据的buffer.
在这里插入图片描述

优点 它是一个数组(连续的内存),比链表快。在硬件级别,数组中的元素是会被预加载的,因此在ringbuffer当中,cpu无需时不时去主存加载数组中的下一个元素。
其次你可以为数组预先分配内存。不需要花大量时间用于垃圾回收。

ringbuffer在skynet gate服务中的应用
在旧的gate服务中,skynet启动gate服务,监听一个端口,然后该服务要处理连接到这个端口的所有TCP连接。Skynet使用了一个ringbuffer缓存了所有连接发过来的数据包。然后在一条一条数据去
进行逻辑分包。
ringbuffer的数据结构如下

struct messagepool_list {
    struct messagepool_list *next;
    struct message pool[MESSAGEPOOL]; //数组长度是1023
};
messagepoll数据绑定在gate数据上。messagepoll结构里有个messagepool_list的数组,即环形数组
struct messagepool {
    struct messagepool_list * pool;
    struct message * freelist;
};

databuffer数据是绑定在每一条tcp连接上。所以databuffer是n,messagepoll是1

struct databuffer {
    int header; // 约定的包头长度 2或者4
    int offset;
    int size; // 该条tcp连接,已经接收到数据大小。 databuffer_push 是加 datebuffer_read是减
    struct message * head; // 头指针  message数据最终是存在messagepool_list数组里
    struct message * tail; // 尾指针
}; 

datebuffer_read 是 去messagepool读指定 sz(2字节或4字节)的消息。

databuffer_read(struct databuffer *db, struct messagepool *mp, char * buffer, int sz) {
    assert(db->size >= sz); // sz 为2 或者 4
    db->size -= sz; // size是该条连接已经缓存还没读的消息大小。 现在需要读sz个大小的数据出来.
    for (;;) {
        struct message *current = db->head;
        int bsz = current->size - db->offset;
        if (bsz > sz) { // 这条message的大小已经够读了,并且还有数据多。多出来的数据说明是下一个逻辑包的数据,这里需要用offset来标记。
            memcpy(buffer, current->buffer + db->offset, sz);
            db->offset += sz;
            return;
        }
        if (bsz == sz) { // 已经读出了一个完整的包
            memcpy(buffer, current->buffer + db->offset, sz);
            db->offset = 0;
            _return_message(db, mp); // 回收该内存 db->head往下跳一格
            return;
        } else { // 还没读完,需要跳到下一个message去继续读数据。 
            memcpy(buffer, current->buffer + db->offset, bsz);
            _return_message(db, mp); // 回收该内存 db->head往下跳一格 并且标记messagepool的freelist为这个已经read的message
            db->offset = 0;
            buffer+=bsz;
            sz-=bsz;
        }
    }
}


static void
databuffer_push(struct databuffer *db, struct messagepool *mp, void *data, int sz) {
    struct message * m;
    if (mp->freelist) {
        m = mp->freelist;
        mp->freelist = m->next;
    } else { // ringbuff的数组初始化。
        struct messagepool_list * mpl = skynet_malloc(sizeof(*mpl));
        struct message * temp = mpl->pool;
        int i;
        for (i=1;i<MESSAGEPOOL;i++) {
            temp[i].buffer = NULL;
            temp[i].size = 0;
            temp[i].next = &temp[i+1];
        }
        temp[MESSAGEPOOL-1].next = NULL;//最后一个m的next是空 但freelist指针指向数组最后一个元素时。数组会进行回绕。然后第38行到41行。会把当前的
                                        //数组的元素往后移动一格。这里实现非常巧妙。
        mpl->next = mp->pool;
        mp->pool = mpl;
        m = &temp[0]; // 第一个m存储当前push进来的数据
        mp->freelist = &temp[1]; // freelist指向第二个m
    }
    m->buffer = data;
    m->size = sz;
    m->next = NULL;
    db->size += sz; // 标记databuff又接收了sz个数据。等待下次read。
    if (db->head == NULL) {
        assert(db->tail == NULL);
        db->head = db->tail = m;
    } else {
        db->tail->next = m;
        db->tail = m; // 尾指针后移
    }
}
static inline void
_return_message(struct databuffer *db, struct messagepool *mp) {
    struct message *m = db->head;
    if (m->next == NULL) {
        assert(db->tail == m);
        db->head = db->tail = NULL;
    } else {
        db->head = m->next;
    }
    skynet_free(m->buffer);
    m->buffer = NULL;
    m->size = 0;
    m->next = mp->freelist; // freelist是下个tcp进来需要存放的地址,这里db->head的message进行回收,把freelist指针指向这里。下次数据进来就会存放在这里了。
    mp->freelist = m;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值