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客户端:
没问题。大概就是这样样子,如果大家觉得有什么地方不妥,或者有好的建议或者意见的话希望能够告诉我。谢谢。