C#基于Windows服务的聊天程序(1)

 更新时间:2020年6月25日 11:18  点击:1649

本文将演示怎么通过C#开发部署一个Windows服务,该服务提供各客户端的信息通讯,适用于局域网。采用TCP协议,单一服务器连接模式为一对多;多台服务器的情况下,当客户端连接数超过预设值时可自动进行负载转移,当然也可手动切换服务器,这种场景在实际项目中应用广泛。

简单的消息则通过服务器转发,文件类的消息则让客户端自己建立连接进行传输。后续功能将慢慢完善。

自定义协议:

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 = 11011, int backlog = 100)
    {
      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 = 0;
        while (true)
        {
          byte[] buffer = new byte[10240];
          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 == 0)
          {
            Clients.Remove(param);
            WriteLog("客户端:" + remoteName + "断开连接2");
            BroadCast("REMOVE|" + remoteName);
            param.Close();
            return;
          }
          var clientMsg = Encoding.UTF8.GetString(buffer, 0, 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[1]);
            if (toSocket != null)
            {
              WriteLog(remoteName + "发给" + msgs[1] + "的消息" + msgs[2]);
              toSocket.Send(Encoding.UTF8.GetBytes("TRANSF|" + remoteName + "|" + msgs[2]));
            }
          }
        }
      }
    }
  }
}

重写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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持猪先飞。

[!--infotagslink--]

相关文章

  • Windows VPN服务器配置图文教程 超详细版

    VPN可以虚拟出一个专用网络,让远处的计算机和你相当于处在同一个局域网中,而中间的数据也可以实现加密传输,用处很大,特别是在一些大公司,分公司处在不同的区域。...2016-01-27
  • C#实现简单的登录界面

    我们在使用C#做项目的时候,基本上都需要制作登录界面,那么今天我们就来一步步看看,如果简单的实现登录界面呢,本文给出2个例子,由简入难,希望大家能够喜欢。...2020-06-25
  • 浅谈C# 字段和属性

    这篇文章主要介绍了C# 字段和属性的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下...2020-11-03
  • C#中截取字符串的的基本方法详解

    这篇文章主要介绍了C#中截取字符串的的基本方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-11-03
  • C#实现简单的Http请求实例

    这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
  • C#连接SQL数据库和查询数据功能的操作技巧

    本文给大家分享C#连接SQL数据库和查询数据功能的操作技巧,本文通过图文并茂的形式给大家介绍的非常详细,需要的朋友参考下吧...2021-05-17
  • C#中new的几种用法详解

    本文主要介绍了C#中new的几种用法,具有很好的参考价值,下面跟着小编一起来看下吧...2020-06-25
  • 使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序)

    这篇文章主要介绍了使用Visual Studio2019创建C#项目(窗体应用程序、控制台应用程序、Web应用程序),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • C#开发Windows窗体应用程序的简单操作步骤

    这篇文章主要介绍了C#开发Windows窗体应用程序的简单操作步骤,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-12
  • C#从数据库读取图片并保存的两种方法

    这篇文章主要介绍了C#从数据库读取图片并保存的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...2021-01-16
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • 轻松学习C#的基础入门

    轻松学习C#的基础入门,了解C#最基本的知识点,C#是一种简洁的,类型安全的一种完全面向对象的开发语言,是Microsoft专门基于.NET Framework平台开发的而量身定做的高级程序设计语言,需要的朋友可以参考下...2020-06-25
  • C#变量命名规则小结

    本文主要介绍了C#变量命名规则小结,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-09-09
  • C#绘制曲线图的方法

    这篇文章主要介绍了C#绘制曲线图的方法,以完整实例形式较为详细的分析了C#进行曲线绘制的具体步骤与相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C# 中如何取绝对值函数

    本文主要介绍了C# 中取绝对值的函数。具有很好的参考价值。下面跟着小编一起来看下吧...2020-06-25
  • c#自带缓存使用方法 c#移除清理缓存

    这篇文章主要介绍了c#自带缓存使用方法,包括获取数据缓存、设置数据缓存、移除指定数据缓存等方法,需要的朋友可以参考下...2020-06-25
  • c#中(&&,||)与(&,|)的区别详解

    这篇文章主要介绍了c#中(&&,||)与(&,|)的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • C#学习笔记- 随机函数Random()的用法详解

    下面小编就为大家带来一篇C#学习笔记- 随机函数Random()的用法详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • 经典实例讲解C#递归算法

    这篇文章主要用实例讲解C#递归算法的概念以及用法,文中代码非常详细,帮助大家更好的参考和学习,感兴趣的朋友可以了解下...2020-06-25