無意中在一個國外的站點下到了一個利用wcf實現(xiàn)聊天的程序,作者是:nikola paljetak。研究了一下,自己做了測試和部分修改,感覺還不錯,分享給大家。
先來看下運行效果:
開啟服務(wù):
客戶端程序:
程序分為客戶端和服務(wù)器端:
------------服務(wù)器端:
ichatservice.cs:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
using system; using system.collections.generic; using system.linq; using system.runtime.serialization; using system.servicemodel; using system.text; using system.collections; namespace wcfchatservice { // sessionmode.required 允許session會話。雙工協(xié)定時的回調(diào)協(xié)定類型為ichatcallback接口 [servicecontract(sessionmode = sessionmode.required, callbackcontract = typeof (ichatcallback))] public interface ichatservice { [operationcontract(isoneway = false , isinitiating = true , isterminating = false )] //----->isoneway = false等待服務(wù)器完成對方法處理;isinitiating = true啟動session會話,isterminating = false 設(shè)置服務(wù)器發(fā)送回復后不關(guān)閉會話 string [] join( string name); //用戶加入 [operationcontract(isoneway = true , isinitiating = false , isterminating = false )] void say( string msg); //群聊信息 [operationcontract(isoneway = true , isinitiating = false , isterminating = false )] void whisper( string to, string msg); //私聊信息 [operationcontract(isoneway = true , isinitiating = false , isterminating = true )] void leave(); //用戶加入 } /// <summary> /// 雙向通信的回調(diào)接口 /// </summary> interface ichatcallback { [operationcontract(isoneway = true )] void receive( string sendername, string message); [operationcontract(isoneway = true )] void receivewhisper( string sendername, string message); [operationcontract(isoneway = true )] void userenter( string name); [operationcontract(isoneway = true )] void userleave( string name); } /// <summary> /// 設(shè)定消息的類型 /// </summary> public enum messagetype { receive, userenter, userleave, receivewhisper }; /// <summary> /// 定義一個本例的事件消息類. 創(chuàng)建包含有關(guān)事件的其他有用的信息的變量,只要派生自eventargs即可。 /// </summary> public class chateventargs : eventargs { public messagetype msgtype; public string name; public string message; } } |
chatservice.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
using system; using system.collections.generic; using system.linq; using system.runtime.serialization; using system.servicemodel; using system.text; namespace wcfchatservice { // instancecontextmode.persession 服務(wù)器為每個客戶會話創(chuàng)建一個新的上下文對象。concurrencymode.multiple 異步的多線程實例 [servicebehavior(instancecontextmode = instancecontextmode.persession, concurrencymode = concurrencymode.multiple)] public class chatservice : ichatservice { private static object syncobj = new object (); ////定義一個靜態(tài)對象用于線程部份代碼塊的鎖定,用于lock操作 ichatcallback callback = null ; public delegate void chateventhandler( object sender, chateventargs e); //定義用于把處理程序賦予給事件的委托。 public static event chateventhandler chatevent; //定義事件 static dictionary< string , chateventhandler> chatters = new dictionary< string , chateventhandler>(); //創(chuàng)建一個靜態(tài)dictionary(表示鍵和值)集合(字典),用于記錄在線成員,dictionary<(of <(tkey, tvalue>)>) 泛型類 private string name; private chateventhandler myeventhandler = null ; public string [] join( string name) { bool useradded = false ; myeventhandler = new chateventhandler(myeventhandler); //將myeventhandler方法作為參數(shù)傳遞給委托 lock (syncobj) //線程的同步性,同步訪問多個線程的任何變量,利用lock(獨占鎖),確保數(shù)據(jù)訪問的唯一性。 { if (!chatters.containskey(name) && name != "" && name != null ) { this .name = name; chatters.add(name, myeventhandler); useradded = true ; } } if (useradded) { callback = operationcontext.current.getcallbackchannel<ichatcallback>(); //獲取當前操作客戶端實例的通道給ichatcallback接口的實例callback,此通道是一個定義為ichatcallback類型的泛類型,通道的類型是事先服務(wù)契約協(xié)定好的雙工機制。 chateventargs e = new chateventargs(); //實例化事件消息類chateventargs e.msgtype = messagetype.userenter; e.name = name; broadcastmessage(e); chatevent += myeventhandler; string [] list = new string [chatters.count]; //以下代碼返回當前進入聊天室成員的稱列表 lock (syncobj) { chatters.keys.copyto(list, 0); //將字典中記錄的用戶信息復制到數(shù)組中返回。 } return list; } else { return null ; } } public void say( string msg) { chateventargs e = new chateventargs(); e.msgtype = messagetype.receive; e.name = this .name; e.message = msg; broadcastmessage(e); } public void whisper( string to, string msg) { chateventargs e = new chateventargs(); e.msgtype = messagetype.receivewhisper; e.name = this .name; e.message = msg; try { chateventhandler chatterto; //創(chuàng)建一個臨時委托實例 lock (syncobj) { chatterto = chatters[to]; //查找成員字典中,找到要接收者的委托調(diào)用 } chatterto.begininvoke( this , e, new asynccallback(endasync), null ); //異步方式調(diào)用接收者的委托調(diào)用 } catch (keynotfoundexception) { } } public void leave() { if ( this .name == null ) return ; lock (syncobj) { chatters.remove( this .name); } chatevent -= myeventhandler; chateventargs e = new chateventargs(); e.msgtype = messagetype.userleave; e.name = this .name; this .name = null ; broadcastmessage(e); } //回調(diào),根據(jù)客戶端動作通知對應(yīng)客戶端執(zhí)行對應(yīng)的操作 private void myeventhandler( object sender, chateventargs e) { try { switch (e.msgtype) { case messagetype.receive: callback.receive(e.name, e.message); break ; case messagetype.receivewhisper: callback.receivewhisper(e.name, e.message); break ; case messagetype.userenter: callback.userenter(e.name); break ; case messagetype.userleave: callback.userleave(e.name); break ; } } catch { leave(); } } private void broadcastmessage(chateventargs e) { chateventhandler temp = chatevent; if (temp != null ) { //循環(huán)將在線的用戶廣播信息 foreach (chateventhandler handler in temp.getinvocationlist()) { //異步方式調(diào)用多路廣播委托的調(diào)用列表中的chateventhandler handler.begininvoke( this , e, new asynccallback(endasync), null ); } } } //廣播中線程調(diào)用完成的回調(diào)方法功能:清除異常多路廣播委托的調(diào)用列表中異常對象(空對象) private void endasync(iasyncresult ar) { chateventhandler d = null ; try { //封裝異步委托上的異步操作結(jié)果 system.runtime.remoting.messaging.asyncresult asres = (system.runtime.remoting.messaging.asyncresult)ar; d = ((chateventhandler)asres.asyncdelegate); d.endinvoke(ar); } catch { chatevent -= d; } } } } |
------------客戶端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
|
using system; using system.collections.generic; using system.componentmodel; using system.data; using system.drawing; using system.linq; using system.text; using system.windows.forms; using system.runtime.interopservices; using system.servicemodel; namespace wcfchatclient { public partial class chatform : form, ichatservicecallback { /// <summary> /// 該函數(shù)將指定的消息發(fā)送到一個或多個窗口。此函數(shù)為指定的窗口調(diào)用窗口程序,直到窗口程序處理完消息再返回。 /// </summary> /// <param name="hwnd">其窗口程序?qū)⒔邮障⒌拇翱诘木浔?lt;/param> /// <param name="msg">指定被發(fā)送的消息</param> /// <param name="wparam">指定附加的消息指定信息</param> /// <param name="lparam">指定附加的消息指定信息</param> [dllimport( "user32.dll" )] private static extern int sendmessage(intptr hwnd, int msg, int wparam, intptr lparam); //當一個窗口標準垂直滾動條產(chǎn)生一個滾動事件時發(fā)送此消息給那個窗口,也發(fā)送給擁有它的控件 private const int wm_vscroll = 0x115; private const int sb_bottom = 7; private int lastselectedindex = -1; private chatserviceclient proxy; private string username; private waitform wfdlg = new waitform(); private delegate void handledelegate( string [] list); private delegate void handleerrordelegate(); public chatform() { initializecomponent(); showinterchatmenuitem( true ); } /// <summary> /// 連接服務(wù)器 /// </summary> private void interchatmenuitem_click( object sender, eventargs e) { lbonlineusers.items.clear(); loginform logindlg = new loginform(); if (logindlg.showdialog() == dialogresult.ok) { username = logindlg.txtusername.text; logindlg.close(); } txtchatcontent.focus(); application.doevents(); instancecontext site = new instancecontext( this ); //為實現(xiàn)服務(wù)實例的對象進行初始化 proxy = new chatserviceclient(site); iasyncresult iar = proxy.beginjoin(username, new asynccallback(onendjoin), null ); wfdlg.showdialog(); } private void onendjoin(iasyncresult iar) { try { string [] list = proxy.endjoin(iar); handleendjoin(list); } catch (exception e) { handleendjoinerror(); } } /// <summary> /// 錯誤提示 /// </summary> private void handleendjoinerror() { if (wfdlg.invokerequired) wfdlg.invoke( new handleerrordelegate(handleendjoinerror)); else { wfdlg.showerror( "無法連接聊天室!" ); exitchatsession(); } } /// <summary> /// 登錄結(jié)束后的處理 /// </summary> /// <param name="list"></param> private void handleendjoin( string [] list) { if (wfdlg.invokerequired) wfdlg.invoke( new handledelegate(handleendjoin), new object [] { list }); else { wfdlg.visible = false ; showinterchatmenuitem( false ); foreach ( string name in list) { lbonlineusers.items.add(name); } appendtext( " 用戶: " + username + "--------登錄---------" + datetime.now.tostring()+ environment.newline); } } /// <summary> /// 退出聊天室 /// </summary> private void outinterchatmenuitem_click( object sender, eventargs e) { exitchatsession(); application.exit(); } /// <summary> /// 群聊 /// </summary> private void btnchat_click( object sender, eventargs e) { sayandclear( "" , txtchatcontent.text, false ); txtchatcontent.focus(); } /// <summary> /// 發(fā)送消息 /// </summary> private void sayandclear( string to, string msg, bool pvt) { if (msg != "" ) { try { communicationstate cs = proxy.state; //pvt 公聊還是私聊 if (!pvt) { proxy.say(msg); } else { proxy.whisper(to, msg); } txtchatcontent.text = "" ; } catch { abortproxyandupdateui(); appendtext( "失去連接: " + datetime.now.tostring() + environment.newline); exitchatsession(); } } } private void txtchatcontent_keypress( object sender, keypresseventargs e) { if (e.keychar == 13) { e.handled = true ; btnchat.performclick(); } } /// <summary> /// 只有選擇一個用戶時,私聊按鈕才可用 /// </summary> private void lbonlineusers_selectedindexchanged( object sender, eventargs e) { adjustwhisperbutton(); } /// <summary> /// 私聊 /// </summary> private void btnwhisper_click( object sender, eventargs e) { if (txtchatdetails.text == "" ) { return ; } object to = lbonlineusers.selecteditem; if (to != null ) { string receivername = ( string )to; appendtext( "私下對" + receivername + "說: " + txtchatcontent.text); //+ environment.newline sayandclear(receivername, txtchatcontent.text, true ); txtchatcontent.focus(); } } /// <summary> /// 連接聊天室 /// </summary> private void showinterchatmenuitem( bool show) { interchatmenuitem.enabled = show; outinterchatmenuitem.enabled = this .btnchat.enabled = !show; } private void appendtext( string text) { txtchatdetails.text += text; sendmessage(txtchatdetails.handle, wm_vscroll, sb_bottom, new intptr(0)); } /// <summary> /// 退出應(yīng)用程序時,釋放使用資源 /// </summary> private void exitchatsession() { try { proxy.leave(); } catch { } finally { abortproxyandupdateui(); } } /// <summary> /// 釋放使用資源 /// </summary> private void abortproxyandupdateui() { if (proxy != null ) { proxy.abort(); proxy.close(); proxy = null ; } showinterchatmenuitem( true ); } /// <summary> /// 接收消息 /// </summary> public void receive( string sendername, string message) { appendtext(sendername + "說: " + message + environment.newline); } /// <summary> /// 接收私聊消息 /// </summary> public void receivewhisper( string sendername, string message) { appendtext(sendername + " 私下說: " + message + environment.newline); } /// <summary> /// 新用戶登錄 /// </summary> public void userenter( string name) { appendtext( "用戶 " + name + " --------登錄---------" + datetime.now.tostring() + environment.newline); lbonlineusers.items.add(name); } /// <summary> /// 用戶離開 /// </summary> public void userleave( string name) { appendtext( "用戶 " + name + " --------離開---------" + datetime.now.tostring() + environment.newline); lbonlineusers.items.remove(name); adjustwhisperbutton(); } /// <summary> /// 控制私聊按鈕的可用性,只有選擇了用戶時按鈕才可用 /// </summary> private void adjustwhisperbutton() { if (lbonlineusers.selectedindex == lastselectedindex) { lbonlineusers.selectedindex = -1; lastselectedindex = -1; btnwhisper.enabled = false ; } else { btnwhisper.enabled = true ; lastselectedindex = lbonlineusers.selectedindex; } txtchatcontent.focus(); } /// <summary> /// 窗體關(guān)閉時,釋放使用資源 /// </summary> private void chatform_formclosed( object sender, formclosedeventargs e) { abortproxyandupdateui(); application.exit(); } } } |
代碼中我做了詳細的講解,相信園友們完全可以看懂。代碼中的一些使用的方法還是值得大家參考學習的。這里涉及到了wcf的使用方法,需要注意的是:如果想利用工具生成代理類,需要加上下面的代碼:
1
2
3
4
5
6
7
|
if (host.description.behaviors.find<system.servicemodel.description.servicemetadatabehavior>() == null ) { bindingelement metaelement = new tcptransportbindingelement(); custombinding metabind = new custombinding(metaelement); host.description.behaviors.add( new system.servicemodel.description.servicemetadatabehavior()); host.addserviceendpoint( typeof (system.servicemodel.description.imetadataexchange), metabind, "mex" ); } |
否則在生成代理類的時候會報錯如下的錯誤:
源碼下載:
/files/gaoweipeng/wcfchat.rar