本文將演示怎么通過c#開發(fā)部署一個(gè)windows服務(wù),該服務(wù)提供各客戶端的信息通訊,適用于局域網(wǎng)。采用tcp協(xié)議,單一服務(wù)器連接模式為一對(duì)多;多臺(tái)服務(wù)器的情況下,當(dāng)客戶端連接數(shù)超過預(yù)設(shè)值時(shí)可自動(dòng)進(jìn)行負(fù)載轉(zhuǎn)移,當(dāng)然也可手動(dòng)切換服務(wù)器,這種場(chǎng)景在實(shí)際項(xiàng)目中應(yīng)用廣泛。
簡(jiǎn)單的消息則通過服務(wù)器轉(zhuǎn)發(fā),文件類的消息則讓客戶端自己建立連接進(jìn)行傳輸。后續(xù)功能將慢慢完善。
自定義協(xié)議:
1.新建windows服務(wù)項(xiàng)目
2.修改配置文件添加
1
2
3
4
|
<appsettings> <add key= "maxqueuecount" value= "10" /> <add key= "failoverserver" value= "192.168.250.113,192.168.250.141" /> </appsettings> |
說明:maxqueuecount為最大連接數(shù),failoverserver故障轉(zhuǎn)移備用服務(wù)器(多個(gè)服務(wù)器,隔開)
3.打開chatservice右鍵添加安裝程序,此時(shí)會(huì)自動(dòng)添加projectinstaller.cs文件,文件中會(huì)默認(rèn)添加serviceprocessinstaller1和serviceinstaller1兩個(gè)組件
修改serviceinstaller1和serviceprocessinstaller1的屬性信息如圖
starttype屬性說明:
automatic 指示服務(wù)在系統(tǒng)啟動(dòng)時(shí)將由(或已由)操作系統(tǒng)啟動(dòng)。如果某個(gè)自動(dòng)啟動(dòng)的服務(wù)依賴于某個(gè)手動(dòng)啟動(dòng)的服務(wù),則手動(dòng)啟動(dòng)的服務(wù)也會(huì)在系統(tǒng)啟動(dòng)時(shí)自動(dòng)啟動(dòng)。
disabled 指示禁用該服務(wù),以便它無法由用戶或應(yīng)用程序啟動(dòng)。
manual 指示服務(wù)只由用戶(使用“服務(wù)控制管理器”)或應(yīng)用程序手動(dòng)啟動(dòng)。
account屬性說明:
localservice 充當(dāng)本地計(jì)算機(jī)上非特權(quán)用戶的帳戶,該帳戶將匿名憑據(jù)提供給所有遠(yuǎn)程服務(wù)器。
localsystem 服務(wù)控制管理員使用的帳戶,它具有本地計(jì)算機(jī)上的許多權(quán)限并作為網(wǎng)絡(luò)上的計(jì)算機(jī)。
networkservice 提供廣泛的本地特權(quán)的帳戶,該帳戶將計(jì)算機(jī)的憑據(jù)提供給所有遠(yuǎn)程服務(wù)器。
user 由網(wǎng)絡(luò)上特定的用戶定義的帳戶。如果為 serviceprocessinstaller.account 成員指定 user,則會(huì)使系統(tǒng)在安裝服務(wù)時(shí)提示輸入有效的用戶名和密碼,除非您為 serviceprocessinstaller 實(shí)例的 username 和 password 這兩個(gè)屬性設(shè)置值。
4.完成以后打開chatservice代碼,重寫onstart和onstop方法(即服務(wù)的啟動(dòng)和停止方法)。若要重寫其它方法請(qǐng)?jiān)趕ervicebase中查看。
5.在項(xiàng)目中添加服務(wù)注冊(cè)和卸載腳本文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
install.bat @echo off path %systemroot%\microsoft.net\framework\v4.0.30319;%path% installutil %~dp0\windowschat.exe %systemroot%\system32\sc failure "chatservice" reset= 30 actions= restart/1000 pause @echo on uninstall.bat @echo off path %systemroot%\microsoft.net\framework\v4.0.30319;%path% installutil -u %~dp0\windowschat.exe pause @echo on |
說明:%~dp0 表示bat文件所在的目錄
文件屬性選擇 始終復(fù)制-內(nèi)容,這樣才能生成到輸出文件夾中
6.回到上面的重寫onstart和onstop方法
創(chuàng)建一個(gè)sockethelper類
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
|
namespace windowschat { public delegate void writeinfo( string info); public class sockethelper { #region 構(gòu)造函數(shù) public sockethelper() { } public sockethelper(writeinfo method) { this .method = method; } #endregion public static socket localsocket = null ; private object lockobj = new object (); public static list<socket> clients = new list<socket>(); private writeinfo method = null ; /// <summary> /// 創(chuàng)建socket /// </summary> /// <param name="port">端口默認(rèn) 11011</param> /// <param name="backlog">the maximum length of the pending connections queue.</param> /// <returns></returns> public socket create( int port = 11011, int backlog = 100) { if (localsocket == null ) { ipendpoint ipendpoint = new ipendpoint(ipaddress.any, port); //本機(jī)預(yù)使用的ip和端口 localsocket = new socket(addressfamily.internetwork, sockettype.stream, protocoltype.tcp); localsocket.bind(ipendpoint); localsocket.listen(backlog); } return localsocket; } /// <summary> /// 查找客戶端連接 /// </summary> /// <param name="id">標(biāo)識(shí)</param> /// <returns></returns> private socket findlinked( string id) { foreach (var item in clients) { if (item.remoteendpoint.tostring() == id) return item; } return null ; } /// <summary> /// 接受遠(yuǎn)程連接 /// </summary> public void accept() { if (localsocket != null ) { while ( true ) { socket client = localsocket.accept(); thread thread = new thread( new parameterizedthreadstart(revice)); thread.start(client); writelog( "客戶端:" + client.remoteendpoint.tostring() + " 接入" ); lock (lockobj) { clients.add(client); } broadcast( "add|" + client.remoteendpoint.tostring()); } } } /// <summary> /// 日志 /// </summary> /// <param name="info">信息</param> private void writelog( string info) { using (filestream fs = new filestream( "c:\\chatservice.txt" , filemode.append, fileaccess.write, fileshare.readwrite)) { using (streamwriter sw = new streamwriter(fs, encoding.utf8)) { sw.writeline(info); } } if (method != null ) { method(info); } } /// <summary> /// 廣播 /// </summary> /// <param name="info">信息</param> public void broadcast( string info) { foreach (var item in clients) { try { item.send(encoding.utf8.getbytes(info)); } catch (exception ex) { writelog(item.remoteendpoint.tostring() + ex.message); continue ; } } } /// <summary> /// 介紹信息 /// </summary> /// <param name="client"></param> public void revice( object client) { socket param = client as socket; var remotename = param.remoteendpoint.tostring(); if (param != null ) { int res = 0; while ( true ) { byte [] buffer = new byte [10240]; int size = param.receivebuffersize; try { res = param.receive(buffer); } catch (socketexception ex) { if (ex.socketerrorcode == socketerror.connectionreset) { clients.remove(param); writelog( "客戶端:" + remotename + "斷開連接1" ); broadcast( "remove|" + remotename); param.close(); return ; } } if (res == 0) { clients.remove(param); writelog( "客戶端:" + remotename + "斷開連接2" ); broadcast( "remove|" + remotename); param.close(); return ; } var clientmsg = encoding.utf8.getstring(buffer, 0, res); writelog( string .format( "收到客戶端{(lán)0}命令:{1}" , remotename, clientmsg)); if (clientmsg == "getall" ) { stringbuilder sb = new stringbuilder(); foreach (var item in clients) { sb.appendformat( "{0}|" , item.remoteendpoint.tostring()); } param.send(encoding.utf8.getbytes( "all|" + sb.tostring())); } else if (clientmsg == "offline" ) { if (clients.contains(param)) { clients.remove(param); writelog( "客戶端:" + remotename + "斷開連接2" ); broadcast( "remove|" + remotename); param.close(); return ; } } else if (clientmsg.startswith( "transt|" )) { var msgs = clientmsg.split( '|' ); var tosocket = findlinked(msgs[1]); if (tosocket != null ) { writelog(remotename + "發(fā)給" + msgs[1] + "的消息" + msgs[2]); tosocket.send(encoding.utf8.getbytes( "transf|" + remotename + "|" + msgs[2])); } } } } } } } |
重寫onstart和onstop方法
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
|
public partial class chatservice : servicebase { sockethelper helper; thread mainthread; public chatservice() { initializecomponent(); } protected override void onstart( string [] args) { if (helper == null ) { helper = new sockethelper(); } helper.create(); mainthread = new thread( new threadstart(helper.accept)); mainthread.isbackground = true ; mainthread.start(); } protected override void onstop() { helper.broadcast( "shutdown" ); } } |
至此一個(gè)簡(jiǎn)易的windows服務(wù)的聊天服務(wù)端開發(fā)完成,后續(xù)會(huì)在這基礎(chǔ)上進(jìn)行擴(kuò)展。
運(yùn)行install.bat(以管理員身份運(yùn)行)如圖
7.運(yùn)行 services.msc查找到chatservice服務(wù),能正常啟動(dòng)停止說明部署成功!
當(dāng)然你也可以將installutil.exe拷貝到執(zhí)行文件所在目錄,比如c:\bin\
則部署腳本為
cd c:\bin\
installutil windowschat.exe
卸載腳本
installutil -u windowschat.exe
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。