SignalR ASP.NET Core-ის ის ნაწილია, რომელიც სერვერსა და ბრაუზერს შორის კავშირს ღიად ინახავს, რომ სერვერმა შეტყობინებები ისე გაგზავნოს, რომ არავინ სთხოვოს. მისი ჰოსტინგი ერთ სერვერზე მარტივია და საინტერესო ხდება იმ წამიდან, როცა შუაში რამე დგება. proxy-ს უკან მდგარ ერთ ინსტანსს სამი რამ სჭირდება: proxy-მ WebSocket upgrade უნდა გაატაროს, მისი idle timeout SignalR-ის 15-წამიან keep-alive-ზე გრძელი უნდა იყოს, და ავთენტიფიკაცია საკუთარი header-ების გარეშე უნდა მუშაობდეს. ორ ან მეტ ინსტანსს კიდევ ორი სჭირდება: sticky session-ები, ან მხოლოდ WebSocket-ები გამოტოვებული negotiation-ით, და backplane, მაგალითად Valkey, რომ ერთ სერვერზე გაგზავნილი შეტყობინება მეორეზე მყოფ კლიენტებამდე მივიდეს. ეს პოსტი თითოეულს განიხილავს იმ ნაგულისხმევი მნიშვნელობებით, რომლებიც წყვეტს, როდის წყდება კავშირები.
რას აკეთებს SignalR ხაზზე სინამდვილეში#
SignalR კავშირი ჩვეულებრივი HTTP მოთხოვნით იწყება. კლიენტი POST-ს აგზავნის /<hub>/negotiate-ზე, ხოლო სერვერი პასუხობს კავშირის ტოკენით და მის მიერ მხარდაჭერილი transport-ებით. შემდეგ კლიენტი საუკეთესოს ირჩევს, რომელსაც ორივე მხარე იღებს:
| Transport | როგორ მუშაობს | შენიშვნები |
|---|---|---|
| WebSockets | ერთი სრულად ორმხრივი TCP კავშირი HTTP upgrade-ის შემდეგ | სასურველია. ყველაზე დაბალი ხარჯი და დაყოვნება |
| Server-Sent Events | ხანგრძლივი HTTP პასუხი სერვერიდან კლიენტამდე, ცალკე POST-ები კლიენტიდან სერვერამდე | სარეზერვო, როცა WebSocket-ები დაბლოკილია |
| Long Polling | განმეორებითი მოთხოვნები, რომლებიც ღიად რჩება, სანამ მონაცემები გამოჩნდება | უკანასკნელი საშუალება. თითქმის ყველგან მუშაობს, ყველაზე ძვირი ჯდება |
transport-ის თავზე hub პროტოკოლი მუშაობს, ნაგულისხმევად JSON, ან MessagePack, თუ სერვერზე Microsoft.AspNetCore.SignalR.Protocols.MessagePack-ს და შესაბამის კლიენტის პაკეტს დაამატებ. MessagePack შეტყობინებები უფრო პატარაა და მათი parse უფრო იაფია; JSON ბრაუზერის network ჩანართში უფრო ადვილად იკითხება.
მინიმალური სერვერი ორი ხაზი და hub კლასია:
builder.Services.AddSignalR();var app = builder.Build();app.MapHub<ChatHub>("/hubs/chat");public class ChatHub : Hub{ public Task Send(string room, string text) => Clients.Group(room).SendAsync("message", Context.UserIdentifier, text); public Task Join(string room) => Groups.AddToGroupAsync(Context.ConnectionId, room);}ბრაუზერის მხარე კი @microsoft/signalr პაკეტით:
import * as signalR from "@microsoft/signalr";const connection = new signalR.HubConnectionBuilder() .withUrl("/hubs/chat") .withAutomaticReconnect() .build();connection.on("message", (user, text) => render(user, text));await connection.start();await connection.invoke("Join", "general");withAutomaticReconnect() არგუმენტების გარეშე ცდილობს 0, 2, 10 და 30 წამის შემდეგ, შემდეგ ნებდება და onclose-ს იძახებს. reconnect-ის შემდეგ ჯგუფები არ აღდგება, რადგან ხელახლა დაკავშირებულ კლიენტს ახალი connection ID აქვს; ხელახლა შეუერთდი მათ onreconnected handler-ში. ეს ერთი დეტალი ხსნის საჩივრების უმეტესობას "wifi-ს წამიერი გათიშვის შემდეგ შეტყობინებები აღარ მოდის".
Keep-alive-ები და timeout-ები#
SignalR კავშირს, რომელიც შეტყობინებებს არ ატარებს, ping-ები ცოცხლად ინახავს. ორივე მხარის ნაგულისხმევი მნიშვნელობები ერთმანეთზეა მორგებული:
| პარამეტრი | მხარე | ნაგულისხმევი | რას ნიშნავს |
|---|---|---|---|
KeepAliveInterval | სერვერი | 15 წამი | სერვერი უქმ კლიენტს ამ სიხშირით აგზავნის ping-ს |
ClientTimeoutInterval | სერვერი | 30 წამი | სერვერი აგდებს კლიენტს, რომლისგანაც ამდენ ხანს არაფერი სმენია |
HandshakeTimeout | სერვერი | 15 წამი | საწყისი handshake-ისთვის დაშვებული დრო |
keepAliveIntervalInMilliseconds | JS კლიენტი | 15,000 | კლიენტი სერვერს ამ სიხშირით აგზავნის ping-ს |
serverTimeoutInMilliseconds | JS კლიენტი | 30,000 | კლიენტი ნებდება, თუ სერვერი ამდენ ხანს დუმს |
წესი ასეთია: თითოეული მხარის timeout მეორე მხარის keep-alive ინტერვალის დაახლოებით ორმაგი უნდა იყოს. თუ სერვერზე KeepAliveInterval-ს გაზრდი, კლიენტზე serverTimeoutInMilliseconds შესაბამისად გაზარდე, თორემ კლიენტები უმიზეზოდ გაითიშებიან.
builder.Services.AddSignalR(options =>{ options.KeepAliveInterval = TimeSpan.FromSeconds(15); options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); options.MaximumReceiveMessageSize = 64 * 1024;});MaximumReceiveMessageSize ნაგულისხმევად 32 KB-ია თითო შემომავალ შეტყობინებაზე. კლიენტები, რომლებიც უფრო დიდ payload-ებს აგზავნიან, ითიშებიან შეცდომით, რომელიც ადვილად შეიძლება ქსელის პრობლემად წაიკითხო. გაზარდე, თუ აუცილებელია, მაგრამ დიდი ატვირთვები ჩვეულებრივ HTTP endpoint-ს ეკუთვნის და არა hub მეთოდს. MaximumParallelInvocationsPerClient ნაგულისხმევად 1-ია, ანუ კლიენტის hub გამოძახებები სათითაოდ მუშავდება; ეს გონივრული ნაგულისხმევია, რადგან ერთ კლიენტს სერვერის მონოპოლიზებას უშლის, და ხშირი სიურპრიზია, როცა ხანგრძლივი hub მეთოდი იმავე კლიენტის შემდეგ გამოძახებას ალოდინებს.
proxy-ს საკუთარი timeout აქვს. nginx-ის proxy_read_timeout ნაგულისხმევად 60 წამია, რაც 15-წამიან keep-alive-ზე საკმაოდ გრძელია, ამიტომ ნაგულისხმევი SignalR აპლიკაცია ნაგულისხმევ nginx-ს უძლებს. პრობლემები ჩნდება, როცა ვინმე keep-alive ინტერვალს "ტრაფიკის შესამცირებლად" proxy-ის idle timeout-ზე მეტად ზრდის, ან როცა გზაზე სადღაც load balancer უქმ კავშირებს 30 წამის შემდეგ ხურავს. keep-alive გზაზე არსებულ ყველაზე მოკლე idle timeout-ზე საგრძნობლად ნაკლები დატოვე.
WebSocket-ები reverse proxy-ს გავლით#
WebSocket იწყება HTTP მოთხოვნით Upgrade: websocket და Connection: Upgrade header-ებით. proxy, რომელიც მათ არ ატარებს ან backend-ს HTTP/1.0-ით ელაპარაკება, ყოველ კავშირს ჩავარდნილ upgrade-ად აქცევს. მაშინ SignalR ჩუმად გადადის Server-Sent Events-ზე ან long polling-ზე, აპლიკაცია მუშაობას აგრძელებს, და ვერავინ ამჩნევს, რომ ყოველი კლიენტი ახლა დამატებით მოთხოვნებს ინახავს ღიად და სერვერი რამდენჯერმე მეტ სამუშაოს აკეთებს. შეამოწმე ბრაუზერის network ჩანართი: როცა WebSocket-ები მუშაობს, negotiate მოთხოვნას მოსდევს მოთხოვნა hub-ის URL-ზე სტატუსით 101 Switching Protocols.
nginx-ისთვის ოთხი ხაზი ასეთია:
location /hubs/ { proxy_pass http://127.0.0.1:5000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 120s;}WebSocket-ები reverse proxy-ს უკან განიხილავს map ბლოკს, რომელიც ერთ location-ში ამუშავებს როგორც upgrade, ისე ჩვეულებრივ მოთხოვნებს, და შეცდომებს, რომლებსაც ხედავ, როცა ის არასწორია. აპლიკაციის მხარეს ჩართე forwarded header-ები, რომ აპლიკაციამ საწყისი სქემა და კლიენტის მისამართი იცოდეს; ASP.NET Core reverse proxy-ს უკან ზუსტ ForwardedHeadersOptions-ს შეიცავს.
RE:NODE-ზე C# / .NET გეგმებში proxy slot შედის: მიუთითე A ჩანაწერი ნაჩვენებ მისამართზე და სერტიფიკატი ავტომატურად გაიცემა და განახლდება, კლიენტის მისამართით X-Forwarded-For-ში. deploy-ის შემდეგ გახსენი network ჩანართი და დაადასტურე 101, სანამ ენდობი, რომ შენი მომხმარებლები WebSocket-ებს იღებენ. თუ აპლიკაციასთან პირდაპირ დაკავშირება გირჩევნია, გეგმის გამოყოფილი პორტი პირდაპირ ხელმისაწვდომია და მეტი პორტის დამატება Network ჩანართზე შეიძლება - ამ შემთხვევაში კავშირს თავად Kestrel ასრულებს.
სხვა origin-იდან მომავალ კლიენტებს CORS credentials-ით სჭირდება, რადგან SignalR negotiate-ზე cookie-ებს აგზავნის:
builder.Services.AddCors(o => o.AddPolicy("app", p => p .WithOrigins("https://app.example.com") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials()));AllowCredentials() ვერ შეუთავსდება AllowAnyOrigin()-ს; origin-ები ჩამოთვალე.
ავთენტიფიკაცია მუდმივ კავშირზე#
Cookie-ით ავთენტიფიკაცია SignalR-თან რაიმე განსაკუთრებული დამუშავების გარეშე მუშაობს: ბრაუზერი cookie-ს negotiate მოთხოვნაზეც აგზავნის და WebSocket upgrade-ზეც. Bearer ტოკენები უფრო რთულია, რადგან ბრაუზერის WebSocket API-ს Authorization header-ის დაყენება არ შეუძლია. JavaScript კლიენტი ამას ასე უვლის გვერდს: WebSocket-ებისა და Server-Sent Events-ისთვის ტოკენს query string-ში სვამს access_token-ად, ხოლო სერვერს უნდა უთხრა, რომ იქიდან წაიკითხოს:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // Authority, Audience or TokenValidationParameters as usual ... options.Events = new JwtBearerEvents { OnMessageReceived = context => { var token = context.Request.Query["access_token"]; if (!string.IsNullOrEmpty(token) && context.HttpContext.Request.Path.StartsWithSegments("/hubs")) { context.Token = token; } return Task.CompletedTask; } }; });კლიენტზე: .withUrl("/hubs/chat", { accessTokenFactory: () => getToken() }). factory ყოველ დაკავშირებასა და reconnect-ზე გამოიძახება, ამიტომ დააბრუნე ახალი ტოკენი და არა ერთხელ დაჭერილი სტრიქონი.
აქედან ორი შედეგი გამომდინარეობს. query string-ები access ლოგებში ჩანს, ამიტომ დარწმუნდი, რომ hub-ის გზებისთვის სრულ URL-ს არც შენი proxy წერს და არც შენი მოთხოვნების ლოგირება. და ტოკენი მოწმდება კავშირის დამყარებისას და არა ყოველ შეტყობინებაზე; კავშირი, რომელიც საათში ვადაგასული ტოკენით ავთენტიფიცირდა, ავთენტიფიცირებული რჩება, სანამ არ გაითიშება. თუ გინდა, რომ გაუქმებამ რეალურად იმოქმედოს, დახურე კავშირები სერვერიდან, როცა მომხმარებელი გადის, ან დააწესე კავშირის მაქსიმალური სიცოცხლე კავშირების თვალყურის დევნებით და მათი Context.Abort()-ით შეწყვეტით.
Clients.User(userId) აგზავნის მომხმარებლის ყველა ღია კავშირზე - რამდენიმე ჩანართი, ტელეფონი და ლეპტოპი. მომხმარებლის ID მოდის IUserIdProvider-იდან, რომელიც ნაგულისხმევად ClaimTypes.NameIdentifier claim-ს კითხულობს. თუ შენი ტოკენები ID-ს sub-ში სვამს და claim-ების მიბმა გამორთულია, Context.UserIdentifier null-ია და მომხმარებელზე მიმართული შეტყობინებები არსად მიდის; დაარეგისტრირე საკუთარი provider, რომელიც იმ claim-ს კითხულობს, რომელსაც რეალურად გასცემ.
გაგზავნა hub-ის გარედან#
რეალური შეტყობინებების უმეტესობა hub მეთოდში არ იბადება. შეკვეთა webhook handler-ში ფორმდება, ფონური ამოცანა სრულდება, ფასი იცვლება. ჩასვი IHubContext<THub> აპლიკაციის ნებისმიერ ადგილას:
public class OrderNotifier(IHubContext<ChatHub> hub){ public Task OrderPaid(string userId, int orderId) => hub.Clients.User(userId).SendAsync("orderPaid", orderId);}Hub-ის ინსტანსები transient-ია: თითო მეთოდის გამოძახებაზე ერთი იქმნება და შემდეგ dispose-დება, ამიტომ hub-ის ველში შენახული მდგომარეობა მაშინვე იკარგება. შეინახე კავშირის მდგომარეობა Context.ConnectionId-ით გასაღებულ singleton სერვისში, ან Context.Items-ში, რომელიც კავშირის სიცოცხლის განმავლობაში ცოცხლობს. hub-იდან დაწყებული ხანგრძლივი სამუშაო ფონურ სერვისს უნდა გადაეცეს და არა hub მეთოდის შიგნით ელოდო, ზემოთ ნახსენები ერთი-გამოძახება-ერთდროულად ნაგულისხმევის გამო. .NET worker service-ები და ფონური ამოცანები queue-ს პატერნს განიხილავს.
მეხსიერება და ზომა კავშირზე#
ყოველი ღია კავშირი ინახავს buffer-ებს, კავშირის კონტექსტს და იმას, რასაც შენი აპლიკაცია მას მიამაგრებს - ჯგუფებში წევრობა, მომხმარებლის მდგომარეობა, დაქეშილი მონაცემები. საბაზისო ხარჯი თითო უქმ WebSocket კავშირზე მცირეა, მაგრამ იზრდება შეტყობინებების ზომასთან, ჯგუფების რაოდენობასთან და, უპირველეს ყოვლისა, აპლიკაციის მდგომარეობასთან ერთად, რომელსაც კავშირზე ინახავ. ერთადერთი სანდო რიცხვი შენია: გახსენი ცნობილი რაოდენობის სატესტო კავშირი სკრიპტით, რომელიც .NET კლიენტს იყენებს (Microsoft.AspNetCore.SignalR.Client), ან დატვირთვის ტესტირების ინსტრუმენტით, რომელიც WebSocket-ებს ლაპარაკობს, და წაიკითხე მეხსიერების გრაფიკი მანამდე და მერე.
პრაქტიკული რჩევები პატარა სერვერისთვის:
- თვალი ადევნე სარეზერვო transport-ებს. ათასი long-polling კლიენტი ბევრად მეტი ჯდება, ვიდრე ათასი WebSocket კლიენტი, რადგან ყოველი poll სრული მოთხოვნის ციკლია. თუ WebSocket-ები შენი proxy-ს გავლით ვარდება, ამის გასწორება ნებისმიერ hardware-ზე მეტი ღირს.
- შეზღუდე upgrade-ებული კავშირები. Kestrel-ის
MaxConcurrentUpgradedConnectionsნაგულისხმევად შეუზღუდავია. ზღვარი მოულოდნელ ნაკადს უარყოფილ კავშირებად აქცევს და არა მეხსიერების ამოწურვით გაჩერებად. RE:NODE-ზე სერვერი, რომელიც მეხსიერების ლიმიტს აღწევს, ჩერდება და სუფთად იწყება ხელახლა, რაც ყველა კავშირს ერთდროულად აგდებს და reconnect-ების ქარიშხალს იწვევს; დატოვე მარაგი. - გაანაწილე reconnect-ები დროში. restart-ის შემდეგ ყოველი კლიენტი წამებში ხელახლა უკავშირდება. ნაგულისხმევი დაყოვნებები მათ უკვე ცოტათი ანაწილებს; საკუთარი
IRetryPolicyjitter-ით უფრო მეტად. - Blazor Server არის SignalR. Blazor Server-ის ყოველი მომხმარებელი SignalR კავშირია circuit-ით, რომელიც UI-ის მდგომარეობას სერვერის მეხსიერებაში ინახავს, რაც ტიპურ hub კავშირზე მძიმეა. Blazor Server თუ WebAssembly ჰოსტინგი ამ ზომის შეფასებას გადის.
Scale-out Valkey backplane-ით#
ორი ან მეტი აპლიკაციის ინსტანსით, A ინსტანსზე დაკავშირებული კლიენტი ვერ მიიღებს B ინსტანსზე IHubContext-ით გაგზავნილ შეტყობინებას, რადგან თითოეულმა ინსტანსმა მხოლოდ საკუთარი კავშირები იცის. backplane ამას ასწორებს ყოველი შეტყობინების საერთო pub/sub არხზე გამოქვეყნებით, რომელზეც ყველა ინსტანსია გამოწერილი.
$ dotnet add package Microsoft.AspNetCore.SignalR.StackExchangeRedisbuilder.Services.AddSignalR() .AddStackExchangeRedis(builder.Configuration.GetConnectionString("Valkey")!, options => { options.Configuration.ChannelPrefix = RedisChannel.Literal("myapp"); });connection string არის StackExchange.Redis-ის კონფიგურაციის სტრიქონი, მაგალითად valkey.example.net:6380,password=...,abortConnect=false. Valkey Redis-ის პროტოკოლს ლაპარაკობს, ამიტომ Redis backplane-ის პაკეტი მასთან უცვლელად მუშაობს. დააყენე ChannelPrefix, როცა ერთზე მეტი აპლიკაცია ერთსა და იმავე Valkey სერვერს იზიარებს, თორემ მათი შეტყობინებები აირევა.
იცოდე, რას არ აკეთებს backplane:
- ის sticky session-ების საჭიროებას არ აქრობს. negotiate მოთხოვნა და მომდევნო კავშირი ერთსა და იმავე ინსტანსს უნდა მიაღწიოს. ან load balancer დააკონფიგურირე session affinity-ზე, ან ყოველმა კლიენტმა მხოლოდ WebSocket-ები გამოიყენოს
skipNegotiation: true-ით დაtransport: signalR.HttpTransportType.WebSockets-ით, რაც ცალკე negotiate ნაბიჯს აშორებს. მეორე ვარიანტი სარეზერვო transport-ებს აშორებს კლიენტებს, რომლებიც WebSocket-ების დამბლოკავი ქსელების უკან არიან. - ის შეტყობინებებს არ ინახავს. თუ Valkey მიუწვდომელია, გათიშვისას გამოქვეყნებული შეტყობინებები იკარგება და SignalR მათ ხელახლა არ გაგზავნის. შეტყობინებები, რომლებიც აუცილებლად უნდა მივიდეს, მონაცემთა ბაზას ან queue-ს ეკუთვნის, ხოლო SignalR შეტყობინებაა იმის შესახებ, რომ რაღაც ახალი არსებობს.
- ყოველი შეტყობინება ყველა ინსტანსზე მიდის. backplane-ის გამტარუნარიანობა მთელი კლასტერის შეტყობინებების სიჩქარის ჭერია. აპლიკაციების უმეტესობისთვის ეს ჭერი შორსაა; ძალიან მაღალი fan-out-ის დატვირთვისთვის სწორედ ის უნდა გაზომო.
Valkey-ს pub/sub მოდელი და მისი განსხვავება stream-ებისგან განხილულია პოსტში Valkey pub/sub და stream-ები, ხოლო ერთი პატარა Valkey სერვერი backplane-ისთვის სრულიად საკმარისია - ის მონაცემებს არ ინახავს, მხოლოდ გზაში მყოფ შეტყობინებებს.
პრობლემების მოგვარება#
`Error: Failed to start the transport 'WebSockets'` და შემდეგ სარეზერვოზე გადასვლა. upgrade აპლიკაციამდე არ აღწევს. შეამოწმე proxy-ის Upgrade და Connection header-ები და HTTP/1.1 backend-მდე.
`No Connection with that ID` ორ ინსტანსამდე გაფართოების შემდეგ. negotiate და დაკავშირება სხვადასხვა ინსტანსზე მოხვდა. ჩართე sticky session-ები ან გამოტოვე negotiation მხოლოდ WebSocket-ებით.
კავშირები ზუსტად ყოველ 60 ან 100 წამში წყდება. გზაზე სადღაც idle timeout შეტყობინებებს შორის შუალედზე მოკლეა, ხოლო keep-alive-ები გამორთულია ან ძალიან იშვიათია. დააბრუნე ნაგულისხმევი 15-წამიანი keep-alive.
`Server timeout elapsed without receiving a message from the server.` კლიენტის serverTimeoutInMilliseconds სერვერის keep-alive-ის ორმაგზე მოკლეა, ან სერვერი ზედმეტად დაკავებულია ping-ების გასაგზავნად - შეამოწმე CPU.
401 WebSocket-ზე, თუმცა negotiate მუშაობს. socket-ისთვის ტოკენი query string-ში იგზავნება და სერვერი მას არ კითხულობს. დაამატე OnMessageReceived handler.
ქსელის ცვლილების შემდეგ შეტყობინებები ერთ ჩანართზე მოდის, მეორეზე - არა. ხელახლა დაკავშირებულმა კავშირმა თავისი ჯგუფები დაკარგა. ხელახლა შეუერთდი მათ onreconnected-ში.
FAQ#
მჭირდება backplane ერთი სერვერისთვის?
არა. ერთმა ინსტანსმა ყველა კავშირი იცის, ამიტომ Clients.All და Clients.Group უკვე ყველას აღწევს. backplane მხოლოდ ორი ან მეტი ინსტანსისთვისაა და ამატებს დამოკიდებულებას, რომელიც სხვაგვარად არ გექნებოდა.
შემიძლია SignalR backplane-ისთვის Redis-ის ნაცვლად Valkey გამოვიყენო?
კი. backplane-ის პაკეტი Redis-ის პროტოკოლს StackExchange.Redis-ის გავლით ლაპარაკობს, ხოლო Valkey ამ პროტოკოლს ახორციელებს. მიმართე connection string Valkey სერვერზე და დააყენე არხის პრეფიქსი, თუ მას სხვა რამეც იყენებს.
რამდენ SignalR კავშირს გაუძლებს ერთი პატარა სერვერი?
ათასობით უქმი WebSocket კავშირი მეხსიერების მოკრძალებულ რაოდენობაში ეტევა; გზღუდავს ის მდგომარეობა, რომელსაც შენი აპლიკაცია კავშირზე ინახავს, და შეტყობინებების სიჩქარე. გაზომე სატესტო კლიენტით შენივე hub-ის წინააღმდეგ და ნუ ენდობი გამოქვეყნებულ ციფრს.
ხომ არ ჯობს Azure SignalR Service გამოვიყენო?
ის კავშირების დამუშავებას შენს სერვერებს აცილებს და კავშირების ძალიან დიდ რაოდენობამდე მისვლის ყველაზე მარტივი გზაა, მაგრამ ეს გარე დამოკიდებულებაა საკუთარი ფასებით, და შენი შეტყობინებები შენს ინფრასტრუქტურას ტოვებს. რამდენიმე ინსტანსზე მომუშავე აპლიკაციების უმეტესობისთვის საკუთარი backplane საკმარისია.
რატომ იყენებს SignalR production-ში long polling-ს, როცა ლოკალურად WebSocket-ებს იყენებდა?
რაღაც ბრაუზერსა და აპლიკაციას შორის WebSocket upgrade-ს არ ატარებს - ჩვეულებრივ reverse proxy, ზოგჯერ კორპორატიული ქსელი. აპლიკაცია მუშაობას აგრძელებს, და სწორედ ამიტომ რჩება შეუმჩნეველი. შეამოწმე 101 პასუხი network ჩანართში.




კომენტარები
სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.