標籤:LTP 控制 block dom html nas img sig 建立
背景
由於最近公司要做小程式聊天,所以.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請求需要特殊配置。
找營運同學配置了下,配置完後告訴我這個連結以後只能進行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,從而實現多台伺服器間的通訊。所以我才有上面的疑問。 但這樣有個弊端,就是無法監控用戶端何時斷開。
所以我提了個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 國際」創作共用協議,轉載請在文章明顯位置註明作者及出處。
.NetCore SignalR 踩坑記