skynet 源码解析 message queue

本文详细介绍了Skynet消息队列的实现原理及核心函数,包括消息队列的创建、销毁、推送、弹出等操作,并对消息队列的内部结构进行了深入分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#ifndef SKYNET_MESSAGE_QUEUE_H
#define SKYNET_MESSAGE_QUEUE_H

#include <stdlib.h>
#include <stdint.h>

struct skynet_message {
	uint32_t source;		//消息源的句柄
	int session;			//	
	void * data;			//消息指针
	size_t sz;				//消息大小
};

// type is encoding in skynet_message.sz high 8bit
#define MESSAGE_TYPE_MASK (SIZE_MAX >> 8)
#define MESSAGE_TYPE_SHIFT ((sizeof(size_t)-1) * 8)

struct message_queue;

void skynet_globalmq_push(struct message_queue * queue);
struct message_queue * skynet_globalmq_pop(void);

struct message_queue * skynet_mq_create(uint32_t handle);
void skynet_mq_mark_release(struct message_queue *q);

typedef void (*message_drop)(struct skynet_message *, void *);

void skynet_mq_release(struct message_queue *q, message_drop drop_func, void *ud);
uint32_t skynet_mq_handle(struct message_queue *);

// 0 for success
int skynet_mq_pop(struct message_queue *q, struct skynet_message *message);
void skynet_mq_push(struct message_queue *q, struct skynet_message *message);

// return the length of message queue, for debug
int skynet_mq_length(struct message_queue *q);
int skynet_mq_overload(struct message_queue *q);

void skynet_mq_init();

#endif

 

#include "skynet.h"
#include "skynet_mq.h"
#include "skynet_handle.h"
#include "spinlock.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>

#define DEFAULT_QUEUE_SIZE 64
#define MAX_GLOBAL_MQ 0x10000

// 0 means mq is not in global mq.
// 1 means mq is in global mq , or the message is dispatching.

#define MQ_IN_GLOBAL 1
#define MQ_OVERLOAD 1024


//消息结构为先进先出,从tail压入数据,从head拿出数据


//  ↑   
//|head|
//|	   |
//|	   |
//|tail|
//  ↑

//https://blog.youkuaiyun.com/zxm342698145/article/details/80847301
//消息队列可以看做一个圆,分别有两个tail和head哨兵进行循环



//消息队列
struct message_queue {
	struct spinlock lock;		//消息节点锁
	uint32_t handle;			//句柄
	int cap;					//预分配大小
	int head;					//头部
	int tail;					//尾部
	int release;                    //释放标记
	int in_global;                  //是否在全局队列内
	int overload;                   //过载
	int overload_threshold;         //过载阈值
	struct skynet_message *queue;	//消息
	struct message_queue *next;		//下一个消息队列
};

//全局队列
struct global_queue {				
	struct message_queue *head;
	struct message_queue *tail;
	struct spinlock lock;			//全局队列锁
};
//消息队列
static struct global_queue *Q = NULL;


//将消息队列放入全局队列
void 
skynet_globalmq_push(struct message_queue * queue) {
	struct global_queue *q= Q;

	SPIN_LOCK(q) //上锁
	assert(queue->next == NULL);
	if(q->tail) {			//如果当前全局队列中已有消息队列就将尾部置为当前队列
		q->tail->next = queue;
		q->tail = queue;
	} else {
		q->head = q->tail = queue;//如果当前全局队列为空,则设定头尾节点
	}
	SPIN_UNLOCK(q)//解锁
}

//弹出全局队列头部节点
struct message_queue * 
skynet_globalmq_pop() {
	struct global_queue *q = Q;	
	SPIN_LOCK(q)
	//获取当前头部节点
	struct message_queue *mq = q->head;
	if(mq) {
		//将头部向下顺移一个节点
		q->head = mq->next;
		if(q->head == NULL) {	    //如果head的下一个节点为空,则将全局队列的尾部节点置为空,
			assert(mq == q->tail);	//当尾部节点不等于尾部节点触发断言
			q->tail = NULL;
		}
		mq->next = NULL;	//将我们待取出的节点的下个节点字段置为null,使其称为一个独立的节点
	}
	SPIN_UNLOCK(q)

	return mq;
}

//通过传入的句柄创建一个消息节点
struct message_queue * 
skynet_mq_create(uint32_t handle) {
	//声请一个消息节点
	struct message_queue *q = skynet_malloc(sizeof(*q));
	q->handle = handle;				//设置句柄
	q->cap = DEFAULT_QUEUE_SIZE;	//设置最大容量
	q->head = 0;					//头部哨兵,指当前头部的位置
	q->tail = 0;					//尾部哨兵,指当前尾部的位置
	SPIN_INIT(q)					//初始化锁
	// When the queue is create (always between service create and service init) ,
	// set in_global flag to avoid push it to global queue .
	// If the service init success, skynet_context_new will call skynet_mq_push to push it to global queue.
	q->in_global = MQ_IN_GLOBAL;	//设为全局节点	
	q->release = 0;					//释放标记
	q->overload = 0;				//过载
	q->overload_threshold = MQ_OVERLOAD;//过载阈值
	q->queue = skynet_malloc(sizeof(struct skynet_message) * q->cap);	//申请一个消息内存
	q->next = NULL;														//暂无下个节点

	return q;
}


//释放消息队列,从函数设计来看,函数的执行应在节点完全独立之后.
static void 
_release(struct message_queue *q) {
	assert(q->next == NULL);
	SPIN_DESTROY(q)
	skynet_free(q->queue);
	skynet_free(q);
}

//获取当前节点的句柄
uint32_t 
skynet_mq_handle(struct message_queue *q) {
	return q->handle;
}

//获取当前队列剩余容量,由于队列设计为一个圆,所以当尾节点小于头节点,要加上当前整个圆的长度
int
skynet_mq_length(struct message_queue *q) {
	int head, tail,cap;

	SPIN_LOCK(q)
	head = q->head;
	tail = q->tail;
	cap = q->cap;
	SPIN_UNLOCK(q)
	
	if (head <= tail) {
		return tail - head;
	}
	return tail + cap - head;
}

//获取当前节点超载信息,并置为0
int
skynet_mq_overload(struct message_queue *q) {
	if (q->overload) {
		int overload = q->overload;
		q->overload = 0;
		return overload;
	} 
	return 0;
}

//从一个消息队列取出一个消息,为参数返回值形式放入放入的skynet_message指针内
//返回0为取出消息成功,返回1为取出消息失败
int
skynet_mq_pop(struct message_queue *q, struct skynet_message *message) {
	int ret = 1;
	SPIN_LOCK(q)
	
	if (q->head != q->tail) {//当消息队列中有消息时
		*message = q->queue[q->head++];	//从头部开始取出消息放入传入的参数内
		ret = 0;						//取出成功
		int head = q->head;				
		int tail = q->tail;
		int cap = q->cap;
										//在取出成功后,对当前消息队列进行整理
		if (head >= cap) {				//当取出的消息超过了预分配的最大容量,则置为0
			q->head = head = 0;
		}
		int length = tail - head;		//获取当前长度
		if (length < 0) {				//
			length += cap;
		}
		while (length > q->overload_threshold) {	//当前消息队列长度大于过载阈值时,对过载大小和过载阈值进行重新计算分配
			q->overload = length;
			q->overload_threshold *= 2;
		}
	} else {
		// reset overload_threshold when queue is empty
		q->overload_threshold = MQ_OVERLOAD;
	}
	//当消息队列没有消息,则设为非全局队列
	if (ret) {
		q->in_global = 0;
	}
	
	SPIN_UNLOCK(q)
	
	return ret;
}

//扩大当前队列
static void
expand_queue(struct message_queue *q) {
	struct skynet_message *new_queue = skynet_malloc(sizeof(struct skynet_message) * q->cap * 2);
	int i;
	for (i=0;i<q->cap;i++) {
		new_queue[i] = q->queue[(q->head + i) % q->cap];
	}
	q->head = 0;
	q->tail = q->cap;
	q->cap *= 2;
	
	skynet_free(q->queue);
	q->queue = new_queue;
}

//向全局队列添加节点
void 
skynet_mq_push(struct message_queue *q, struct skynet_message *message) {
	assert(message);
	SPIN_LOCK(q)
	//向消息队列的尾部插入一个消息
	q->queue[q->tail] = *message;
	//当尾节点和预分配大小重合,等于tail回到出发点,则置为0
	if (++ q->tail >= q->cap) {
		q->tail = 0;
	}
	//当尾部位置等于头部位置,说明当前消息队列已经置满,立即进行扩容
	if (q->head == q->tail) {
		expand_queue(q);
	}
	//如果当前消息队列为非全局,则设为全局
	if (q->in_global == 0) {
		q->in_global = MQ_IN_GLOBAL;
		skynet_globalmq_push(q);
	}
	
	SPIN_UNLOCK(q)
}

//全局队列初始化
void 
skynet_mq_init() {
	struct global_queue *q = skynet_malloc(sizeof(*q));
	memset(q,0,sizeof(*q));
	SPIN_INIT(q);
	Q=q;
}

//释放消息队列
void 
skynet_mq_mark_release(struct message_queue *q) {
	SPIN_LOCK(q)
	assert(q->release == 0);
	q->release = 1;
	if (q->in_global != MQ_IN_GLOBAL) {
		skynet_globalmq_push(q);
	}
	SPIN_UNLOCK(q)
}

//对当前消息队列内的消息进行drop_func操作,执行完毕后释放当前消息队列
static void
_drop_queue(struct message_queue *q, message_drop drop_func, void *ud) {
	struct skynet_message msg;
	while(!skynet_mq_pop(q, &msg)) {
		drop_func(&msg, ud);
	}
	_release(q);
}

//检测是否需要进行释放,分别进行释放操作和全局队列添加操作,地狱或天堂
void 
skynet_mq_release(struct message_queue *q, message_drop drop_func, void *ud) {
	SPIN_LOCK(q)
	
	if (q->release) {
		SPIN_UNLOCK(q)
		_drop_queue(q, drop_func, ud);
	} else {
		skynet_globalmq_push(q);
		SPIN_UNLOCK(q)
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值