欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-12-14 19:34:52
本文将演示怎么通过c#开发部署一个windows服务,该服务提供各客户端的信息通讯,适用于局域网。采用tcp协议,单一服务器连接模式为一对多;多台服务器的情况下,当客户端连...

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

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

自定义协议:

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

1.新建windows服务项目

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

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

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

修改serviceinstaller1和serviceprocessinstaller1的属性信息如图

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

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

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

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

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

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

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

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

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

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

则部署脚本为

  cd c:\bin\

  installutil windowschat.exe

  卸载脚本

  installutil -u windowschat.exe

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

上一篇:

下一篇: