Posted on 2008-10-27 23:25
Fox 阅读(5594)
评论(3) 编辑 收藏 引用 所属分类:
T技术碎语
本文同时发布在http://www.yulefox.com和http://www.cppblog.com/fox。
近来在Windows下用WSAEventSelect时,碰到一个棘手的问题,当然现在已经解决了。
问题描述:
一个Server,一个ClientA,一个ClientB,Server用WSAEventSelect模型监听(只有监听,没有读写),ClientA在连接Server后,ClientA对应的EventA被触发,Server的WSAWaitForMultipleEvents等待到EventA,ClientB连接Server时,TCP三次握手成功,ClientB与Server的TCP状态被置为ESTABLISHED,然而Server的WSAWaitForMultipleEvents没有等待到EventB被触发。
用netstat看了一下,ClientB与Server的状态是ESTABLISHED,此时如果ClientB退出,由于Server无法正常Close该连接,因此Server的状态不是TIME_WAIT而是CLOSE_WAIT(持续2小时),Client的状态是FIN_WAIT_2(持续10分钟)。
我尝试将ClientA主动关闭后再次连接Server,Server的WSAWaitForMultipleEvents在wait到EventA之后,EventB此时也被触发。
开始一直以为问题的根源在于WSAEventSelect的使用上,毕竟,之前没有系统写过类似的代码,难免怀疑到事件模型的使用上。多方查阅资料,最后还是没有发现类似问题的解决方案。
又跟了一上午之后,Kevin开始怀疑是多线程使用的问题,我看了一下,的确没有对event的多线程操作进行处理,但因为在另一个应用中,使用了同样的模块,却没有该问题。最后考虑必要性时还是放弃了加临界资源,无视多线程同步问题。Kevin本来劝我换个模型,但我固执的认为要做就把这事儿做好。因为下午还要回学校一趟,就想尽快搞定,毕竟因为这一块已经把Kevin的进度拖了一周了,心下还是过意不去,而且隐约感觉到离问题的解决越来越近了。
问题分析:
在对着WSAWaitForMultipleEvents思考了半天之后,忽然开窍了,如果ThreadA在WSAWaitForMultipleEvents时,只有一个EventA被WSAEventSelect并set到signaled状态,则该EventA会被wait成功,ThreadA处理EventA之后继续阻塞在WSAWaitForMultipleEvents。此时,ThreadB通过WSAEventSelect将EventB初始化为nonsignaled状态,之后即使EventB被set为signaled状态,但ThreadA的WSAWaitForMultipleEvents因为处于阻塞状态,不可能刷新事件集,也就不可能wait到EventB,最终导致了ClientB的请求无法被响应。如果EventA被触发则会被ThreadA等待到,WSAWaitForMultipleEvents返回后再次进入时事件集已经被刷新,EventB被wait到也就不难理解了。
问题解决:
说到底是因为当ThreadA阻塞在WSAWaitForMultipleEvents处之时,事件集的变更无法立即得到体现。如果允许上层应用随时create或close一些event,则WSAWaitForMultipleEvents就不应该无限阻塞下去。
因此最后的一个解决方法就是让WSAWaitForMultipleEvents超时返回并Sleep一段时间,当WSAWaitForMultipleEvents再次进入时事件集得以更新。
想了一下,另一个应用中之所以没出现该问题也只是个巧合,因为该应用中ThreadB的两次WSAEventSelect间隔很短,在ThreadA获得时间片之前已经确定了事件集。
说白了这也不是一个什么大问题,甚至谈不上任何难度,但是因为之前对WSAEventSelect没有一个清晰的概念,因此在发现和分析问题上花费了大量时间,加上在VS2005调试过程中,有个别文件更新时没有被重新编译,也耗费了很多无谓的时间,以至于我们都在考虑是不是要放弃IDE,因为我们确实太依赖IDE了,有些TX为了稳妥,每次都是“重新生成整个解决方案”,如果一个解决方案有几千个文件、几十万行的代码,估计重编一次也要花个几分钟吧。
总结:
- netstat观察的网络连接处于ESTABLISHED状态并不意味着逻辑连接被accept,只是表明客户端connect的TCP物理连接(三次握手)被服务器端ack,如果服务器没有accept到该连接,证明网络模块代码有问题;
- 多线程怎么都是个问题,线程同步尽量避免,毕竟,用Kevin的话来说,加锁是丑陋的。但在涉及到同步问题时,还是权衡一下,我这儿之所以最后没有加临界区,是因为事件主要是在ThreadA中处理,ThreadB中只有create操作,而且ThreadA对事件集的刷新要求不是那么严格,也就不考虑加临界区了;
- 如果能力和条件允许的话,放弃IDE吧,IDE的确不是个好东西,我主要是指在编译链接的时候,如果作为编辑器说不定还会好用:)。
个人网站http://www.yulefox.com用的主机最近从据说要黑屏的Windows换成了Debian,还在调整,估计明天能弄好,内容肯定比Cppblog杂的多,谈点技术的还是会同步更新到http://www.cppblog.com/fox。