更新說明
2019-10-11
- 跨域允許AllowAnyOrigin可以使用以下方案
services.AddCors(options => options.AddPolicy("SignalR", builder => { builder.AllowAnyMethod() .AllowAnyHeader() .SetIsOriginAllowed(str => true) .AllowCredentials(); }));
2. 使用以下方式啟用MessagePack
-
- 后台安裝
Microsoft.AspNetCore.SignalR.Protocols.MessagePack
包 - js 安裝
npm install @aspnet/signalr-protocol-msgpack
- 后台安裝
//后台新增如下代碼 AddSignalr().AddMessagePackProtocol(); //js端新增如下代碼 const connection = new signalR.HubConnectionBuilder() .withUrl("/chatHub") .withHubProtocol(new signalR.protocols.msgpack.MessagePackHubProtocol())//新增這一行 .build();
3.0新增功能
- signalr hub注冊方式改變,轉移到
UseEndpoints
中
//<=2.2.0方式 app.UseSignalR(routes => { routes.MapHub<ChatHub>("/chat"); }); //3.0方式, app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>(); });
- 使用
NewtonsoftJson
1. 安裝AspNetCore SignalR NewtonsoftJson
-
2 . 使用以下代碼進行添加
services.AddSignalR().AddNewtonsoftJsonProtocol(...);
- 斷線重連
3.1可以通過傳遞基於毫秒的持續時間數組來指定重新連接間隔:
.withAutomaticReconnect([0, 3000, 5000, 10000, 15000, 30000])
3.2 以下代碼在嘗試連接時使用 onreconnecting 更新 UI:
connection.onreconnecting((error) => { const status = `Connection lost due to error "${error}". console.log(error); });
3.3 以下代碼在連接時使用 onreconnected 更新 UI:
connection.onreconnected((connectionId) => { //todo });
2019-5-27
1. 看到好多人需求微信客服端,特地傳到github上,修改版本為編譯后的js修改,需要的自取。github地址
2. NetCore 2.2版本開始,跨域需要使用WithOrigins(domins)指定特定域名才能訪問。AllowAnyOrigin 已經失效,具體參考文檔 跨域說明
背景
由於最近公司要做微信小程序聊天,所以.NetFramwork
版本的SignalR
版本的不能用了。因為小程序里沒有windows
對象,導致JQuery
無法使用。而Signalr
的 js客戶端是依賴JQuery
的。
所以看下了Core版本的SignarlR
,經過測試,發現可以在微信中運行,不過要將JS客戶端中的webscoekt
改為微信自家的。
目的
本文的主要目的是為了介紹下使用.NetCore
版本SignalR
的一些坑,並提供了解決方式。主要是以前的大部分文章只是簡單的官方demo介紹。沒有真正投入使用,其中一些細小問題沒有進行深入挖掘並進行處理。
跨域問題
.Net Frmawork
版本很簡單,引用相應的包,只要加上AddCores()
就行了,而Core版本的則控制的更加精確。如下ConfigureServices
添加如下代碼
services.AddCors(options => options.AddPolicy("SignalR", builder => { builder.AllowAnyMethod() //允許任意請求方式 .AllowAnyHeader() //允許任意header .AllowAnyOrigin() //允許任意origin .AllowCredentials();//允許驗證 //.WithOrigins(domins) //指定特定域名才能訪問 }));
然后在Configure
使用定義好的跨域策略
app.UseCors("SignalR");
使用Redis Scale Out
和.Net Framwork
一樣,.NetCore版本SignalR
可以使用Redis在多台服務器間通信。但是如果redis沒有連接成功,程序不會報錯,但是通訊不能正常使用。而.Net Framwork
版本的話,SignalR
的地址直接404.
所以我想在啟動時候就監控Redis是否連接成功。但SignalR
的官方文檔只有簡單使用,連Redis
怎么進行配置都沒有。所以只能去最大的交友網站去找。一條條翻看issue,終於發現怎么監控了。
要用以下代碼進行配置,就可以監控Redis
是否連接成功了.
services.AddSignalR() .AddMessagePackProtocol() .AddRedis(o => { o.ConnectionFactory = async writer => { var config = new ConfigurationOptions { AbortOnConnectFail = false }; config.EndPoints.Add(IPAddress.Loopback, 0); config.SetDefaultPorts(); var connection = await ConnectionMultiplexer.ConnectAsync(config, writer); connection.ConnectionFailed += (_, e) => { Console.WriteLine("Connection Redis failed."); }; if (!connection.IsConnected) { Console.WriteLine("Connection did not connect."); } return connection; }; });
但是發現用這種方式,Redis
連接了2次,按道理不應該額。加上我事情多,沒空研究源代碼。所以就在這條issue里直接問作者。到現在還沒找到原因。詳情可以看上面的鏈接。
WebSocket 負載均衡配置
使用負載均衡對請求轉發的話,需要對WebSocket請求特殊配置。
找運維同學配置了下,配置完后告訴我這個SingllR的通訊地址以后只能GET請求,不能POST請求了。手動黑人問號。。。
這樣的話只能用WebSocket
方式了,像LongPollin
及SSE
協議都不能用了。
我去,這么坑嗎?於是讓運維把配置代碼發我,如下
proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_connect_timeout 300; proxy_read_timeout 300; proxy_send_timeout 300;
於是我把應用發布到本地虛擬機里,並用docker
方式運行。然后把配置寫進nginx配置文件里。
發現真的不能進行POST請求了,返回400
。400
的意是思請求異常。肯定是這個配置有問題額。於是又去交友網站找issue,果然又讓我找到了。 在一個issue里面,提供的配置如下
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $http_connection;
不同點在於proxy_set_header Connection
,沒有寫死,於是我把配置改了下,果然好了。
原來proxy_set_header Connection
不能寫死,要從請求頭里面獲取。這樣其他請求方式也就沒啥問題了。
ConnectionId獲取
在JS
客戶端代碼里,沒有再提供ConnectionId的獲取。也就是如果要用的話,需要自己改源碼加上。改是沒問題,但是微軟那群大神不應該犯這么低級的錯誤。ConnectionId
明明在negotiate
請求時候返回了,為什么不開放呢?難道是bug?不應該有這么低級的bug吧。
於是又去看issues,果然,里面也有人問,作者也有解釋。
大體意思ConnectonId
是服務端使用,客戶端不應該使用這種不可控的方式進行通信。 可以采用Group
或者User
這種可控方式通信,並且也有例子給出。
這里插一句,在使用
.Net Framwork
版本時候,我們網站是使用ConnectionId
進行通信,經常出現重連導致ConnectionId
變掉,進而通信失敗。
所以我也調整了下設計思路,改使用Group
進行通信。
以上都搞定了,辛苦了這么久,按道理應該沒問題了吧!那么發布上線!
大坑來了
應用我本地測試一切正常,測試機也沒有問題,於是就發到生產環境,結果問題出現了。
因為本地和測試環境都是單台服務器,測試沒問題。而到了生產環境,服務器有多台。 不管我JS怎么設置,總會在執行完negotiate
請求后,接下來的連接請求肯定404,並且返回No Connection with that Id
。
如下圖
看到這個錯誤,第一個反應,我的想法是難道是Redis
沒連接成功,所以只能單機跑?所以我就在上面Redis代碼加上各種監控,發現連接成功了。代碼Review了n遍代碼,實在沒有地方可以改了。
於是官方文檔一個個過。終於發現Js可以進行以下配置
let connection = new signalR.HubConnectionBuilder() .withUrl("/myhub", { skipNegotiation: true, transport: signalR.HttpTransportType.WebSockets }); .build();
上面代碼意思是跳過negotiate
握手操作,直接使用WebSocket
進行連接。
按照文檔配置了,我去,還真的可以。因為只發送了一條請求就建立了通信連接。
這下我就不淡定了,難道只能部署一台服務器嗎?這下穩定性怎么保證?這個還是用在微信小程序里的(js客戶端進行了修改),低版本不能用Websocket,難道低版本就不管了嗎?流量大了不加機器怎么抗的住?難道要換方案自己擼一套通訊嗎?
沒辦法,只能上大招。把源碼clone下來,花了點時間看了下,找到如下代碼
private async Task<HttpConnectionContext> GetConnectionAsync(HttpContext context) { var connectionId = GetConnectionId(context); if (StringValues.IsNullOrEmpty(connectionId)) { // There's no connection ID: bad request context.Response.StatusCode = StatusCodes.Status400BadRequest; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("Connection ID required"); return null; } if (!_manager.TryGetConnection(connectionId, out var connection)) { // No connection with that ID: Not Found context.Response.StatusCode = StatusCodes.Status404NotFound; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("No Connection with that ID"); return null; } return connection; }
這段代碼啥意思呢?就是connection在本地沒找到的話,就返回404!
我去,難道是代碼bug?
額外補充一下
在
.Net Framwork
版本里,源碼里面會對ConnectionId
進行驗證。驗證通過,但本地找不到connection的話,就會新建一個connection,從而實現多台服務器間的通訊。所以我才有上面的疑問。 但這樣有個弊端,就是無法監控客戶端何時斷開。這里驗證通過是需要在Web.config文件里設置相同的machinekey。如果不設置,也是需要進行會話保持的。現在直接是把這個功能砍掉了。
所以我提了個issue問作者。 戳我看明細
得到的回復是
It's not a bug it's by design. ASP.NET Core SignalR requires sticky sessions when using scale out. This means you need to pin a connection to a particular serve
啥意思呢?就是這不是bug,就是這么設計的。使用SignalR
時,要進行會話保持,請求要一直落到同一台服務器上。這樣更穩定,並且還可以實時監控客戶端的情況。
於是找運維同學在負載上配置了下會話保持,再次測試,終於可以了。
總結
在此次使用SignalR
的過程中,遇到了太多的坑。花了幾個小時整理並記錄下來,與各位進行分享。希望能幫到那些准備或者有打算使用.Net Core的.Neter
作者:cgyqu
出處:https://www.cnblogs.com/cgyqu/p/9563193.html
本站使用「署名 4.0 國際」創作共享協議,轉載請在文章明顯位置注明作者及出處。