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