作为自己的第一个上线的.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>
来源:oschina
链接:https://my.oschina.net/u/4273421/blog/3587413