WebSocket Client鏈接AspNetCore SignalR Json Hub

忽然有個需求,須要使用普通的websocket客戶端去鏈接SignalR服務器。javascript

由於使用的是.net core 版的signalr,目前對於使用非signalr客戶端鏈接的中文文檔幾乎爲0,在gayhub折騰幾天總算折騰出來了。java

 

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

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請求頭中加入了一個字段json

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啓動成功了。api

   對於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協議和遠程調用方式。未完待續

相關文章
相關標籤/搜索