这大半年主要做了一个用于视频存储的服务器端程序。程序的主要功能就是存储或回放视频流。程序不大,不到5000行。为什么这么短短的5000行程序前后我却花了大半年的时间修改调试才在近期投入商业运用?它的稳定性和性能有把握么?它在未来还需要大量修改么?带着这些疑问,回溯这大半年的开发历程,希望能找到答案。
如果下面按照时间先后流水帐的叙述方式恐怕我会有所遗漏,大家也会看得云里雾里;所以我通过例子来叙述,修改时间并不一定按照先后顺序。这些例子可能属于:一.网络通信,二.文件系统,三.多线程管理。
网络通信部分基于ACE,涉及其中的udp和组播;文件系统主要涉及磁盘管理和文件管理两方面;多线程管理是系统核心部分,也是最容易犯错误的部分。我们可以在脑中想象此系统完美工作的场景:ACE网络底层不用关心,udp用于响应命令,建立多线程加入多个组播组接收视频流数据或者建立回放线程读取文件数据流发送至需要的地方;下面文件系统发挥出色,在空闲磁盘建立合适的文件上报文件名,源源不断地写入数据,并根据时间点新建文件切换视频流继续写入新文件;多线程资源也被管理得井井有条,每个线程都遵纪守法,文明健康地访问或修改资源。好了,理想很美好,但现实往往复杂残酷得多。
我在做回放模块的时候采用的是缓存窗机制,当缓存窗超出上限值时会发出stop命令,这时我要将发送线程挂起;当缓存窗低于下限值时发出resume命令,我要将线程恢复。我首先想到的方法是采用SuspendThread 和 ResumeThread函数;有时候回放进行的很好,但有时候回放会突然中止,甚至导致整个程序其它模块挂起,此现象出现的概率不是很高。也许有些人说,会不会是线程试图从堆中分配内存,而Suspend操作时导致其它线程无法访问该堆而产生死锁呢?我也不知道,在相当长的一段时间里,我采用了各种同步办法,总是无法彻底杜绝此现象。后来,我干脆开始怀疑SuspendThread 和 ResumeThread函数频繁调用的稳定性了。俗话说:用人不疑疑人不用。大概几个月后,在我基本完善了其它模块后,我决定放弃这两个函数。我使用了一个标志bReplayFlag,当收到stop命令时将它置false,resume时置true,在回放线程中只是简单地做了判断,当标志为false时sleep,true时正常发送,问题迎刃而解,至今未再出现过死锁。从这一个问题中得到的教训是:高级的方法并不一定能解决你的问题,过时的方法用在合适的时候就能帮上大忙。正如电影《赤壁》中说的一样:没有过时的阵法,只有适合的阵法。
我去年的几篇日志里有些代码,没做任何解释,因为它们实在太简单了。其中有个简单获取磁盘空间的(这里我又做了简化):
ULONG GetFreeSpace(LPSTR driver/*如:"D:"*/)
{
ULONG Disknum;
ULONG total=0;
if (GetDiskFreeSpaceEx(driver,
(PULARGE_INTEGER)&Disknum,NULL,NULL))
{
total=Disknum/(1024*1024);//M bytes
}
return total;
}
看上去是不是很简单,的确,它也存在相当的隐患:1.ULONG是32位数;2.盘符不存在怎么处理。显然,ULONG不足以表示磁阵等TB级别的空闲空间;盘符不存在时返回0值,无法判断是无剩余空间还是磁盘不存在了。修正后:
ULONG64 GetFreeSpace(LPSTR driver/*如:"D:"*/)
{
ULONG64 Disknum;
ULONG64 total=0;
if (GetDiskFreeSpaceEx(driver,
(PULARGE_INTEGER)&Disknum,NULL,NULL))
{
total=Disknum/(1024*1024);//M bytes
}
else
total=0xFFFFFFFFFFFFFFFF;
return total;
}
这两个问题都是在商用前测试磁阵时才发现的,也就是说在我写下第一个函数后将近半年才改正了这两个隐患。当然,我实际使用的GetFreeSpace函数要比这个复杂几倍,使其满足更多需要:输入值为多个盘符;无磁盘告警等。结论:再简单的东西面对最恶劣的环境才能接受真正的考验。如果你对未来的环境无知,那就尽可能地使其强大。(未完待续)