代码改变世界

高性能服务框架revolver:RUDP(可靠UDP)算法详解

2015-01-28 14:09  crow!  阅读(2723)  评论(0编辑  收藏  举报

数据块定义

在RUDP模块中,所有发送的数据被定义成RUDPRecvSegment 和 RUDPSendSegment结构,其中RUDPSendSegment是发送块定义,RUDPRecvSegment 是接收块定义。如下:

//发送数据片
typedef struct tagRUDPSendSegment
{
	uint64_t	seq_;                          //块序号
	uint64_t	push_ts_;		       //进入发送队列的时刻
	uint64_t	last_send_ts_;		       //最后一次发送的时刻
	uint16_t	send_count_;		       //发送的次数
	uint8_t		data_[MAX_SEGMENT_SIZE];       //块数据	
	uint16_t	data_size_;                    //块数据长度
}RUDPSendSegment;
typedef struct tagRUDPRecvSegment
{
	uint64_t	seq_;                          //块序号
	uint8_t		data_[MAX_SEGMENT_SIZE];       //块数据
	uint16_t	data_size_;	               //块数据长度
}RUDPRecvSegment;

块的最大尺寸为MAX_SEGMENT_SIZE = 1408(不能大于MTU,一般MTU是1492)。为了加快内存分配的速度,RUDP模块中使用了对象池来保证块对象的快速申请,对象池定义:

ObjectPool<RUDPSendSegment, RUDP_SEGMENT_POOL_SIZE>	SENDSEGPOOL;
ObjectPool<RUDPRecvSegment, RUDP_SEGMENT_POOL_SIZE>	RECVSEGPOOL;

#define GAIN_SEND_SEG(seg) \
	RUDPSendSegment* seg = SENDSEGPOOL.pop_obj();\
	seg->reset()

#define RETURN_SEND_SEG(seg) \
	if(seg != NULL)\
		SENDSEGPOOL.push_obj(seg)

#define GAIN_RECV_SEG(seg) \
	RUDPRecvSegment* seg = RECVSEGPOOL.pop_obj(); \
	seg->reset()

#define RETURN_RECV_SEG(seg) \
	if(seg != NULL)\
		RECVSEGPOOL.push_obj(seg)

几个宏是作为块申请和释放的宏。以上就是块的定义介绍,更具体的只有去查看相关源代码了。

发送缓冲区

发送缓冲区,定义如下:

class RUDPSendBuffer
{
public:
	...
	//发送数据接口
	int32_t				send(const uint8_t* data, int32_t data_size);
	//ACK处理
	void				on_ack(uint64_t ack_seq);
	//NACK处理
	void				on_nack(uint64_t base_seq, const LossIDArray& loss_ids);
	//定时器接口
	void				on_timer(uint64_t now_ts);
	//检查BUFFER是否可以写入数据
	void				check_buffer();
	...

public:
	uint64_t			get_buffer_seq() {return buffer_seq_;};
	//设置NAGLE算法	
	void				set_nagle(bool nagle = true){nagle_ = nagle;};
	bool				get_nagle() const {return nagle_;};
	//设置发送缓冲区的大小
	void				set_buffer_size(int32_t buffer_size){buffer_size_ = buffer_size;};
	int32_t				get_buffer_size() const {return buffer_size_;};
	...

protected:
	IRUDPNetChannel*	net_channel_;

	//正在发送的数据片
	SendWindowMap		send_window_;
	//正在发送的报文的丢包集合
	LossIDSet			loss_set_;
	//等待发送的数据片
	SendDataList		send_data_;

	//发送缓冲区的大小
	int32_t				buffer_size_;
	//当前缓冲数据的大小
	int32_t				buffer_data_size_;
	//当前BUFFER中最大的SEQ
	uint64_t			buffer_seq_;
	//当前WINDOW中最大的SEQ
	uint64_t			cwnd_max_seq_;
	//接收端最大的SEQ
	uint64_t			dest_max_seq_;
	//速度控制器
	RUDPCCCObject*		ccc_;
	//是否启动NAGLE算法
	bool				nagle_;
}

其中send函数是数据写入函数,在这个函数里面,缓冲区对象先会对写入的数据进行报文拼接成发送块,让发送数据尽量接近MAX_SEGMENT_SIZE,如果发送的数据大于MAX_SEGMENT_SIZE,也会进行MAX_SEGMENT_SIZE为单元的分片。然后写入到对应的发送缓冲列表send_data_当中。最后尝试进行网络发送。伪代码如下:

int32_t RUDPSendBuffer::send(const uint8_t* data, int32_t data_size)
{
	int32_t copy_pos = 0;
	int32_t copy_size = 0;
	uint8_t* pos = (uint8_t *)data;
	uint64_t now_timer = CBaseTimeValue::get_time_value().msec();

	if(!send_data_.empty()) //拼接报文,让其接近MAX_SEGMENT_SIZE
	{
		//取出send_data_中的最后一片,如果它没有达到MAX_SEGMENT_SIZE,数据追加到MAX_SEGMENT_SIZE大小为止。
		RUDPSendSegment* last_seg = send_data_.back();
		if(last_seg != NULL && last_seg->data_size_ < MAX_SEGMENT_SIZE)
		{
			copy_size = MAX_SEGMENT_SIZE - last_seg->data_size_;
			if( copy_size > data_size) 
				copy_size = data_size;

			memcpy(last_seg->data_ + last_seg->data_size_, pos, copy_size);

			copy_pos += copy_size;
			pos += copy_size;
			last_seg->data_size_ += copy_size;
		}
	}

	//剩余数据分成MAX_SEGMENT_SIZE为单位的若干分片
	while(copy_pos < data_size)
	{
		GAIN_SEND_SEG(last_seg);

		//设置初始化的的时刻
		last_seg->push_ts_ = now_timer; //记录压入时间戳
		last_seg->seq_ = buffer_seq_;
		buffer_seq_ ++;

		//确定拷贝的块长度
		copy_size = (data_size - copy_pos);
		if(copy_size > MAX_SEGMENT_SIZE)
			copy_size = MAX_SEGMENT_SIZE;

		memcpy(last_seg->data_, pos, copy_size);

		copy_pos += copy_size;
		pos += copy_size;
		last_seg->data_size_ = copy_size;
		//压入发送队列
		send_data_.push_back(last_seg);
	}

	//记录缓冲区的数据长度
	buffer_data_size_ += copy_pos;

	//尝试发送,立即发送
	attempt_send(now_timer);

	return copy_pos;
}

这里会触发attempt_send()函数。这个函数是尝试发送的核心函数。在后面的几个过程里面也会调用到这个函数。以上就是发送函数的过程。