基于Windows服务的聊天程序
本文将演示怎么通过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服务的聊天程序的更多相关文章
- 基于Windows服务的聊天程序(一)
本文将演示怎么通过C#开发部署一个Windows服务,该服务提供各客户端的信息通讯,适用于局域网.采用TCP协议,单一服务器连接模式为一对多:多台服务器的情况下,当客户端连接数超过预设值时可自动进行负 ...
- Glue4Net简单部署基于win服务的Socket程序
smark 专注于高并发网络和大型网站架规划设计,提供.NET平台下高吞吐的网络通讯应用技术咨询和支持 Glue4Net简单部署基于win服务的Socket程序 在写一些服务应用的时候经常把要它部署到 ...
- asp.net基于windows服务实现定时发送邮件的方法
本文实例讲述了asp.net基于windows服务实现定时发送邮件的方法.分享给大家供大家参考,具体如下: //定义组件 private System.Timers.Timer time; publi ...
- vs 2010创建Windows服务定时timer程序
vs 2010创建Windows服务定时timer程序: 版权声明:本文为搜集借鉴各类文章的原创文章,转载请注明出处: http://www.cnblogs.com/2186009311CFF/p/ ...
- 制作Windows服务和安装程序(C#版)
http://blog.sina.com.cn/s/blog_5f4ffa170100vt2b.html 1.创建服务项目: 打开VS 2005 编程环境,在C#中新建Windows服务程序 2.将安 ...
- 利用TCP协议,实现基于Socket的小聊天程序(初级版)
TCP TCP (Transmission Control Protocol)属于传输层协议.其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送.可靠性.有效流控.全双工操作和多路复用 ...
- 为C# Windows服务添加安装程序
最近一直在搞Windows服务,也有了不少经验,感觉权限方面确定比一般程序要受限很多,但方便性也很多.像后台运行不阻塞系统,不用用户登录之类.哈哈,扯远了,今天讲一下那个怎么给Windows服务做个安 ...
- 第十三篇 一个安装、管理windows服务的桌面程序
在网上看到一个修改程序入口的程序去把windows 服务修改成控制台的程序,然后利用控制台的程序把服务安装和管理,也想起自己原来也写了一个对windows 报务管理的程序,不过是winform的. ...
- windows服务与自启动程序的区别(转载)
转载:http://blog.csdn.net/anddy926/article/details/8464142 在客户端服务器项目实践中,作为服务端必须保持程序的24小时不间断运行,需要做一个监控, ...
随机推荐
- php判断两个数组是否相等
php判断两个数组是否相等 一.总结 一句话总结: php判断两个数组是否相等可以直接上==或者===号 二.php 判断两个数组是否相等 转自或参考:php 判断两个数组是否相等https://ww ...
- How to appraise Hearthstone card values
https://elie.net/blog/hearthstone/how-to-appraise-hearthstone-card-values/ In 2014, I became an avid ...
- 安装jdk1.8.0_11环境脚本
安装jdk1.8.0_11的脚本,具体的版本可在脚本中调整,发现最后的重置环境变量没生效,还得再终端界面source /etc/profile [root@ZFVM-APP-- ~]# vim jdk ...
- Linux 之Shell for循环
@代表所有参数所以如果后面跟上echo $v你会发现他会一次显示user userdebug eng $poo -le ${#prodlist[@]} 这句话是说 $poo小于等于prodlist中的 ...
- JavaWeb MySQL 实现登录验证
0. 环境准备 项目创建: IDEA 创建 Servlet 项目详细步骤:https://www.jianshu.com/p/386a79d16e05 导入 MySQL 驱动包: Java MySQL ...
- Queue class
#pragma once#include <iostream>#include <iomanip> using namespace std; class Queue{ stru ...
- scikit-learn机器学习(一)简单线性回归
# -*- coding: utf-8 -*- import numpy as np import matplotlib.pyplot as plt ## 设置字符集,防止中文乱码 import ma ...
- CockroachDB学习笔记——[译]CockroachDB是如何进行分布式原子事务的
原文:How CockroachDB Does Distributed, Atomic Transactions 原文链接:https://www.cockroachlabs.com/blog/how ...
- PyToune:一款类Keras的PyTorch框架
PyToune is a Keras-like framework for PyTorch and handles much of the boilerplating code needed to t ...
- checklist和基础安全知识
checklist和基础安全知识 https://book.yunzhan365.com/umta/rtnp/mobile/index.html 网络安全科普小册子 http://sec.cuc.ed ...