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