我觉得refine比refactor的幅度大,refactor主要是等价替换,而refine有时候直接寻找更好的方案。

Server在管理Client的BufferTime,即决定什么时候应该发包的这部分逻辑,最初是这么写的:

u_int32_t Connection::PerformIO(){u_int32_t ret = PErrorCode::Success;ChunkPacketFarm* cfarm = rtmp.GetChunkFarm();MessagePacketFarm* mfarm = rtmp.GetMessageFarm();// buffer is full, async write will flush it.if(!cfarm->CanWrite()){return ret;}int client_buffer_time = 10 * 1000;// if stream farm is NULL, initialize it and do metadata.if(stream_farm == NULL){if((ret = EnQueueMetaData()) != PErrorCode::Success){return ret;}// enqueue the pre-load data ASAP.int64_t current_time = fire_time = client_start_time = Utility::GetCurrentTime();if((ret = EnQueueMessages(current_time, client_buffer_time)) != PErrorCode::Success){return ret;}// the abs time used to sync with client.fire_time = Utility::GetCurrentTime() + client_buffer_time;Singleton::Timer()->AddEvent(TimerEvent::EventIO, this, 0);}// if we are transfering audio/video, do itelse{// we have changed the original position of current time.int64_t current_time = Utility::GetCurrentTime() + client_buffer_time;if(fire_time <= current_time){// TODO: analysis the right queue size.int queue_interval_time = 10000;if((ret = EnQueueMessages(current_time, queue_interval_time)) != PErrorCode::Success){return ret;}}Singleton::Timer()->AddEvent(TimerEvent::EventIO, this, fire_time - client_buffer_time);}// when enqueue messages, send out all of them.bool all_sent_out = false;while(!all_sent_out){if((ret = mfarm->SendMessage(all_sent_out)) != PErrorCode::Success){return ret;}}return ret;
}u_int32_t Connection::EnQueueMessages(int64_t current_time, int buffer_time){u_int32_t ret = PErrorCode::Success;Message* msg = NULL;FLVTag* tag = NULL;fire_time = current_time + buffer_time;for(;;){if((ret = EnQueueTag(&tag, &msg)) != PErrorCode::Success){return ret;}if(tag != NULL && tag->tag_header.timestamp + client_start_time >= fire_time){break;}}return ret;
}

即第一次我们先发送10秒的包,让client能减少延迟。然后每隔10秒发送一次包。很不直观,而且很麻烦,refine如下:

u_int32_t Connection::PerformIO(){u_int32_t ret = PErrorCode::Success;ChunkPacketFarm* cfarm = rtmp.GetChunkFarm();MessagePacketFarm* mfarm = rtmp.GetMessageFarm();// buffer is full, wait for async write flush it.if(!cfarm->CanWrite()){return ret;}// if stream farm is NULL, initialize it and do metadata.if(stream_farm == NULL){if((ret = EnQueueMetaData()) != PErrorCode::Success){return ret;}// to notify the buffer time manager we begin stream.buffer_time.StreamBegin();}// enqueue the pakckets ASAP.if((ret = EnQueueMessages()) != PErrorCode::Success){return ret;}Singleton::Timer()->AddEvent(TimerEvent::EventIO, this, buffer_time.GetFireTime());// when enqueue messages, send out all of them.bool all_sent_out = false;while(!all_sent_out){if((ret = mfarm->SendMessage(all_sent_out)) != PErrorCode::Success){return ret;}}return ret;
}u_int32_t Connection::EnQueueMessages(){u_int32_t ret = PErrorCode::Success;Message* msg = NULL;FLVTag* tag = NULL;int64_t timestamp = buffer_time.GetTimesamp();if(timestamp == -1){return ret;}for(;;){if((ret = EnQueueTag(&tag, &msg)) != PErrorCode::Success){return ret;}if(tag != NULL && msg != NULL && tag->tag_header.timestamp > timestamp){break;}}return ret;
}void BufferTimeManager::StreamBegin(){client_start_time = Utility::GetCurrentTime();
}int64_t BufferTimeManager::GetFireTime(){return fire_time;
}int64_t BufferTimeManager::GetTimesamp(){int64_t current_time = Utility::GetCurrentTime();// we are not ready to enqueue more messages.if(current_time < fire_time){return -1;}// move fire time to next position.// the fire time and current time is used for timer_farm to calc the event active time.fire_time = current_time + pulse_time;// while the preload time is used to send the messages ASAP.    int preload_time = client_buffer_time + pulse_time;return current_time - client_start_time + preload_time;
}

我们extract出了一个新类BufferTimeManager,第一次发送流时告诉它记录当前时间,然后就EnQueueMessage(它从BufferTimeManager获取当前最大的时间戳GetTimestamp),发送完毕后下次发包的时间是GetFireTime。

Refine之前,很难理解在做什么;Refine之后,有点像自然语言,把buffertime聚集在一起,抽象出来时就会迫使我们去分析到底buffertime是什么,提供什么样的API,迫使代码接近逻辑上的本来抽象。

后来始终觉得这个缓冲区算法太复杂,随着功能增加,这个算法变成如下这样样子:

int64_t GetTimesamp(){int64_t current_time = Utility::GetCurrentTime();// we are not ready to enqueue more messages.if(current_time < fire_time){return -1;}// pause or low bandwidth delay.// only when event_abs_time is 0, that is the event is active by async write,// we think there exists a delay of socket buffer full.if((client_pauseraw || client_paused) || (fire_time > 0 && has_delay)){delay_time += current_time - fire_time;}// move fire time to next position.// the fire time and current time is used for timer_farm to calc the event active time.fire_time = current_time + pulse_time;// if paused, we return not-ready(-1) when we update the fire_time.if(client_paused){delay_time += pulse_time; // the pulse time is the message time, we consume it when pause.RtmpTrace("pulse time coming, client is paused. "\"pulse=%d, delay=%ld, buffer=%d, seek=%ld, current=%ld, fire=%ld", pulse_time, delay_time, client_buffer_time, seek_time, current_time, fire_time);return -1;}// main message timestamp algorithmint preload_time = client_buffer_time + pulse_time;int64_t absolute_auto_growth_time = current_time - client_start_time;int64_t preload_cache_time = absolute_auto_growth_time + preload_time;int64_t conti_delay_time = preload_cache_time - delay_time;int64_t message_time = seek_time + conti_delay_time;RtmpTrace("client=%d buffer time manager report. "\"message=%ld, seek=%ld, current=%ld, start=%ld, buffer=%d, pulse=%d, delay=%ld, fire=%ld", connection->GetFD(), message_time, seek_time, current_time, client_start_time, client_buffer_time, pulse_time, delay_time, fire_time);return message_time;
}

揪心啊,这么复杂的东西,别人实在很难看懂,我不能写这样的东西。以真实数据看看这个算法在做什么:

发现其实每次返回的值是一个序列!马上灵光一闪,简化算法如下:

int64_t GetTimesamp(){int64_t current_time = Utility::GetCurrentTime();// we are not ready to enqueue more messages.if(current_time < fire_time){return -1;}// move fire time to next position.// the fire time and current time is used for timer_farm to calc the event active time.fire_time = current_time + pulse_time;// only not paused, we consume a pulse time of messages.if(!client_paused){current_message_time += pulse_time;}RtmpTrace("client=%d buffer time manager report. "\"message=%ld, current=%ld, buffer=%d, pulse=%d, fire=%ld", connection->GetFD(), current_message_time, current_time, client_buffer_time, pulse_time, fire_time);return current_message_time;
}

恩,这个很简单,但和实际的场景一致。异步socket在没有发完数据之前都会继续发送,每次进到这个函数时,说明数据发完了,不管延迟还是什么的,都是发完了,然后再发一个pulse的包,其实就可以了。

再进一步,发觉if(paused)其实可以换算为speed,当paused时speed为0,正常时为1.0,pauseraw时加速为2.0,然后加速后需要一个衰减,改进如下:

#define FasterPulse 1000
#define NormalPulse 5000#define FasterSpeed 2.0
#define NormalSpeed 1.0
#define ZeroSpeed 0int64_t BufferTimeManager::GetCurrentTime(){int64_t current_time = Utility::GetCurrentTime();// we are not ready to enqueue more messages.if(current_time < fire_time){return -1;}// move fire time to next position.// the fire time and current time is used for timer_farm to calc the event active time.fire_time = current_time + pulse_time;// we can control the buffer by the speed. for example:// pauseraw: write faster, speed > 1// paused: write paused, speed = 0.message_time += pulse_time * speed;// if high speed, use attenuation to slow it down.if(speed > NormalSpeed){speed -= Attenuation;speed = RtmpMax(NormalSpeed, speed);}RtmpTrace("client=%d paused=%d buffer time manager report. "\"message=%ld, current=%ld, buffer=%d, pulse=%d, speed=%0.1f, fire=%ld delay=%ld", connection->GetFD(), client_paused, message_time, current_time, buffer_time, pulse_time, speed, fire_time, current_time - start_time - message_time + buffer_time + pulse_time);return message_time;
}

这样,控制speed就能控制发包(填充缓冲区)速度,控制pulse就能控制发包频率,控制attenuation能控制高速时的速度衰减。简化的原因是pulse和message的time没有关系,不应该关联到一起。简化过后这样的代码是可读的。

pulse:脉冲,发包的频率。
speed:速度,发包的速度。
attenuation:衰减,高速时的速度衰减。

一个refine/refactor的例子相关推荐

  1. stackoverflow上一个最会举例子的专家

    https://stackoverflow.com/ Premraj是stackoverflow上一个一个最会举例子的专家,我特意收集了他的一些有趣的举例: Java弱引用最精彩的解释 https:/ ...

  2. Python网络爬虫 - 一个简单的爬虫例子

    下面我们创建一个真正的爬虫例子 爬取我的博客园个人主页首页的推荐文章列表和地址 scrape_home_articles.py from urllib.request import urlopen f ...

  3. mvc登录实例 mysql_spring mvc + mybatis + mysql 调整的一个简单的登录例子

    spring mvc + mybatis + mysql 整合的一个简单的登录例子 今天用spring跟mybatis整合写了一个简单的登录例子,第一次整合,给自己做个笔记,可能注释写的有点少,做的不 ...

  4. .NET(C#) Internals: 以一个数组填充的例子初步了解.NET 4.0中的并行(二)

    引言 随着CPU多核的普及,编程时充分利用这个特性越显重要.上篇首先用传统的嵌套循环进行数组填充,然后用.NET 4.0中的System.Threading.Tasks提供的Parallel Clas ...

  5. .NET(C#) Internals: 以一个数组填充的例子初步了解.NET 4.0中的并行(一)

    引言 随着CPU多核的普及,编程时充分利用这个特性越显重要.本文首先用传统的嵌套循环进行数组填充,然后用.NET 4.0中的System.Threading.Tasks提供的Parallel Clas ...

  6. PHP服务器脚本实例,Shell脚本实现的一个简易Web服务器例子分享_linux shell

    这篇文章主要介绍了Shell脚本实现的一个简易Web服务器例子分享,本文实现的Web服务器非常简单实用,可以在你不想安装nginx.apache等大型WEB服务器时使用,需要的朋友可以参考下 假设你想 ...

  7. boost::math模块使用 non_finite_num facet 的一个非常简单的例子

    boost::math模块使用 non_finite_num facet 的一个非常简单的例子 实现功能 C++实现代码 实现功能 boost::math模块使用 non_finite_num fac ...

  8. boost::math模块使用词法转换的一个非常简单的例子的测试程序

    boost::math模块使用词法转换的一个非常简单的例子的测试程序 实现功能 C++实现代码 实现功能 boost::math模块使用词法转换的一个非常简单的例子的测试程序 C++实现代码 #inc ...

  9. boost::log模块实现一个简单日志的例子

    boost::log模块实现一个简单日志的例子 实现功能 C++实现代码 实现功能 boost::log模块实现一个简单日志的例子 C++实现代码 #include <boost/log/tri ...

最新文章

  1. 基于OpenCV提取特定区域
  2. 困扰数学家25年的“切苹果”难题,被一位华人统计学博士解决了
  3. 缅甸、老挝出入证可在西双版纳办
  4. Linux 修改系统编码
  5. scala学习-Description Resource Path Location Type value toDF is not a member of org.apache.spark.rdd.R
  6. 手把手教你如何做门店盈亏平衡分析图表,内附模板可直接套用
  7. 虚拟机环境下Centos6.5如何上网
  8. 产品读书《About Face 3交互设计精髓》
  9. 泛泰A870 CWM Recovery En/Cn合集
  10. AWS Device Shadow使用
  11. 计算机网络常见面试题目
  12. 粗糙集(Rough Sets)
  13. 中国电子与IBM携手构建健康云平台;微软推3款机器学习工具;【软件网每日新闻播报│第9-26期】
  14. linux鼠标箭头消失了,Ubuntu 16.04 鼠标光标消失的解决方法(右键可弹窗,可以点击)...
  15. 万兆单模模块_万兆(10G SFP+)单模光模块的介绍及应用
  16. oracle 中YYYY-MM-DD HH24:MI:SS的使用
  17. 【游戏开发实战】(完结)使用Unity制作像天天酷跑一样的跑酷游戏——第七篇:游戏界面的基础UI
  18. sun.misc.BASE64Encoder详解
  19. matlab绘图去白边
  20. layui点击按钮弹出另一个界面(增加界面),layui弹出层

热门文章

  1. docker 常用命令大全
  2. Hadoop国内镜像下载地址:极速
  3. h5 ios中软键盘弹起后 fixed定位失效
  4. NTP网络时钟同步协议对计算机网络数据的重要性
  5. 5G行业消费者洞察:这23个词最热
  6. 登录《北京市社会保险网上服务平台》的手机号销号了去哪里更改
  7. QTableView添加复选框
  8. 大数据课程体系-学习笔记概要
  9. GNU 编译C++程序
  10. 【面试/笔试】—— 数学