.NET CORE 2.2創建WebSocket Windows服務


作為自己的第一個上線的.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>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM