.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>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现