SendMessage重入问题。

来源:互联网 发布:mac手写输入法怎么退出 编辑:程序博客网 时间:2024/04/29 04:19

 

用CSocket编写程序,由于它不支持(或者说是很不方便)多线程,所以在工作线程中干完活之后,调用SendMessage通知窗口线程(CSocket所在的线程),在窗口线程将结果发送给服务器。没想到,这样一个问题让我困扰了两天。

程序写好后,进行压力测试。程序在小流量(100K)是表现正常,在大流量(1M)时会有丢包现象。由于服务器要同时接受来自4个客户端的消息,每一个消息包括3个数据包,每一个包设置为1M,客户端另起一个线程发送。用Timer控制,100ms的周期,所以理论上一秒将发送120M的数据,而且要开10个线程来分别发送这些消息,每一个发送12M。客户端显示发送正常,而服务器端丢包,情况不一。

首先,我认为是服务器接受的问题,查找了很久,没有发现什么问题。只能归咎于使用了CSocket + CSocketFile + CArchive架构,微软的东西很好用,但是口碑不好,网上诟病的人很多。但我一直感觉不是它们的问题;

后来,考虑是不是接收端的Buffer给快速的网络接收给冲掉了?还是不太相信,协议栈没这么脆弱。郁闷中......

今天上午准备暂且放弃这个问题,就随便看看客服端的代码。仔细检查了一下工作线程,发现有一些线程同步问题没有考虑。我有一个任务链表,在窗口线程中添加,在工作线程后完成任务后删除。感觉可能是这里出问题了,加上临界区,调试,问题还是依旧。

如是将线程函数一行一行的看了看,终于将目前聚焦到SendMessage函数上来。其实不用PostMessage函数,而用SendMessage函数是因为我需要在函数调用后做delete工作。SendMessage以前用过,直观的感觉是它是阻塞的,要到消息处理后在返回。所以感觉好像不需要同步,那么多个线程同时向一个窗口线程发送消息,情况会怎样?心理一惊,赶紧加上临界区,调试,Success!!

Oh,my god. Google吧,果然SendMessage调用窗口函数,是重入的!我的客户端在在测试时间之内,总共调用了30次SendMessage,重入的结果是客户端的输出缓冲被覆盖了。而客户端的SendMessage正常返回,所以我打印出已经发送的消息。CSocket也没有任何错误提示消息,所以让我误认为是服务器端的问题。

关于SendMessage的解释,再次认真学习,不可一知半解,呵呵。

1. 窗口函数(是个回调函数)的代码什么时候都可以被系统(调用者一般是user32模块)调用。比如在窗口过程中,向自己的窗口SendMessage(***); 那么执行过程是怎样的?我们知道,SendMessage是要等到消息发送并被目标窗口执行完之后才返回的。那么窗口在处理消息,然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行,程序不就互相死锁了吗?其实是不会的。Windows设计一套适合SendMessage的算法,他判断如果发送的消息是属于本线程创建的窗口的,那么直接由user32模块调用窗口函数(可能就有窗口重入),并将消息的处理结果结果返回。
NOTE: 由于窗口的可重入性。在win32 SDK程序中应尽量少用全局变量和静态变量,因为在窗口函数执行过程中可能窗口重入,如果重入后将这些变量改了,但你的程序在窗口重入返回之后继续执行,可能就是使用已经改变的全局或静态变量。在MFC中(所有窗口的窗口函数基本上都是AfxWndProc),按照类的思想进行了组织,一般变量都是类中的,好管理的多。

2. PostMessage只把消息放入队列,不管其他程序是否处理都返回,然后继续执行,这是个异步消息投放函数。而sendmessage必须等待其他程序处理消息完了之后才返回,继续执行,这是个同步消息投放函数。而且,PostMessage的返回值表示postmessage函数执行是否正确;而SendMessage的返回值表示其他程序处理消息后的返回值。这点大家应该都明白。

3. 如果在同一个线程内,PostMessage发送消息时,消息要先放入线程的消息队列,然后通过消息循环Dispatch到目标窗口。SendMessage发送消息时,系统直接调用目标窗口的消息处理程序,并将结果返回。SendMessage在同一线程中发送消息并不入线程消息队列。 如果在不同线程内, 最好用PostThreadMessage代替PostMessage, 他工作的很好。SendMessage发送消息到目标窗口所属的线程的消息队列,然后发送消息的线程等待(事实上,他应该还在做一些监测工作,比如监视QS_SENDMESSAGE标志),直到目标窗口处理完并且结果返回,发送消息的线程才继续运行。这是SendMessage的一般情况,事实上,处理过程要复杂的多。比如,当发送消息的线程监测到有别的窗口SendMessage一个消息到来时,他直接调用窗口处理过程(重入),并将处理结果返回(这个过程不需要消息循环中GetMessage等的支持)。

4. 如果发送的消息码在WM_USER之下(非自定义消息)且消息参数中带有指针,那么PostMessage,SendNotifyMessage,SendMessageCallback这些异步消息发送函数将会调用失败。 最好不要用PostMessage发送带有指针参数的消息。

 

Also available at  http://leepyzh.blogspot.com/2009/09/sendmessage-problem.html。