翻译了 利用 WCF duplex Service 【推送】数据到Siliverlight客户端 的双向通讯例子 收益菲浅, 终于通讯真正做起来了, 刚做好的聊天程序, 后面再补充笔记, 把一些关键问题解决调, 在优化一下.
访问地址: http://www.shareach.com:81/chat
代码下载:http://www.cnblogs.com/yinpengxiang/archive/2009/03/30/1424724.html
以前js版本的: http://www.shareach.com/chat.aspx对应
看了一周的WCF终于有点成果了. 服务端参考了MSDN的这个文章, 客户端参考了MSDN这个文章
看大家登录后测试情况, 这个 双向通讯还是不稳定, 经常有js错误出现,但是不影响正常使用
后续计划:
- 做公聊的聊天室,现在只有私聊的
- 完成登录状态即时更新, 现在人家走了一年也不知道
- 把前面测试的上传功能结合进来
- 想做一个相册
这么多功能先想想,不知道要到猴年马月搞定
遇到的问题/原因和解决办法吧
1.PollingDuplexHttpBinding.ReceiveTimeout 对应的超时问题 和 这个问题发现后的代码修改方式
这个问题用作IM通讯项目里可能会碰到, 这个属性是针对监听超时, MSDN上描述是”Gets or sets the interval of time that a connection can remain inactive, during which no application messages are received, before it is dropped.” 我开始一度以为,这个是针对整个通道的, 所以一直没有搞明白消息收发仍在正常继续,但又会有超时报错, 经过几天的反复验证,终于发现问题原因和症结了. 我出的错误信息如下:CompleteReceive:Receive on local address http://docs.oasis-open.org/ws-rx/wsmc/200702/anonymous?id=ce16853b-f9cb-47e8-b812-3ea66bda0c45 timed out after 00:01:30. The time allotted to this operation may have been a portion of a longer timeout.这是一个普通的超时错误, 这个错误不是针对通道的, 而是对每个异步监听的错误, 我把我错误原因描述一下, 但是没有想好解决方法.
这个错误是我启动了很多异步监听(不是一个), 我是抄MSDN上的例子. 每次发送消息后我都会调用LoopRecive等消息,然后消息接受完了以后我还调用LoopRecive等消息. 有很多这个监听轮询, 导致超时的. MSDN上的例子的void CompleteOpenChannel(IAsyncResult result)调用了ReceiveLoop(channel); void CompleteReceive(IAsyncResult result)里面有调用了 ReceiveLoop(channel); 他这个是同步方式在循环发送信息,所以不会出超时异常,我把 SendMessage做了一个消息缓冲池, 每次有消息我就批量的发出去了 而且都调用ReceiveLoop,导致很多监听等待. 主要是例子里面所有的通讯都是顺序的, 但是真实使用的时候不可能这样完全顺序的. 有可能几个消息同时到达客户端, 为了保证消息正常到达,我又有回复机制,这就导致又有RecieveLoop循环,呵呵恶性循环. 还没想到好办法,只是把他try catch调了.
其实我觉得这是一个好处,反正都是异步监听,异步发送,不会影响到通道, 所以我把代码做了修改, 把消息池都去掉了, 呵呵, 看下面:
MSDN相关代码
![](/Images/OutliningIndicators/ContractedBlock.gif)
MSDN相关代码
1
void CompleteOpenChannel(IAsyncResult result)
2![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
3
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
4
channel.EndOpen(result);
5
// The channel is now open. Send a message.
6
Message message =
7
Message.CreateMessage(channel.GetProperty<MessageVersion>(),
8
"Silverlight/IDuplexService/Order", order);
9
IAsyncResult resultChannel =
10
channel.BeginSend(message, new AsyncCallback(OnSend), channel);
11
if (resultChannel.CompletedSynchronously)
12![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
13
CompleteOnSend(resultChannel);
14
}
15
// Also start the receive loop to listen for callbacks from the service.
16
ReceiveLoop(channel);
17
}
18
void CompleteReceive(IAsyncResult result)
19![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
20
// A callback was received.
21
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
22
try
23![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
24
Message receivedMessage = channel.EndReceive(result);
25
if (receivedMessage == null)
26![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
27
// Server closed its output session, can close client channel,
28
// or continue sending messages to start a new session.
29
}
30
else
31![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
32
// Show the service response in the UI.
33
string text = receivedMessage.GetBody<string>();
34
uiThread.Post(WriteText, "Service says: " + text + Environment.NewLine);
35
// Check whether the order is complete.
36
if (text == order + " order complete")
37![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
38
// If the order is complete, close the client channel.
39
IAsyncResult resultFactory =
40
channel.BeginClose(new AsyncCallback(OnCloseChannel), channel);
41
if (resultFactory.CompletedSynchronously)
42![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
43
CompleteCloseChannel(result);
44
}
45
}
46
else
47![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
48
// If the order is not complete, continue listening.
49
ReceiveLoop(channel);
50
}
51
}
52
}
53
catch (CommunicationObjectFaultedException)
54![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
55
// The channel inactivity time-out was reached.
56
}
57
}
58
void CompleteOnSend(IAsyncResult result)
59![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
60
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
61
channel.EndSend(result);
62
// The message is now sent. Notify the user.
63
uiThread.Post(WriteText, "Client says: Sent order of " + order + Environment.NewLine);
64
uiThread.Post(WriteText, "Service will call back with updates" + Environment.NewLine);
65
} 我
改了一下流程和发送方式, 去掉了前面加的消息发送池, 既然超时只是针对每次监听回调, 那么我可以怎么发消息都没事了, 也就是连接完成后,我把Channel提出来了, 把发送功能(SendMessage)单独的抛出来了, 以后就一直用它好了,除非通道异常了, 至于通道异常不是这个主题了
![](/Images/OutliningIndicators/ContractedBlock.gif)
我的代码
1
IDuplexSessionChannel _channel = null;
2
void CompleteOpenChannel(IAsyncResult result)
3![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
4
try
5![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
6
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
7
channel.EndOpen(result);
8
//OnException("Info: 通道打开了. ");
9
_status = true;
10
_channel = channel;
11
if (OnAfterOpenChannel != null)
12![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
13
OnAfterOpenChannel(channel);
14
}
15
}
16
catch (Exception exp)
17![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
18
if (OnException != null)
19![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
20
OnException("CompleteOpenChannel:" + exp.Message);
21
}
22
}
23
}
24
void CompleteReceive(IAsyncResult result)
25![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
26
//A callback was received so process data
27
IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
28
try
29![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
30
// 完成异步操作,以接收到服务端发过来的消息
31
Message receivedMessage = channel.EndReceive(result);
32
// Show the service response in the UI.
33
if (receivedMessage != null)
34![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
35
TransMessage data = receivedMessage.GetBody();
36
_UiThread.Post(Client.RecvMessage, data);
37
if (!_status)
38![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
39
}
40
else
41![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
42
//再次轮询
43
ReceiveLoop(channel);
44
}
45
}
46
else
47![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
48
// 服务端会话已被关闭
49
// 此时应该关闭客户端会话,或向服务端发送消息以启动一个新的会话
50
OnException("服务端会话已被关闭");
51
}
52
}
53
catch (CommunicationObjectFaultedException exp)
54![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
55
if (OnException != null)
56![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
57
OnException("和服务器通讯出错了");
58
//重试连接
59
if (OnAfterOpenChannel != null)
60![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
61
OnAfterOpenChannel(channel);
62
}
63
}
64
}
65
catch (CommunicationObjectAbortedException exp)
66![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
67
if (OnException != null)
68![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
69
OnException("断开服务器连接");
70
}
71
}
72
catch (TimeoutException exp)
73![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
74
if (OnException != null)
75![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
76
//OnException("接收超时,不影响使用的超时");
77
}
78
}
79
catch (Exception exp)
80![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
81
if (OnException != null)
82![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
83
OnException("CompleteReceive:" + exp.Message);
84
}
85
}
86
}
87
class SendMessageState
88![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
89![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public TransMessage Msg
{ get; set; }
90![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public IDuplexSessionChannel Channel
{ get; set; }
91
}
92
public void SendMessage(TransMessage data)
93![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
94
if (_channel == null || !_status)
95![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
96
if (OnException != null)
97![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
98
OnException("你没有连接到网络或者网络异常");
99
}
100
if (((MessageType)(data.Type) == MessageType.Chat) ||
101
((MessageType)(data.Type) == MessageType.ChatRoom))
102![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
103
if (OnAfterSendError != null)
104![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
105
OnAfterSendError(data);
106
}
107
}
108
109
return;
110
}
111
try
112![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
113
// 构造需要发送到服务端的 System.ServiceModel.Channels.Message (客户端终结点与服务端终结点之间的通信单元)
114
Message message = Message.CreateMessage(
115
_channel.GetProperty(),
116
_action,
117
data);
118
SendMessageState state = new SendMessageState()
119![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
120
Msg = data,
121
Channel = _channel
122
};
123
IAsyncResult resultChannel = _channel.BeginSend(message, new AsyncCallback(OnSend), state);
124
if (resultChannel.CompletedSynchronously)
125![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
126
CompleteOnSend(resultChannel);
127
}
128
ReceiveLoop(_channel);
129
}
130
catch (CommunicationException exp)
131![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
132
//IAsyncResult result = _channel.BeginSend(message, new AsyncCallback(OnSend), state);
133
_status = false;
134
//关闭当前通道
135
CloseChannel(_channel);
136
_channel = null;
137
if (this.OnAfterChannelClosed != null)
138![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
139
OnAfterChannelClosed(null);
140
}
141
}
142
catch (Exception exp)
143![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
144
if (OnException != null)
145![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
146
OnException("SendMessage:" + exp.Message);
147
}
148
}
149
}
150
void CompleteOnSend(IAsyncResult result)
151![](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](/Images/OutliningIndicators/ContractedBlock.gif)
{
152
SendMessageState state = null;
153
try
154![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
155
state = (SendMessageState)result.AsyncState;
156
state.Channel.EndSend(result);
157
}
158
catch(CommunicationException exp)
159![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
160
_status = false;
161
//关闭当前通道
162
CloseChannel(_channel);
163
_channel = null;
164
165
if (OnException != null)
166![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
167
if (state != null)
168![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
169
OnException(string.Format("无法连接服务器或者网络异常({0})", FieldDefine.GetCapital((MessageType)state.Msg.Type)));
170
}
171
else
172![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
173
OnException("无法连接服务器或者网络异常");
174
}
175
}
176
if (state != null && OnAfterSendError != null)
177![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
178
OnAfterSendError(state.Msg);
179
}
180
}
181
catch (Exception exp)
182![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
183
_status = false;
184
CloseChannel(_channel);
185
_channel = null;
186
//关闭当前通道
187
188
if (OnException != null)
189![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
190
if (state != null)
191![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
192
OnException(string.Format("CompleteOnSend({0}):", state.Msg.Type+exp.Message));
193
}
194
else
195![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
196
OnException("CompleteOnSend:" + exp.Message);
197
}
198
}
199
if (state != null && OnAfterSendError != null)
200![](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
201
OnAfterSendError(state.Msg);
202
}
203
}
204
}
205![](/Images/OutliningIndicators/None.gif)
上面的代码中,我可以任意的地方调用SendMessage, 这样就封装成了一个只有几个口的通讯类了, 其它都用了异常属性方法回调, 比较懒, 都是同步调用的,没有用异步方式. 那么那个异常就让他异常去吧, 反正不影响我通讯. 除非后面有好的解决方法.
还有一个就是那个SendMessageState类, 为了在异步发送完成后, 如果出错,知道原始消息是什么了, 但是有个问题就是站着内存, 不过消息不大,呵呵.
2. 异步调用,其它线程访问UI
前面好多地方为了使Silverlight不出错, 都加了Try Catch了, 也没处理(习惯不好,为了快速验证学过的东西), 而且很多回调都是同步的, 在界面上都没有看到错误在哪, 经常出了莫名奇妙的问题, 后来发现很多是因为通道接收到的消息都是uiThread.Post()方法出来的,导致了这个冲突, 后来做了那个调试日志才发现的. 置于怎么访问更新和处理UI看Silverlight中 非UI线程更新UI 的几种方法, 这和WinForm有点类似.
3.全屏模式下键盘无效了
这个问题比较恶心, 我感觉应该是个开关, 微软解释是什么操作安全问题, 全屏不让随便碰屏幕什么的. 如果是开关多好, 开始全屏感觉慢爽的. 发现全屏不能输入, 就不爽了.
4.IE 8
Ie 8 从Beta 开始我就安装了一次,为了那个Dev tool(和firebug类似), 发现不好用, RC的时候又是装了卸了, 正式版还是一样, SL在上面好多问题(是一个在聊天室告诉我的), 包括cnblog版面都是不正常, 又卸了. 这个问题应该不是这个里面的问题, 罗列一下吧.
5.客户端退出让Service即时知道
退出或者异常退出,还没找到办法及时让Service监测到,只有等待session过期才能检测到, 只能把session值设置小一点. 不知道有没有其它办法, 让Service快速知道客户端断了.
总的感觉, WCF 非常强大, Silverlight结合WCF做RIA应用觉得比Flash强, 那个SmartFoxServer好难用啊, 而且那个代码太难受.
上次看了CodePlex上的一个开源代码, 上传大文件的, 我上传了7G还没死, 后来不敢继续了, 我把他改了一下上传照片, 网址是 http://www.shareach.com:81/upload, 客户端就处理图片,很爽.
后面还要继续研究WCF其它的东西. good good study, day day up.