C# 制作Windows服务安装包
这两天公司要用C#写一个windows服务,做成安装安装包。制作的过程中遇到了一些问题,写完之后总结一下。如果以后在用到的话可以可以参考一下,而且由于原来没有做过,不知道这样做是对是不对,请各位看官如果发现有不当之处请指教。
开始的时候我的开发工具VS 2012,需要用 InstallShield,没闹明白,时间紧迫没有搞,改用vs2010。
首先创建一个windows服务:
添加安装程序:
这里面简单设置一下服务的属性,ServiceName就是服务的名称,DispalyName是在本地服务列表中现实的名称,如果DispalyName没有设置,那么默认为ServiceName。StartType是服务的启动方式,Automatic为服务开机启动,Manual是手动。设置如下:
OK下面开始搞一下服务的逻辑部分
服务逻辑:
就是写一个Soket服务端,在Service1.cd文件中有如下代码段:
1
protected
override
void
OnStart(
string
[] args)
2
{
3
4
}
5
6
protected
override
void
OnStop()
7
{
8
9
}
从字面理解也很容易,一个是服务启动时候一个是服务停止时候调用的方法,
我定义了一个处理Socket的帮助类SocketHandle.cs
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Linq;
4
using
System.Text;
5
using
System.Net.Sockets;
6
using
System.Net;
7
using
System.Threading;
8
9
namespace
SocketServer
10
{
11
class
SocketHandle
12
{
13
private
static
Socket serverSocket;
14
int
pointInt =
8888
;
15
private
static
byte
[] result =
new
byte
[
1024
];
16
private
static
SocketHandle socketHandle;
17
18
public
SocketHandle()
19
{
20
21
}
22
23
public
static
SocketHandle getInstance()
24
{
25
if
(socketHandle ==
null
)
26
{
27
socketHandle =
new
SocketHandle();
28
}
29
30
return
socketHandle;
31
}
32
33
public
void
InitSocketServer()
34
{
35
IPAddress localIp =
GetLocalIP();
36
if
(localIp ==
null
)
37
{
38
localIp = IPAddress.Parse(
"
127.0.0.1
"
);
39
}
40
try
41
{
42
if
(serverSocket ==
null
)
43
{
44
serverSocket =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
45
serverSocket.Bind(
new
IPEndPoint(localIp, pointInt));
46
serverSocket.Listen(
10
);
47
Thread myThread =
new
Thread(ListenClientConnect);
48
myThread.Start();
49
50
}
51
}
52
catch
(Exception ex)
53
{
54
}
55
}
56
57
58
59
///
<summary>
60
///
监听客户端连接
61
///
</summary>
62
private
static
void
ListenClientConnect()
63
{
64
while
(
true
)
65
{
66
try
67
{
68
Socket clientSocket =
serverSocket.Accept();
69
Thread receiveThread =
new
Thread(ReceiveMessage);
70
receiveThread.Start(clientSocket);
71
}
72
catch
(Exception ex)
73
{
74
}
75
}
76
}
77
78
79
80
///
<summary>
81
///
接收消息
82
///
</summary>
83
///
<param name="clientSocket"></param>
84
private
static
void
ReceiveMessage(
object
clientSocket)
85
{
86
Socket myClientSocket =
(Socket)clientSocket;
87
byte
[] bs =
new
byte
[
1024
];
88
while
(
true
)
89
{
90
try
91
{
92
//
通过clientSocket接收数据
93
int
receiveNumber =
myClientSocket.Receive(result);
94
string
data = Encoding.ASCII.GetString(result,
0
, receiveNumber);
95
bs = Encoding.ASCII.GetBytes(
"
Receive Data
"
+
data);
96
}
97
catch
(Exception ex)
98
{
99
myClientSocket.Shutdown(SocketShutdown.Both);
100
myClientSocket.Send(bs, bs.Length,
0
);
//
返回信息给客户端
101
myClientSocket.Close();
102
break
;
103
}
104
myClientSocket.Send(bs, bs.Length,
0
);
105
myClientSocket.Close();
106
break
;
107
}
108
109
}
110
111
///
<summary>
112
///
获取本地IP地址
113
///
</summary>
114
///
<returns></returns>
115
private
IPAddress GetLocalIP()
116
{
117
IPAddress localIp =
null
;
118
try
119
{
120
IPAddress[] ipArray;
121
ipArray =
Dns.GetHostAddresses(Dns.GetHostName());
122
123
localIp = ipArray.First(ip => ip.AddressFamily ==
AddressFamily.InterNetwork);
124
}
125
catch
(Exception ex)
126
{
127
}
128
129
return
localIp;
130
}
131
}
132
}
在Service1.cs文件中写成如下形式:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.ComponentModel;
4
using
System.Data;
5
using
System.Diagnostics;
6
using
System.Linq;
7
using
System.ServiceProcess;
8
using
System.Text;
9
using
System.Threading;
10
11
namespace
SocketServer
12
{
13
public
partial
class
Service1 : ServiceBase
14
{
15
private
Thread thread;
16
17
public
Service1()
18
{
19
InitializeComponent();
20
}
21
22
///
<summary>
23
///
服务启动的时候,初始化socket服务。
24
///
</summary>
25
private
void
RequestHandle()
26
{
27
try
28
{
29
SocketHandle socketHandle =
SocketHandle.getInstance();
30
socketHandle.InitSocketServer();
31
}
32
catch
(Exception ex)
33
{
34
}
35
}
36
37
protected
override
void
OnStart(
string
[] args)
38
{
39
thread =
new
Thread(
new
ThreadStart(
this
.RequestHandle));
40
thread.Start();
41
}
42
43
protected
override
void
OnStop()
44
{
45
thread.Abort();
46
}
47
}
48
}
OK,逻辑部分已经完成了,下面看关键的操作,将服务打包,首先在本解决方案中添加安装部署项目,
创建安装项目以后是这个样子的:
在应用程序文件夹中添加项目输出:
选择SocketServer作为主输出:
创建自定义操作:
选择自定义操作进入到自定义操作界面,在 自定义操作 上选择添加自定义操作:
在弹出的选择项目中的项对话框中选择 应用程序文件夹 中选择主输出项目:
然后分别重新生成SocketServer和SocketSetUp项目,查看解决方案的属性,
查看项目的配置,到对应的文件中去找安装文件,如果是Debug那么就去对应项目中的Debug目录下去找生成的文件,我们的目录是F:\SocketServer,所以得去F:\SocketServer\SocketSetUp\Debug目录去找安装文件。
下面安装一下看一下结果。(/ □ \),在安装的时候让我填写服务器名称密码什么的
,按说我自己在电脑上装东西应该不需要授权啊,回头在看一下,问题出在安装程序上,看到serviceProcessInstaller1的属性的时候发现了授权的信息,Account这个选项中选择LocalSystem应该就是本地安装应该是权限最大的不需要额外授权。试试看吧。OK没有问题,安装成功。下面看一下咱们的逻辑是不是正确,先看已安装程序中是不是存在SocketSetUp程序
没问题,下面在看一下我们SocketServer Release服务是不是运行正常:
没问题,查看8888端口是不是处于监听状态:
没问题,这样就算成功了,但是有个问题,由于服务所要开放的端口很多时候都是需要用户在安装的时候指定,那好吧,现在将Socke服务端的端口由用户指定。
在安装项目的视图中选择用户界面,在启动选项上右键添加对话框:
在弹出的添加对话框中选择文本框,设置文件框的属性:
这样文本框就添加完了,文本框属性很简单的可以看出来,只需要一个,其他的都隐藏就好了,下面就如何获取用户输入的端口值,在自定义操作中的主输出xxxxx的属性选项卡中添加参数
下面只等着接收了,接收之后将指定的端口保存到本地的安装目录,这样还需要设置一个安装路径的参数应该是这样/Port="[PORT]" /targetdir="[TARGETDIR]/"
俺写了一个XML的帮助类,用来保存端口:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Linq;
4
using
System.Text;
5
using
System.Xml;
6
7
namespace
SocketServer
8
{
9
class
XmlHandle
10
{
11
public
void
updatePort(
string
path,
string
port)
12
{
13
try
14
{
15
XmlTextWriter xmlWriter =
new
XmlTextWriter(path +
"
\\server.config
"
, Encoding.UTF8);
16
xmlWriter.WriteStartDocument();
17
xmlWriter.WriteStartElement(
"
Server
"
);
18
19
xmlWriter.WriteStartElement(
"
port
"
,
""
);
20
xmlWriter.WriteString(port);
21
xmlWriter.WriteEndElement();
22
23
xmlWriter.WriteEndDocument();
24
xmlWriter.Close();
25
}
26
catch
(Exception ex)
27
{
28
29
}
30
}
31
32
33
public
static
string
getPort()
34
{
35
string
port =
"
8888
"
;
36
try
37
{
38
XmlDocument doc =
new
XmlDocument();
39
doc.Load(AppDomain.CurrentDomain.BaseDirectory +
"
\\server.config
"
);
40
41
XmlNode xnRoot = doc.SelectSingleNode(
"
Server
"
);
42
XmlNodeList xnl =
xnRoot.ChildNodes;
43
foreach
(XmlNode xn
in
xnl)
44
{
45
XmlElement xe =
(XmlElement)xn;
46
byte
[] bts =
Encoding.UTF8.GetBytes(xe.Name);
47
if
(xe.Name ==
"
port
"
)
48
{
49
port =
xe.InnerText;
50
byte
[] bt =
Encoding.UTF8.GetBytes(port);
51
break
;
52
}
53
}
54
55
}
56
catch
(Exception ex)
57
{
58
59
}
60
return
port;
61
}
62
}
63
}
在ProjectInstaller中处理其中的逻辑,如果需要安装完成以后马上运行,那么就需要这样:
1
using
System;
2
using
System.Collections;
3
using
System.Collections.Generic;
4
using
System.ComponentModel;
5
using
System.Configuration.Install;
6
using
System.Linq;
7
8
9
namespace
SocketServer
10
{
11
[RunInstaller(
true
)]
12
public
partial
class
ProjectInstaller : System.Configuration.Install.Installer
13
{
14
string
port =
""
;
15
public
ProjectInstaller()
16
{
17
InitializeComponent();
18
}
19
20
protected
override
void
OnBeforeInstall(IDictionary savedState)
21
{
22
//
从用户界面获取的参数
23
port = Context.Parameters[
"
Port
"
];
24
}
25
26
protected
override
void
OnAfterInstall(IDictionary savedState)
27
{
28
string
appPath = Context.Parameters[
"
targetdir
"
];
29
XmlHandle xml =
new
XmlHandle();
30
xml.updatePort(appPath, port);
31
System.ServiceProcess.ServiceController sc =
new
System.ServiceProcess.ServiceController(serviceInstaller1.ServiceName);
32
if
(sc !=
null
)
33
{
34
sc.Start();
35
}
36
}
37
}
38
}
这样就在服务安装以后服务就能启动,但是在服务启动的时候,需要从本地的配置中获取端口号,那么就需要在SocketHandle.cs中做一点点修改,改成如下的样子:
1
using
System;
2
using
System.Collections.Generic;
3
using
System.Linq;
4
using
System.Text;
5
using
System.Net.Sockets;
6
using
System.Net;
7
using
System.Threading;
8
9
namespace
SocketServer
10
{
11
class
SocketHandle
12
{
13
14
private
string
pointStr =
XmlHandle.getPort();
15
private
static
Socket serverSocket;
16
int
pointInt =
8888
;
17
private
static
byte
[] result =
new
byte
[
1024
];
18
private
static
SocketHandle socketHandle;
19
20
public
SocketHandle()
21
{
22
23
}
24
25
public
static
SocketHandle getInstance()
26
{
27
if
(socketHandle ==
null
)
28
{
29
socketHandle =
new
SocketHandle();
30
}
31
32
return
socketHandle;
33
}
34
35
public
void
InitSocketServer()
36
{
37
IPAddress localIp =
GetLocalIP();
38
if
(localIp ==
null
)
39
{
40
localIp = IPAddress.Parse(
"
127.0.0.1
"
);
41
}
42
try
43
{
44
if
(serverSocket ==
null
)
45
{
46
if
(pointStr
is
string
)
47
{
48
pointInt =
Int32.Parse(pointStr);
49
}
50
serverSocket =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
51
serverSocket.Bind(
new
IPEndPoint(localIp, pointInt));
52
serverSocket.Listen(
10
);
53
Thread myThread =
new
Thread(ListenClientConnect);
54
myThread.Start();
55
56
}
57
}
58
catch
(Exception ex)
59
{
60
}
61
}
62
63
64
65
///
<summary>
66
///
监听客户端连接
67
///
</summary>
68
private
static
void
ListenClientConnect()
69
{
70
while
(
true
)
71
{
72
try
73
{
74
Socket clientSocket =
serverSocket.Accept();
75
Thread receiveThread =
new
Thread(ReceiveMessage);
76
receiveThread.Start(clientSocket);
77
}
78
catch
(Exception ex)
79
{
80
}
81
}
82
}
83
84
85
86
///
<summary>
87
///
接收消息
88
///
</summary>
89
///
<param name="clientSocket"></param>
90
private
static
void
ReceiveMessage(
object
clientSocket)
91
{
92
Socket myClientSocket =
(Socket)clientSocket;
93
byte
[] bs =
new
byte
[
1024
];
94
while
(
true
)
95
{
96
try
97
{
98
//
通过clientSocket接收数据
99
int
receiveNumber =
myClientSocket.Receive(result);
100
string
data = Encoding.ASCII.GetString(result,
0
, receiveNumber);
101
bs = Encoding.ASCII.GetBytes(
"
Receive Data
"
+
data);
102
}
103
catch
(Exception ex)
104
{
105
myClientSocket.Shutdown(SocketShutdown.Both);
106
myClientSocket.Send(bs, bs.Length,
0
);
//
返回信息给客户端
107
myClientSocket.Close();
108
break
;
109
}
110
myClientSocket.Send(bs, bs.Length,
0
);
111
myClientSocket.Close();
112
break
;
113
}
114
115
}
116
117
///
<summary>
118
///
获取本地IP地址
119
///
</summary>
120
///
<returns></returns>
121
private
IPAddress GetLocalIP()
122
{
123
IPAddress localIp =
null
;
124
try
125
{
126
IPAddress[] ipArray;
127
ipArray =
Dns.GetHostAddresses(Dns.GetHostName());
128
129
localIp = ipArray.First(ip => ip.AddressFamily ==
AddressFamily.InterNetwork);
130
}
131
catch
(Exception ex)
132
{
133
}
134
135
return
localIp;
136
}
137
}
138
}
下面运行一下,在看一下结果:
在服务的安装界面出现了添加端口的输入框:
看端口:
没问题,再看Socket客户端:
没问题。大概就是这样样子,如果大家觉得有什么地方不妥,或者有好的建议或者意见的话希望能够告诉我。谢谢。

