设为首页 收藏本站
查看: 690|回复: 0

[经验分享] 基于Windows服务的聊天程序(一)

[复制链接]

尚未签到

发表于 2017-12-7 14:52:29 | 显示全部楼层 |阅读模式
  本文将演示怎么通过C#开发部署一个Windows服务,该服务提供各客户端的信息通讯,适用于局域网。采用TCP协议,单一服务器连接模式为一对多;多台服务器的情况下,当客户端连接数超过预设值时可自动进行负载转移,当然也可手动切换服务器,这种场景在实际项目中应用广泛。
  简单的消息则通过服务器转发,文件类的消息则让客户端自己建立连接进行传输。后续功能将慢慢完善。
  自定义协议:


标识


含义


参数


From


GETALL


获取所有在线终端





Client


OFFLINE


客户端下线





Client


SHUTDOWN


服务器下线





Server


ALL|{0}


在线终端


{0}所有在线客户端标识|分隔


Server


REMOVE|{0}


通知下线


{0}下线客户端的标识


Server


TRANST|{0}|{1}


发送消息


{0}接受消息客户端的标识{1}消息


Client


TRANSF|{0}|{1}


发送消息


{0}发送消息客户端的标识{1}消息


Server


BROADCAST


广播





Server


FILE


文件





Client


LINKTO|{0}


连接





Server














  1.新建Windows服务项目
   DSC0000.png
  2.修改配置文件添加



<appSettings>
<add key="maxQueueCount" value="10"/>
<add key="failoverServer" value="192.168.250.113,192.168.250.141"/>
</appSettings>
       说明:maxQueueCount为最大连接数,failoverServer故障转移备用服务器(多个服务器,隔开)
  3.打开ChatService右键添加安装程序,此时会自动添加ProjectInstaller.cs文件,文件中会默认添加serviceProcessInstaller1和serviceInstaller1两个组件
   DSC0001.png
  修改serviceInstaller1和serviceProcessInstaller1的属性信息如图
DSC0002.png

DSC0003.png

  StartType属性说明:
  Automatic 指示服务在系统启动时将由(或已由)操作系统启动。如果某个自动启动的服务依赖于某个手动启动的服务,则手动启动的服务也会在系统启动时自动启动。
  Disabled 指示禁用该服务,以便它无法由用户或应用程序启动。
  Manual 指示服务只由用户(使用“服务控制管理器”)或应用程序手动启动。
  Account属性说明:
  LocalService    充当本地计算机上非特权用户的帐户,该帐户将匿名凭据提供给所有远程服务器。
  LocalSystem    服务控制管理员使用的帐户,它具有本地计算机上的许多权限并作为网络上的计算机。
  NetworkService    提供广泛的本地特权的帐户,该帐户将计算机的凭据提供给所有远程服务器。
  User    由网络上特定的用户定义的帐户。如果为 ServiceProcessInstaller.Account 成员指定 User,则会使系统在安装服务时提示输入有效的用户名和密码,除非您为 ServiceProcessInstaller 实例的 Username 和 Password 这两个属性设置值。
  4.完成以后打开ChatService代码,重写OnStart和OnStop方法(即服务的启动和停止方法)。若要重写其它方法请在ServiceBase中查看。
  5.在项目中添加服务注册和卸载脚本文件



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文件所在的目录
  文件属性选择 始终复制-内容,这样才能生成到输出文件夹中
DSC0004.png

  6.回到上面的重写OnStart和OnStop方法
  创建一个SocketHelper类


DSC0005.gif DSC0006.gif


  1 namespace WindowsChat
  2 {
  3     public delegate void WriteInfo(string info);
  4
  5     public class SocketHelper
  6     {
  7         #region 构造函数
  8         public SocketHelper()
  9         {
10         }
11         public SocketHelper(WriteInfo method)
12         {
13             this.method = method;
14         }
15         #endregion
16
17         public static Socket LocalSocket = null;
18         private object lockObj = new object();
19         public static List<Socket> Clients = new List<Socket>();
20         private WriteInfo method = null;
21
22         /// <summary>
23         /// 创建Socket
24         /// </summary>
25         /// <param name="port">端口默认 11011</param>
26         /// <param name="backlog">The maximum length of the pending connections queue.</param>
27         /// <returns></returns>
28         public Socket Create(int port = 11011, int backlog = 100)
29         {
30             if (LocalSocket == null)
31             {
32                 IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);//本机预使用的IP和端口
33                 LocalSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
34                 LocalSocket.Bind(ipEndPoint);
35                 LocalSocket.Listen(backlog);
36             }
37             return LocalSocket;
38         }
39
40         /// <summary>
41         /// 查找客户端连接
42         /// </summary>
43         /// <param name="id">标识</param>
44         /// <returns></returns>
45         private Socket FindLinked(string id)
46         {
47             foreach (var item in Clients)
48             {
49                 if (item.RemoteEndPoint.ToString() == id)
50                     return item;
51             }
52             return null;
53         }
54
55         /// <summary>
56         /// 接受远程连接
57         /// </summary>
58         public void Accept()
59         {
60             if (LocalSocket != null)
61             {
62                 while (true)
63                 {
64                     Socket client = LocalSocket.Accept();
65                     Thread thread = new Thread(new ParameterizedThreadStart(Revice));
66                     thread.Start(client);
67                     WriteLog("客户端:" + client.RemoteEndPoint.ToString() + " 接入");
68                     lock (lockObj)
69                     {
70                         Clients.Add(client);
71                     }
72                     BroadCast("ADD|" + client.RemoteEndPoint.ToString());
73                 }
74             }
75         }
76
77         /// <summary>
78         /// 日志
79         /// </summary>
80         /// <param name="info">信息</param>
81         private void WriteLog(string info)
82         {
83             using (FileStream fs = new FileStream("C:\\chatservice.txt", FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
84             {
85                 using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
86                 {
87                     sw.WriteLine(info);
88                 }
89             }
90             if (method != null)
91             {
92                 method(info);
93             }
94         }
95
96         /// <summary>
97         /// 广播
98         /// </summary>
99         /// <param name="info">信息</param>
100         public void BroadCast(string info)
101         {
102             foreach (var item in Clients)
103             {
104                 try
105                 {
106                     item.Send(Encoding.UTF8.GetBytes(info));
107                 }
108                 catch (Exception ex)
109                 {
110                     WriteLog(item.RemoteEndPoint.ToString() + ex.Message);
111                     continue;
112                 }
113             }
114         }
115
116         /// <summary>
117         /// 介绍信息
118         /// </summary>
119         /// <param name="client"></param>
120         public void Revice(object client)
121         {
122             Socket param = client as Socket;
123             var remoteName = param.RemoteEndPoint.ToString();
124             if (param != null)
125             {
126                 int res = 0;
127                 while (true)
128                 {
129                     byte[] buffer = new byte[10240];
130                     int size = param.ReceiveBufferSize;
131                     try
132                     {
133                         res = param.Receive(buffer);
134                     }
135                     catch (SocketException ex)
136                     {
137                         if (ex.SocketErrorCode == SocketError.ConnectionReset)
138                         {
139                             Clients.Remove(param);
140                             WriteLog("客户端:" + remoteName + "断开连接1");
141                             BroadCast("REMOVE|" + remoteName);
142                             param.Close();
143                             return;
144                         }
145                     }
146
147                     if (res == 0)
148                     {
149                         Clients.Remove(param);
150                         WriteLog("客户端:" + remoteName + "断开连接2");
151                         BroadCast("REMOVE|" + remoteName);
152                         param.Close();
153                         return;
154                     }
155                     var clientMsg = Encoding.UTF8.GetString(buffer, 0, res);
156                     WriteLog(string.Format("收到客户端{0}命令:{1}", remoteName, clientMsg));
157                     if (clientMsg == "GETALL")
158                     {
159                         StringBuilder sb = new StringBuilder();
160                         foreach (var item in Clients)
161                         {
162                             sb.AppendFormat("{0}|", item.RemoteEndPoint.ToString());
163                         }
164                         param.Send(Encoding.UTF8.GetBytes("ALL|" + sb.ToString()));
165                     }
166                     else if (clientMsg == "OFFLINE")
167                     {
168                         if (Clients.Contains(param))
169                         {
170                             Clients.Remove(param);
171                             WriteLog("客户端:" + remoteName + "断开连接2");
172                             BroadCast("REMOVE|" + remoteName);
173                             param.Close();
174                             return;
175                         }
176                     }
177                     else if (clientMsg.StartsWith("TRANST|"))
178                     {
179                         var msgs = clientMsg.Split('|');
180                         var toSocket = FindLinked(msgs[1]);
181                         if (toSocket != null)
182                         {
183                             WriteLog(remoteName + "发给" + msgs[1] + "的消息" + msgs[2]);
184                             toSocket.Send(Encoding.UTF8.GetBytes("TRANSF|" + remoteName + "|" + msgs[2]));
185                         }
186                     }
187                 }
188             }
189         }
190     }
191 }
SocketHelper  重写OnStart和OnStop方法



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");
}
}
  至此一个简易的Windows服务的聊天服务端开发完成,后续会在这基础上进行扩展。
  运行install.bat(以管理员身份运行)如图
DSC0007.png

  7.运行 services.msc查找到ChatService服务,能正常启动停止说明部署成功!
DSC0008.png

  当然你也可以将InstallUtil.exe拷贝到执行文件所在目录,比如c:\bin\
  则部署脚本为
  cd c:\bin\
  InstallUtil WindowsChat.exe
  卸载脚本
  InstallUtil -u WindowsChat.exe
  本文地址:http://www.cnblogs.com/liuxiaobo93/p/7205904.html

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-421759-1-1.html 上篇帖子: SQL Server常见问题介绍及快速解决建议 下篇帖子: windows server 2008 不能执行bat
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表