当前位置: 首页 > news >正文

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协议和远程调用方式。未完待续

 

转载于:https://www.cnblogs.com/webenh/p/11014860.html

相关文章:

  • 精读vue-hooks
  • 扩展SpringMVC以支持更精准的数据绑定1
  • 如何安装部署秋色园QBlog站点
  • Myeclipse优化配置
  • 转换流、缓冲流、流的操作规律
  • TypeScript 学习总结 函数 接口 (二)
  • javaweb期末项目-stage3-项目测试和发布
  • RabbitMQ安装配置-01
  • JDBC详解
  • 浏览器缓存与pageshow监听掉坑
  • Tcp/IP 端口耗尽
  • Hadoop综合大作业
  • Python3.7.1学习(三)求两个list的差集、并集与交集
  • Palindrome Number
  • pod 指令无效
  • Apache的基本使用
  • emacs初体验
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • java 多线程基础, 我觉得还是有必要看看的
  • spring cloud gateway 源码解析(4)跨域问题处理
  • supervisor 永不挂掉的进程 安装以及使用
  • 大型网站性能监测、分析与优化常见问题QA
  • 前端攻城师
  • 如何用vue打造一个移动端音乐播放器
  • 深入 Nginx 之配置篇
  • 算法---两个栈实现一个队列
  • 通过git安装npm私有模块
  • 原生 js 实现移动端 Touch 滑动反弹
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • 《天龙八部3D》Unity技术方案揭秘
  • 关于Kubernetes Dashboard漏洞CVE-2018-18264的修复公告
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • # include “ “ 和 # include < >两者的区别
  • #define
  • $().each和$.each的区别
  • (1)虚拟机的安装与使用,linux系统安装
  • (Python) SOAP Web Service (HTTP POST)
  • (附源码)ssm智慧社区管理系统 毕业设计 101635
  • (三)elasticsearch 源码之启动流程分析
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (数位dp) 算法竞赛入门到进阶 书本题集
  • (淘宝无限适配)手机端rem布局详解(转载非原创)
  • (转)JAVA中的堆栈
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • ../depcomp: line 571: exec: g++: not found
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .Net CF下精确的计时器
  • .NET连接数据库方式
  • .Net转前端开发-启航篇,如何定制博客园主题
  • @Conditional注解详解
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [C++]命名空间等——喵喵要吃C嘎嘎
  • [Django 0-1] Core.Handlers 模块
  • [Flex][问题笔记]TextArea滚动条问题
  • [HarekazeCTF2019]encode_and_encode 不会编程的崽