• WebSocket Client连接AspNetCore SignalR Json Hub


    突然有个需求,需要使用普通的websocket客户端去连接SignalR服务器。

    因为使用的是.net core 版的signalr,目前对于使用非signalr客户端连接的中文文档几乎为0,在gayhub折腾几天总算折腾出来了。

    首先,在startup.cs的ConfigureServices方法中添加signalr配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    services.AddSignalR(options =>
                {
                    // Faster pings for testing
                    options.KeepAliveInterval = TimeSpan.FromSeconds(5);//心跳包间隔时间,单位 秒,可以稍微调大一点儿
                }).AddJsonProtocol(options =>
                {
                    //options.PayloadSerializerSettings.Converters.Add(JsonConver);
                    //the next settings are important in order to serialize and deserialize date times as is and not convert time zones
                    options.PayloadSerializerSettings.Converters.Add(new IsoDateTimeConverter());
                    options.PayloadSerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Unspecified;
                    options.PayloadSerializerSettings.DateParseHandling = DateParseHandling.DateTimeOffset;
                });

    在使用微信小程序的websocket的时候,可以在websocket请求头中加入了一个字段

    Sec-WebSocket-Protocol: protocol1
    这样没有返回这个协议头的服务器就无法连接的。
    要求服务器在返回的时候也需要在头中返回对应协议头。
    在startup.cs的Configure方法中配置Hub时加入子协议。   需要注意一下Chat.SignalR.Chat是命名空间+类名,也就是下边要写的Chat.cs
    复制代码
    app.UseSignalR(routes =>
                {
                    routes.MapHub<AbpCommonHub>("/signalr", options => options.WebSockets.SubProtocolSelector = requestedProtocols =>
                    {
                        return requestedProtocols.Count > 0 ? requestedProtocols[0] : null;
                    }); routes.MapHub<Chat.SignalR.Chat>("/chat", options => options.WebSockets.SubProtocolSelector = requestedProtocols =>
                    {
                        return requestedProtocols.Count > 0 ? requestedProtocols[0] : null;
                    });
    复制代码

    然后是Chat.cs.  因为我用的是Abp.AspNetCore.SignalR,所以在写法上会略微和aspnetcore.signalr有区别

    复制代码
    using Abp.Dependency;
    using Abp.Runtime.Session;
    using Castle.Core.Logging;
    using Microsoft.AspNetCore.SignalR;
    using System;
    using System.Threading.Tasks;
    public class Chat : Hub, ITransientDependency { public IAbpSession AbpSession { get; set; } public ILogger Logger { get; set; } public Chat() { AbpSession = NullAbpSession.Instance; Logger = NullLogger.Instance; } public async Task SendMessage(string message) { await Clients.All.SendAsync("getMessage", string.Format("User {0}: {1}", AbpSession.UserId, message)); } public override async Task OnConnectedAsync() { await base.OnConnectedAsync(); Logger.Debug("A client connected to MyChatHub: " + Context.ConnectionId); } public override async Task OnDisconnectedAsync(Exception exception) { await base.OnDisconnectedAsync(exception); Logger.Debug("A client disconnected from MyChatHub: " + Context.ConnectionId); } public void log(string arg) { Logger.Info("client send:" + arg); }
    复制代码

    这样在浏览器输入http://myhost/chat (myhost换成自己的域名  或者是ip:端口)

    出现  Connection ID required 就说明signalr启动成功了。

       对于websocket客户端来说,服务器连接地址就是把http改为ws就可以了。如http://192.168.1.100:21012/chat,则对应的连接地址就是ws://192.168.1.100:21012/chat

    然后是客户端代码。这里我使用的是ClientWebSocket

    复制代码
     1 ClientWebSocket client = new ClientWebSocket();
     2 client.Options.AddSubProtocol("protocol1");
     3 wait client.ConnectAsync(new Uri(BaseUrl), CancellationToken.None);
     4 Console.WriteLine("Connect success");
     5 
     6 await client.SendAsync(new ArraySegment<byte>(AddSeparator(Encoding.UTF8.GetBytes(@"{""protocol"":""json"", ""version"":1}")))
     7       , WebSocketMessageType.Text, true, CancellationToken.None);//发送握手包
     8 Console.WriteLine("Send success");
     9 var bytes = Encoding.UTF8.GetBytes(@"{
    10     ""type"": 1,
    11   ""invocationId"":""123"",
    12     ""target"": ""log"",
    13     ""arguments"": [
    14         ""Test Message""
    15     ]
    16     }"")");//发送远程调用 log方法
    17  await client.SendAsync(new ArraySegment<byte>(AddSeparator(bytes)), WebSocketMessageType.Text, true, CancellationToken.None);
    18  var buffer = new ArraySegment<byte>(new byte[1024]);
    19  while (true)
    20  {
    21      await client.ReceiveAsync(buffer, CancellationToken.None);
    22      Console.WriteLine(Encoding.UTF8.GetString(RemoveSeparator(buffer.ToArray())));
    23  }
    复制代码

    添加和删除分隔符方法

    复制代码
    private static byte[] AddSeparator(byte[] data)
    {
        List<byte> t = new List<byte>(data) { 0x1e };//0x1e record separator
        return t.ToArray();
    }
    private static byte[] RemoveSeparator(byte[] data)
    {
        List<byte> t = new List<byte>(data);
        t.Remove(0x1e);
        return t.ToArray();
    }
    复制代码

    然后就能在服务器日志中查看到log方法调用后的结果

    然后是微信小程序的连接代码

    在api.js中定义要连接的url,这里有个小技巧,http的自动替换为ws,https自动替换为wss,这样本地调试和服务器运行都只用修改serverRoot这个url就可以了。

    1
    2
    3
    var _serverRoot = 'https://myhost.com/';
    var websocket = (_serverRoot.startsWith("https") ?
      _serverRoot.replace('https''wss') : _serverRoot.replace('http''ws')) + 'signalr';

    然后定义调用的连接方法。token是在登录的时候调用abp登录方法返回并setStorage,然后就可以在任意页面获取到token。

    这样连接的时候传入token,在signalr的远程调用的时候,abpSession中就能获取到用户信息,同时也避免了无关客户端连接远程调用。

    还需要稍微修改一下ABP的验证部分代码,Host项目中的AuthConfigurer.cs的QueryStringTokenResolver方法,从HttpContext.Request的QueryString中获取accesstoken。

    复制代码
           private static Task QueryStringTokenResolver(MessageReceivedContext context)
            {
                 if (!context.HttpContext.Request.Path.HasValue ||
                    !context.HttpContext.Request.Path.Value.StartsWith("/signalr"))
                {
                    //We are just looking for signalr clients
                    return Task.CompletedTask;
                }
                var qsAuthToken = context.HttpContext.Request.Query["accesstoken"].FirstOrDefault();
                if (qsAuthToken == null)
                {
                    //Cookie value does not matches to querystring value
                    return Task.CompletedTask;
                }
                //Set auth token from cookie
                context.Token = qsAuthToken;//SimpleStringCipher.Instance.Decrypt(qsAuthToken, AppConsts.DefaultPassPhrase);
                return Task.CompletedTask;
            }
    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function WsConnect(){
      var token = wx.getStorageSync('token');
      var url = {
        url: api.websocket + '?accesstoken=' + token,
        header: {
          'Abp.TenantId': 2,
          'Content-Type''application/json'
        }
      };
      wx.connectSocket(url);
    }
     
     
    function wsSend(msg){
      console.log('send:'+msg);
       
      msg += String.fromCharCode(0x1e);
      wx.sendSocketMessage({
        data: msg,
      });
    }

    发送的时候需要在后面添加一个分隔符0x1e。所以稍微封装了一下。

    然后就是接收处理和断开重连处理。需要注意一下,signalr连接成功后要发送握手包指定协议。{"protocol":"json","version":1}就表示是使用的json

    复制代码
    wx.onSocketClose(function () {
          console.log("链接关闭 ");
          setTimeout(() => util.WsConnect(), 5000);//5s后自动重连
        })
    
    wx.onSocketError(function (res) {
          console.log('WebSocket连接打开失败,请检查!');
          console.log(res);
          setTimeout(() => util.WsConnect(), 5000);//5s后自动重连
        });
    
    
     wx.onSocketOpen(res => {//websocket打开连接成功回调
          util.wsSend('{"protocol":"json","version":1}');//发送握手包
          wx.onSocketMessage(function (res) {//接收消息回调
            var data = res.data.replace(String.fromCharCode(0x1e), "");//返回时去掉分隔符
            console.log("recv:"+data);
        }
    }    
    复制代码

     贴一下能运行查看的小程序代码片段 wechatide://minicode/3YTuJZmP7BYZ

    粘贴这个到微信web开发者工具--导入代码片段中

    修改连接地址

    然后在控制台接收到{“type”:6} 服务器发送的心跳包,就说明signalr连接成功了

    后续将会讲解一下signalr core的Hub协议和远程调用方式。未完待续

  • 相关阅读:
    idea 管理java 多module的工程
    协程与函数线程异步的关系
    HDU 5640 King's Cake【模拟】
    逻辑运算
    Silverlight 学习笔记——布局 Evil 域 博客园
    ExtJS 日期格式问题
    偏方收藏(此信息为本人收藏,安全性无法验证,使用后产生的一些后果自负)
    form和column:extJS的布局
    Sqlserver 通用存储过程分页
    Ext中TreePanel控件和TabPanel控件搭配测试
  • 原文地址:https://www.cnblogs.com/webenh/p/11014860.html
Copyright © 2020-2023  润新知