.NET CORE 2.2创建WebSocket Windows服务

北战南征 提交于 2020-11-23 06:14:13

作为自己的第一个上线的.Net Core程序,踩得坑还是比较多的,这个程序主要用到了以下几平时没有接触到的方面

开发环境,.Net Core2.2,VS2019

 

Topshelf


Topshelf 是一个开源的跨平台的宿主服务框架,支持Windows和Mono,只需要几行代码就可以构建一个很方便使用的服务宿主。
使用Topshelf可以非常方便的将一个C#控制台程序部署成为一个Windows Service,使用它可以很方便的构建跨平台服务寄主,而在调试时直接以控制台的形式运行即可,非常方便。
  • 首先,通过Nuget安装Topshelf ,我安装的是4.2.0
  • 编写控制台的main函数
System.IO.Directory.SetCurrentDirectory(System.AppDomain.CurrentDomain.BaseDirectory);
var rc = HostFactory.Run(x =>                                   
{
  x.Service<WebSocketService>(s =>
  {
      s.ConstructUsing(name => new WebSocketService());
      s.WhenStarted(tc => tc.Start());
      s.WhenStopped(tc => tc.Stop());
  });
  x.RunAsLocalSystem();
  x.SetDescription("ServiceDescription");
  x.SetDisplayName("ServiceDisplayName");
  x.SetServiceName("ServiceName");
});
var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode());
Environment.ExitCode = exitCode;
WebSocketService:服务的业务逻辑类
tc.Start():WebSocketService.Start(),服务启动时执行的逻辑。
tc.Stop():WebSocketService.Stop(),服务停止时执行的逻辑。

然后将发布好的控制台程序拷贝到生产环境(我用将控制台程序打包成exe文件独立部署),使用cmd运行
WebSocket.exe install

即可注册为windows服务。

 

 

编写webSocket服务


 总体来说就是在控制台程序中创建一个webhost,监听指定的端口号。由于目前supersocket还没有支持Core,所以使用了微软官方的一些做法。参考了群友的GitHub https://github.com/2881099/im


创建
WebSocketHandler处理webSocket
public class WebSocketHandler
    {

        WebSocketHandler(WebSocket socket, string uid)
        {
            this.socket = socket;
            this.uid = uid;
        }

        static object _websockets_lock = new object();
        static Dictionary<string, List<WebSocketHandler>> _websockets = new Dictionary<string, List<WebSocketHandler>>();

        static async Task Acceptor(HttpContext hc, Func<Task> n)
        {if (!hc.WebSockets.IsWebSocketRequest) return;

            string token = hc.Request.Query["token"];
            if (string.IsNullOrEmpty(token)) return;
            var socket = await hc.WebSockets.AcceptWebSocketAsync();
            var sh = new WebSocketHandler(socket, token);
            List<WebSocketHandler> list = null;
            lock (_websockets_lock)
            {
                if (_websockets.TryGetValue(token, out list) == false)
                    _websockets.Add(token, list = new List<WebSocketHandler>());
                list.Add(sh);
            }

            var buffer = new byte[BufferSize];
            var seg = new ArraySegment<byte>(buffer);
            try
            {
                while (socket.State == WebSocketState.Open && _websockets.ContainsKey(token))
                {
                    var incoming = await socket.ReceiveAsync(seg, CancellationToken.None);
                    var outgoing = new ArraySegment<byte>(buffer, 0, incoming.Count);
                }
                socket.Abort();
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            lock (_websockets_lock)
            {
                list.Remove(sh);
                if (list.Count == 0) _websockets.Remove(token);
            }
        }

        /// <summary>发送消息
        /// 
        /// </summary>
        /// <param name="msg"></param>
        public static void SendMessage(string msg) {
            try
            {
                lock (_websockets_lock)
                {
                    var outgoing = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg));
                    foreach (var item in _websockets)
                    {
                        foreach (var sh in item.Value)
                        {
                            sh.socket.SendAsync(outgoing, WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                log.Error("SendMessage:" + ex.Message);
                throw;
            }
        }

        public static void Map(IApplicationBuilder app)
        {
            app.UseWebSockets();
            app.Use(WebSocketHandler.Acceptor);
        }
    }
然后创建Startup.cs,主要内容如下
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
  try
  {
    app.Map("/ws", WebSocketHandler.Map);
  }
  catch (Exception ex)
  {

  }           
}
修改 WebSocketService的Start
public bool Start()
        {
            try
            {
                var isService = !(Debugger.IsAttached);
                var builder = CreateWebHostBuilder(null);
                if (isService)
                {
                    var pathToExe = Process.GetCurrentProcess().MainModule.FileName;
                    var pathToContentRoot = Path.GetDirectoryName(pathToExe);
                    builder.UseContentRoot(pathToContentRoot);
                }
                var host = builder.Build();
                host.Start();
          return true; }
catch (Exception ex) { log.Error("OnStart:" + ex.Message); } }

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>().UseUrls("http://内网地址:8082/");

 

 这里有几个地方需要注意
1.由于windows服务在开发过程中,最麻烦的是Debug,在使用Topshelf的情况下,使用
Debugger.IsAttached来区分是否是Debug。
2.如果使用host.Run()的话,由于他是个阻塞的方法,会导致服务启动时报错,Windows 无法启动xx服务 错误1053:服务没有及时响应启动或控制请求.这时websocket的监听已经启动,并可以运行。
 所以应采用Start函数,它是一个非阻塞的函数。如果非要使用Run函数,可以将上述的代码封装一个函数,另起一个线程运行。
3.如果是运行在阿里云的ECS上,监听的IP应写内网IP,否则无法被访问到。(待验证)



JS代码


<script>

var ws = new WebSocket("ws://IP:8082/ws/pre-connect);
ws.onopen = function(evt) {
    console.log("Connection open ...");
    ws.send("Hello WebSockets!");
};
 
ws.onmessage = function(evt) {
    console.log("Received Message: " + evt.data);
};
 
ws.onclose = function(evt) {
    console.log("Connection closed.");
}
</script>

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!