翻译了 利用 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相关代码
MSDN相关代码
1void CompleteOpenChannel(IAsyncResult result)
2{
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 {
13 CompleteOnSend(resultChannel);
14 }
15 // Also start the receive loop to listen for callbacks from the service.
16 ReceiveLoop(channel);
17}
18void CompleteReceive(IAsyncResult result)
19{
20 // A callback was received.
21 IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
22 try
23 {
24 Message receivedMessage = channel.EndReceive(result);
25 if (receivedMessage == null)
26 {
27 // Server closed its output session, can close client channel,
28 // or continue sending messages to start a new session.
29 }
30 else
31 {
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 {
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 {
43 CompleteCloseChannel(result);
44 }
45 }
46 else
47 {
48 // If the order is not complete, continue listening.
49 ReceiveLoop(channel);
50 }
51 }
52 }
53 catch (CommunicationObjectFaultedException)
54 {
55 // The channel inactivity time-out was reached.
56 }
57}
58void CompleteOnSend(IAsyncResult result)
59{
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)单独的抛出来了, 以后就一直用它好了,除非通道异常了, 至于通道异常不是这个主题了
我的代码
1IDuplexSessionChannel _channel = null;
2void CompleteOpenChannel(IAsyncResult result)
3{
4 try
5 {
6 IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
7 channel.EndOpen(result);
8 //OnException("Info: 通道打开了. ");
9 _status = true;
10 _channel = channel;
11 if (OnAfterOpenChannel != null)
12 {
13 OnAfterOpenChannel(channel);
14 }
15 }
16 catch (Exception exp)
17 {
18 if (OnException != null)
19 {
20 OnException("CompleteOpenChannel:" + exp.Message);
21 }
22 }
23}
24void CompleteReceive(IAsyncResult result)
25{
26 //A callback was received so process data
27 IDuplexSessionChannel channel = (IDuplexSessionChannel)result.AsyncState;
28 try
29 {
30 // 完成异步操作,以接收到服务端发过来的消息
31 Message receivedMessage = channel.EndReceive(result);
32 // Show the service response in the UI.
33 if (receivedMessage != null)
34 {
35 TransMessage data = receivedMessage.GetBody();
36 _UiThread.Post(Client.RecvMessage, data);
37 if (!_status)
38 {
39 }
40 else
41 {
42 //再次轮询
43 ReceiveLoop(channel);
44 }
45 }
46 else
47 {
48 // 服务端会话已被关闭
49 // 此时应该关闭客户端会话,或向服务端发送消息以启动一个新的会话
50 OnException("服务端会话已被关闭");
51 }
52 }
53 catch (CommunicationObjectFaultedException exp)
54 {
55 if (OnException != null)
56 {
57 OnException("和服务器通讯出错了");
58 //重试连接
59 if (OnAfterOpenChannel != null)
60 {
61 OnAfterOpenChannel(channel);
62 }
63 }
64 }
65 catch (CommunicationObjectAbortedException exp)
66 {
67 if (OnException != null)
68 {
69 OnException("断开服务器连接");
70 }
71 }
72 catch (TimeoutException exp)
73 {
74 if (OnException != null)
75 {
76 //OnException("接收超时,不影响使用的超时");
77 }
78 }
79 catch (Exception exp)
80 {
81 if (OnException != null)
82 {
83 OnException("CompleteReceive:" + exp.Message);
84 }
85 }
86}
87class SendMessageState
88{
89 public TransMessage Msg { get; set; }
90 public IDuplexSessionChannel Channel { get; set; }
91}
92public void SendMessage(TransMessage data)
93{
94 if (_channel == null || !_status)
95 {
96 if (OnException != null)
97 {
98 OnException("你没有连接到网络或者网络异常");
99 }
100 if (((MessageType)(data.Type) == MessageType.Chat) ||
101 ((MessageType)(data.Type) == MessageType.ChatRoom))
102 {
103 if (OnAfterSendError != null)
104 {
105 OnAfterSendError(data);
106 }
107 }
108
109 return;
110 }
111 try
112 {
113 // 构造需要发送到服务端的 System.ServiceModel.Channels.Message (客户端终结点与服务端终结点之间的通信单元)
114 Message message = Message.CreateMessage(
115 _channel.GetProperty(),
116 _action,
117 data);
118 SendMessageState state = new SendMessageState()
119 {
120 Msg = data,
121 Channel = _channel
122 };
123 IAsyncResult resultChannel = _channel.BeginSend(message, new AsyncCallback(OnSend), state);
124 if (resultChannel.CompletedSynchronously)
125 {
126 CompleteOnSend(resultChannel);
127 }
128 ReceiveLoop(_channel);
129 }
130 catch (CommunicationException exp)
131 {
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 {
139 OnAfterChannelClosed(null);
140 }
141 }
142 catch (Exception exp)
143 {
144 if (OnException != null)
145 {
146 OnException("SendMessage:" + exp.Message);
147 }
148 }
149}
150void CompleteOnSend(IAsyncResult result)
151{
152 SendMessageState state = null;
153 try
154 {
155 state = (SendMessageState)result.AsyncState;
156 state.Channel.EndSend(result);
157 }
158 catch(CommunicationException exp)
159 {
160 _status = false;
161 //关闭当前通道
162 CloseChannel(_channel);
163 _channel = null;
164
165 if (OnException != null)
166 {
167 if (state != null)
168 {
169 OnException(string.Format("无法连接服务器或者网络异常({0})", FieldDefine.GetCapital((MessageType)state.Msg.Type)));
170 }
171 else
172 {
173 OnException("无法连接服务器或者网络异常");
174 }
175 }
176 if (state != null && OnAfterSendError != null)
177 {
178 OnAfterSendError(state.Msg);
179 }
180 }
181 catch (Exception exp)
182 {
183 _status = false;
184 CloseChannel(_channel);
185 _channel = null;
186 //关闭当前通道
187
188 if (OnException != null)
189 {
190 if (state != null)
191 {
192 OnException(string.Format("CompleteOnSend({0}):", state.Msg.Type+exp.Message));
193 }
194 else
195 {
196 OnException("CompleteOnSend:" + exp.Message);
197 }
198 }
199 if (state != null && OnAfterSendError != null)
200 {
201 OnAfterSendError(state.Msg);
202 }
203 }
204}
205
上面的代码中,我可以任意的地方调用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.