本文将演示怎么通过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服务项目

  

  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两个组件

  

  修改serviceInstaller1和serviceProcessInstaller1的属性信息如图

  

  

  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文件所在的目录

  文件属性选择 始终复制-内容,这样才能生成到输出文件夹中

  

  6.回到上面的重写OnStart和OnStop方法

  创建一个SocketHelper类

 namespace WindowsChat
{
public delegate void WriteInfo(string info); public class SocketHelper
{
#region 构造函数
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>
/// 创建Socket
/// </summary>
/// <param name="port">端口默认 11011</param>
/// <param name="backlog">The maximum length of the pending connections queue.</param>
/// <returns></returns>
public Socket Create(int port = , int backlog = )
{
if (LocalSocket == null)
{
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port);//本机预使用的IP和端口
LocalSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
LocalSocket.Bind(ipEndPoint);
LocalSocket.Listen(backlog);
}
return LocalSocket;
} /// <summary>
/// 查找客户端连接
/// </summary>
/// <param name="id">标识</param>
/// <returns></returns>
private Socket FindLinked(string id)
{
foreach (var item in Clients)
{
if (item.RemoteEndPoint.ToString() == id)
return item;
}
return null;
} /// <summary>
/// 接受远程连接
/// </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 = ;
while (true)
{
byte[] buffer = new byte[];
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 == )
{
Clients.Remove(param);
WriteLog("客户端:" + remoteName + "断开连接2");
BroadCast("REMOVE|" + remoteName);
param.Close();
return;
}
var clientMsg = Encoding.UTF8.GetString(buffer, , res);
WriteLog(string.Format("收到客户端{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[]);
if (toSocket != null)
{
WriteLog(remoteName + "发给" + msgs[] + "的消息" + msgs[]);
toSocket.Send(Encoding.UTF8.GetBytes("TRANSF|" + remoteName + "|" + msgs[]));
}
}
}
}
}
}
}

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(以管理员身份运行)如图

  

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

  

  当然你也可以将InstallUtil.exe拷贝到执行文件所在目录,比如c:\bin\

  则部署脚本为

  cd c:\bin\

  InstallUtil WindowsChat.exe

  卸载脚本

  InstallUtil -u WindowsChat.exe

本文地址:http://www.cnblogs.com/liuxiaobo93/p/7205904.html   未经允许不得转载!

基于Windows服务的聊天程序的更多相关文章

  1. 基于Windows服务的聊天程序(一)

    本文将演示怎么通过C#开发部署一个Windows服务,该服务提供各客户端的信息通讯,适用于局域网.采用TCP协议,单一服务器连接模式为一对多:多台服务器的情况下,当客户端连接数超过预设值时可自动进行负 ...

  2. Glue4Net简单部署基于win服务的Socket程序

    smark 专注于高并发网络和大型网站架规划设计,提供.NET平台下高吞吐的网络通讯应用技术咨询和支持 Glue4Net简单部署基于win服务的Socket程序 在写一些服务应用的时候经常把要它部署到 ...

  3. asp.net基于windows服务实现定时发送邮件的方法

    本文实例讲述了asp.net基于windows服务实现定时发送邮件的方法.分享给大家供大家参考,具体如下: //定义组件 private System.Timers.Timer time; publi ...

  4. vs 2010创建Windows服务定时timer程序

    vs 2010创建Windows服务定时timer程序: 版权声明:本文为搜集借鉴各类文章的原创文章,转载请注明出处:  http://www.cnblogs.com/2186009311CFF/p/ ...

  5. 制作Windows服务和安装程序(C#版)

    http://blog.sina.com.cn/s/blog_5f4ffa170100vt2b.html 1.创建服务项目: 打开VS 2005 编程环境,在C#中新建Windows服务程序 2.将安 ...

  6. 利用TCP协议,实现基于Socket的小聊天程序(初级版)

    TCP TCP (Transmission Control Protocol)属于传输层协议.其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送.可靠性.有效流控.全双工操作和多路复用 ...

  7. 为C# Windows服务添加安装程序

    最近一直在搞Windows服务,也有了不少经验,感觉权限方面确定比一般程序要受限很多,但方便性也很多.像后台运行不阻塞系统,不用用户登录之类.哈哈,扯远了,今天讲一下那个怎么给Windows服务做个安 ...

  8. 第十三篇 一个安装、管理windows服务的桌面程序

    在网上看到一个修改程序入口的程序去把windows 服务修改成控制台的程序,然后利用控制台的程序把服务安装和管理,也想起自己原来也写了一个对windows 报务管理的程序,不过是winform的.   ...

  9. windows服务与自启动程序的区别(转载)

    转载:http://blog.csdn.net/anddy926/article/details/8464142 在客户端服务器项目实践中,作为服务端必须保持程序的24小时不间断运行,需要做一个监控, ...

随机推荐

  1. SQL-W3School-高级:SQL FOREIGN KEY 约束

    ylbtech-SQL-W3School-高级:SQL FOREIGN KEY 约束 1.返回顶部 1. SQL FOREIGN KEY 约束 一个表中的 FOREIGN KEY 指向另一个表中的 P ...

  2. Eclipse的视窗

    PackageExplorer 显示项目结构,包,类,及资源 Outline 显示类的结构,方便查找,识别,修改 Console 程序运行的结果在该窗口显示 Hierarchy 显示Java继承层次结 ...

  3. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_03-vuejs研究-vuejs基础-入门程序

    本次测试我们在门户目录中创建一个html页面进行测试,正式的页面管理前端程序会单独创建工程. 在门户目录中创建vuetest目录,并且在目录下创建vue_01.html文件 <!DOCTYPE ...

  4. Qt编写安防视频监控系统11-动态换肤

    一.前言 Qt中的动态换肤技术是非常一流的,直接调用qApp->setStyleSheet(qss);就可以对整个应用程序进行换肤,如果样式表内容不多,或者对应的贴图不对,效率还是蛮好的,不过据 ...

  5. 目标检测 - TridentNet

    转载: https://zhuanlan.zhihu.com/p/54334986 http://haha-strong.com/2019/07/25/20190725-TridentNet/ 开源代 ...

  6. Oracle查询序列和函数方法封装

    工具方法: @Autowired private JdbcTemplate businessTpmeJdbcTemplate; @SuppressWarnings("deprecation& ...

  7. Delphi10.2安装过程详解

    下载好的Delphi10.2是iso镜像,使用虚拟光驱,快速装载后,提示安装   运行setup.exe安装程序,选择安装语言——点击OK,最好关闭网络和退出其他软件   勾选同意条款,点击next ...

  8. golang web框架设计3:controller设计

    继续学习golang web框架设计 controller作用 MVC设计模式里面的这个C,控制器. Model是后台返回的数据: View是渲染页面,通常是HTML的模板页面: Controller ...

  9. RMQ问题--ST

    #include<iostream> #include<cstdio> #include<cmath> using namespace std; ; ]; int ...

  10. mysql写存储过程根据时间变化增加工龄

    在工作中遇到要程序根据时间自动增加工龄的需求. php没办法自己发起请求,又不想在服务器上写计划任务crontab,通过用户请求来更改又不能保证用户会去操作. 用数据库的存储过程和事件来完成. 数据库 ...