แนวทางปฏิบัติที่ดีที่สุดสำหรับการเชื่อมต่อไคลเอนต์ SignalR 2.0 .NET กับเซิร์ฟเวอร์ฮับอีกครั้ง


86

ฉันใช้ SignalR 2.0 กับไคลเอนต์. NET ในแอปพลิเคชันมือถือที่ต้องการจัดการการตัดการเชื่อมต่อประเภทต่างๆ บางครั้งไคลเอนต์ SignalR จะเชื่อมต่อใหม่โดยอัตโนมัติ - และบางครั้งก็ต้องเชื่อมต่อใหม่โดยตรงโดยการโทรHubConnection.Start()อีกครั้ง

เนื่องจาก SignalR เชื่อมต่อใหม่โดยอัตโนมัติอย่างน่าอัศจรรย์ในบางครั้งฉันจึงสงสัยว่าฉันขาดคุณสมบัติหรือการตั้งค่าการกำหนดค่าหรือไม่?

วิธีใดดีที่สุดในการตั้งค่าไคลเอนต์ที่เชื่อมต่อใหม่โดยอัตโนมัติ


ฉันเคยเห็นตัวอย่างจาวาสคริปต์ที่จัดการClosed()เหตุการณ์แล้วเชื่อมต่อหลังจากนั้นไม่กี่วินาที มีแนวทางที่แนะนำหรือไม่?

ฉันได้อ่านเอกสารและบทความเกี่ยวกับอายุการใช้งานของการเชื่อมต่อ SignalR แล้ว แต่ฉันยังไม่ชัดเจนว่าจะจัดการกับไคลเอนต์เชื่อมต่อใหม่ได้อย่างไร

คำตอบ:


71

ในที่สุดฉันก็คิดออก นี่คือสิ่งที่ฉันได้เรียนรู้ตั้งแต่เริ่มคำถามนี้:

ความเป็นมา:เรากำลังสร้างแอป iOS โดยใช้ Xamarin / Monotouch และไคลเอนต์. NET SignalR 2.0.3 เรากำลังใช้โปรโตคอล SignalR เริ่มต้น - และดูเหมือนว่าจะใช้ SSE แทนเว็บซ็อกเก็ต ฉันยังไม่แน่ใจว่าจะใช้เว็บซ็อกเก็ตกับ Xamarin / Monotouch ได้หรือไม่ ทุกอย่างโฮสต์โดยใช้เว็บไซต์ Azure

เราต้องการแอปเพื่อเชื่อมต่อกับเซิร์ฟเวอร์ SignalR ของเราใหม่อย่างรวดเร็ว แต่เรายังคงประสบปัญหาที่การเชื่อมต่อไม่ได้เชื่อมต่อใหม่ด้วยตัวเองหรือการเชื่อมต่อใหม่ใช้เวลา 30 วินาที (เนื่องจากโปรโตคอลหมดเวลา)

มีสามสถานการณ์ที่เราทดสอบสำหรับ:

สถานการณ์ A - เชื่อมต่อในครั้งแรกที่โหลดแอป สิ่งนี้ทำงานได้อย่างไม่มีที่ติตั้งแต่วันแรก การเชื่อมต่อจะเสร็จสิ้นภายในเวลาน้อยกว่า. 25 วินาทีแม้ผ่านการเชื่อมต่อมือถือ 3G (สมมติว่าเปิดวิทยุแล้ว)

สถานการณ์ B - เชื่อมต่อกับเซิร์ฟเวอร์ SignalR อีกครั้งหลังจากที่แอปไม่ได้ใช้งาน / ปิดเป็นเวลา 30 วินาที ในสถานการณ์นี้ไคลเอนต์ SignalR จะเชื่อมต่อกับเซิร์ฟเวอร์ใหม่ในที่สุดโดยไม่ต้องทำงานพิเศษใด ๆ - แต่ดูเหมือนว่าจะรอ 30 วินาทีก่อนที่จะพยายามเชื่อมต่อใหม่ (ช้าเกินไปสำหรับแอปของเรา)

ในช่วงเวลารอ 30 วินาทีนี้เราได้ลองโทรไปที่ HubConnection.Start () ซึ่งไม่มีผลใด ๆ และการเรียก HubConnection.Stop () ยังใช้เวลา 30 วินาที ฉันพบข้อบกพร่องที่เกี่ยวข้องในไซต์ SignalR ซึ่งดูเหมือนจะได้รับการแก้ไขแต่เรายังคงมีปัญหาเดียวกันใน v2.0.3

สถานการณ์ C - เชื่อมต่อกับเซิร์ฟเวอร์ SignalR อีกครั้งหลังจากที่แอปไม่ได้ใช้งาน / ปิดเป็นเวลา 120 วินาทีหรือนานกว่านั้น ในสถานการณ์นี้โปรโตคอลการขนส่ง SignalR หมดเวลาแล้วดังนั้นไคลเอนต์จะไม่เชื่อมต่อใหม่โดยอัตโนมัติ สิ่งนี้อธิบายว่าเหตุใดบางครั้งไคลเอ็นต์จึงเชื่อมต่อใหม่ด้วยตัวเอง แต่ไม่เคยเชื่อมต่อ ข่าวดีก็คือการเรียก HubConnection.Start () ทำงานได้เกือบจะทันทีเหมือนสถานการณ์ A

ดังนั้นฉันจึงต้องใช้เวลาสักพักกว่าจะรู้ว่าเงื่อนไขการเชื่อมต่อใหม่นั้นแตกต่างกันไปขึ้นอยู่กับว่าแอปปิดเป็นเวลา 30 วินาทีเทียบกับ 120+ วินาทีหรือไม่ และแม้ว่าบันทึกการติดตาม SignalR จะให้แสงสว่างว่าเกิดอะไรขึ้นกับโปรโตคอลพื้นฐาน แต่ฉันไม่เชื่อว่าจะมีวิธีจัดการกับเหตุการณ์ระดับการขนส่งในรหัสได้ (เหตุการณ์ Closed () เริ่มทำงานหลังจาก 30 วินาทีในสถานการณ์ B ทันทีในสถานการณ์ C คุณสมบัติของรัฐระบุว่า "เชื่อมต่อแล้ว" ในระหว่างช่วงเวลารอการเชื่อมต่อใหม่เหล่านี้ไม่มีเหตุการณ์หรือวิธีการอื่น ๆ ที่เกี่ยวข้อง)

วิธีแก้ไข: วิธีแก้ปัญหานั้นชัดเจน เราไม่ได้รอให้ SignalR ทำการเชื่อมต่อเวทย์มนตร์ใหม่ แต่เมื่อเปิดใช้งานแอปหรือเมื่อคืนค่าการเชื่อมต่อเครือข่ายของโทรศัพท์เราก็เพียงแค่ทำความสะอาดเหตุการณ์และยกเลิกการอ้างอิง HubConnection (ไม่สามารถกำจัดได้เนื่องจากใช้เวลา 30 วินาทีหวังว่าการรวบรวมขยะจะดูแลได้ ) และสร้างอินสแตนซ์ใหม่ ตอนนี้ทุกอย่างทำงานได้ดี ด้วยเหตุผลบางประการฉันคิดว่าเราควรใช้การเชื่อมต่อที่มีอยู่ซ้ำและเชื่อมต่อใหม่แทนที่จะสร้างอินสแตนซ์ใหม่


5
คุณยินดีที่จะโพสต์รหัสหรือไม่? แค่อยากรู้ว่าคุณจัดโครงสร้างอย่างไร ฉันใช้ Signalr ในแอพแชทจากใน PCL ในแอพ Xamarin ด้วย มันใช้งานได้ดีจริงๆยกเว้นดูเหมือนว่าฉันจะไม่สามารถใช้เวทย์มนตร์การเชื่อมต่อใหม่ได้หลังจากปิดโทรศัพท์แล้วเปิดใหม่อีกครั้ง ฉันสาบานว่า IT Crowd บอกว่านั่นคือทั้งหมดที่ฉันต้องทำ
Timothy Lee Russell

1
สวัสดี Ender2050 ฉันสังเกตเห็นว่าเมื่ออุปกรณ์ Android ตัดการเชื่อมต่อจากเซิร์ฟเวอร์แล้วจะไม่เชื่อมต่ออีกครั้งดังนั้นฉันจึงทำการเตือนภัยซึ่งทำงานทุก ๆ 5 นาทีและตรวจสอบการเชื่อมต่อ signalR กับฮับเซิร์ฟเวอร์ในเหตุการณ์เห็บสัญญาณเตือนฉันได้ตรวจสอบว่าวัตถุการเชื่อมต่อเป็นโมฆะหรือไม่ connectionId ว่างเปล่าจากนั้นสร้างการเชื่อมต่ออีกครั้ง แต่มันใช้งานได้ไม่ดีผู้ใช้จำเป็นต้องฆ่าแอพแล้วเปิดใหม่อีกครั้ง ฉันใช้ java-client สำหรับ Android และ C # .Net เพื่อให้บริการฮับ กำลังมองหาความช่วยเหลือจากคุณเพื่อแก้ไขปัญหานี้
jignesh

1
FYI โมโนไม่มีซ็อกเก็ตเว็บ นี่คือเหตุผลที่แอป Xamarin ของคุณใช้ SSE เสมอ คุณสามารถเขียนไคลเอนต์คอนโซล หากคุณรันบน Mono มันจะใช้ SSE หากคุณใช้งานบน Windows (อย่างน้อย Windows 8 เนื่องจาก 7 ไม่รองรับเว็บซ็อกเก็ต) จะใช้เว็บซ็อกเก็ต
daramasala

@ Ender2050 โปรดขยายโซลูชันของคุณด้วยตัวอย่างโค้ดได้ไหม
mbx-mbx

เรากำลังมีปัญหาในการเชื่อมต่อกับ SignalR Hub (ไลบรารี SignalR เวอร์ชัน 2.2.2) จาก Android App ซึ่งใช้ "SignalR Java Client library" และ iOS App ซึ่งใช้ "SignalR Object C library" ไลบรารีไคลเอ็นต์บนทั้งสองแพลตฟอร์มไม่ได้รับการอัปเดตมาระยะหนึ่ง ฉันคิดว่าปัญหาเกิดจากความเข้ากันไม่ได้ของโปรโตคอล SignalR ระหว่างไคลเอนต์และเซิร์ฟเวอร์
Nadim Hossain Sonet

44

การตั้งเวลาสำหรับเหตุการณ์ที่ถูกตัดการเชื่อมต่อเพื่อพยายามเชื่อมต่อใหม่โดยอัตโนมัติเป็นวิธีเดียวที่ฉันทราบ

ในจาวาสคริปต์จะทำดังนี้:

$.connection.hub.disconnected(function() {
   setTimeout(function() {
       $.connection.hub.start();
   }, 5000); // Restart connection after 5 seconds.
});

นี่คือแนวทางที่แนะนำในเอกสารประกอบ:

http://www.asp.net/signalr/overview/signalr-20/hubs-api/handling-connection-lifetime-events#clientdisconnect


1
คำใบ้เดียว - ตรวจสอบให้แน่ใจว่าคุณได้เริ่มฟังก์ชันการทำงานที่สมบูรณ์ตั้งแต่เริ่มต้นเช่นเดียวกับที่คุณจะเชื่อมต่อกับฮับ
MikeBaz - MSFT

1
ฉันพบว่าด้วยไคลเอนต์. NET หากคุณสมัครเป็นสมาชิกเหตุการณ์ปิดก่อนที่คุณจะเรียกใช้ hub.Start () หากมีความล้มเหลวในการเชื่อมต่อในตอนแรกตัวจัดการเหตุการณ์ที่ปิดของคุณจะถูกเรียกและพยายามเรียกฮับเริ่ม () อีกครั้ง ทำให้ฮับดั้งเดิม Start () ไม่สมบูรณ์ วิธีแก้ปัญหาของฉันคือสมัครเฉพาะ Closed หลังจาก Start () สำเร็จและยกเลิกการสมัครเป็น Closed ทันทีในการติดต่อกลับ
Oran Dennison

3
@MikeBaz ฉันคิดว่าคุณหมายถึงการเชื่อมต่อกับกลุ่มอีกครั้ง
Simon_Weaver

1
@KingOfHypocrites ฉันสมัครรับข้อมูลreconnectingเหตุการณ์ซึ่งจะเริ่มทำงานเมื่อฮับสูญเสียการเชื่อมต่อและตั้งค่าตัวแปรนั้น (เช่นshouldReconnect) เป็นจริง ผมจึงปรับตัวอย่างของคุณเพื่อตรวจสอบตัวแปรนั้น มันดูดี
Alisson

2
ฉันสุ่มตัวเลขระหว่าง 10 ถึง 60 วินาที เรามีลูกค้ามากเกินไปที่จะใส่เพียง 5 วินาทีเราจะทำ DDoS เอง $ .connection.hub.disconnected (ฟังก์ชัน () {setTimeout (ฟังก์ชัน () {$ .connection.hub.start ();}, (Math.floor (Math.random () * 50) + 10) * 1000); });
Brain2000

17

เนื่องจาก OP ขอไคลเอ็นต์. NET (การใช้งาน winform ด้านล่าง)

private async Task<bool> ConnectToSignalRServer()
{
    bool connected = false;
    try
    {
        Connection = new HubConnection("server url");
        Hub = Connection.CreateHubProxy("MyHub");
        await Connection.Start();

        //See @Oran Dennison's comment on @KingOfHypocrites's answer
        if (Connection.State == ConnectionState.Connected)
        {
            connected = true;
            Connection.Closed += Connection_Closed;
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error: {ex.Message}");
    }
    return connected;
}

private async void Connection_Closed()
{   // A global variable being set in "Form_closing" event 
    // of Form, check if form not closed explicitly to prevent a possible deadlock.
    if(!IsFormClosed) 
    {
        // specify a retry duration
        TimeSpan retryDuration = TimeSpan.FromSeconds(30);
        DateTime retryTill = DateTime.UtcNow.Add(retryDuration);

        while (DateTime.UtcNow < retryTill)
        {
            bool connected = await ConnectToSignalRServer();
            if (connected)
                return;
        }
        Console.WriteLine("Connection closed")
    }
}

ฉันพบใน SignalR 2.3.0 ว่าหากฉันรอการเชื่อมต่อในเหตุการณ์ Closed () ซึ่งบางครั้งจะไม่เชื่อมต่อ อย่างไรก็ตามหากฉันเรียก Manual Wait () ในเหตุการณ์ที่มีการหมดเวลาเช่น 10 วินาทีระบบจะเรียก Closed () โดยอัตโนมัติซ้ำแล้วซ้ำอีกทุกๆ 10 วินาทีจากนั้นการเชื่อมต่อใหม่จะทำงาน
Brain2000

0

ฉันเพิ่มการอัปเดตสำหรับคำตอบibubi อาจมีใครบางคนต้องการมัน ฉันพบว่าในบางกรณี signalr ไม่ขึ้นเหตุการณ์ "ปิด" หลังจากหยุดการเชื่อมต่อใหม่ ฉันแก้ไขโดยใช้เหตุการณ์ "StateChanged" วิธีที่เชื่อมต่อกับเซิร์ฟเวอร์ SignalR:

private async Task<bool> ConnectToSignalRServer()
        {
            bool connected = false;
            try
            {
                var connection = new HubConnection(ConnectionUrl);
                var proxy = connection.CreateHubProxy("CurrentData");
                await connection.Start();

                if (connection.State == ConnectionState.Connected)
                {
                    await proxy.Invoke("ConnectStation");

                    connection.Error += (ex) =>
                    {
                        Console.WriteLine("Connection error: " + ex.ToString());
                    };
                    connection.Closed += () =>
                    {
                        Console.WriteLine("Connection closed");
                    };
                    connection.StateChanged += Connection_StateChanged;
                    Console.WriteLine("Server for Current is started.");
                    connected = true;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            return connected;
        }

วิธีการเชื่อมต่อใหม่:

private async void Connection_StateChanged(StateChange obj)
        {
            if (obj.NewState == ConnectionState.Disconnected)
            {
                await RestartConnection();
            }
        }

วิธีการพยายามเชื่อมต่อกับเซิร์ฟเวอร์อย่างไม่มีที่สิ้นสุด (ฉันใช้วิธีนี้ในการสร้างการเชื่อมต่อแบบกำปั้น):

public async Task RestartConnection()
        {
            while (!ApplicationClosed)
            {
                bool connected = await ConnectToSignalRServer();
                if (connected)
                    return;
            }
        }

-3

คุณอาจพยายามเรียกใช้วิธีการเซิร์ฟเวอร์จาก Android ของคุณก่อนที่สถานะการเชื่อมต่อใหม่จะเริ่มขึ้นเพื่อป้องกันปัญหาการเชื่อมต่อใหม่

ฮับ ​​SignalR C #

 public class MyHub : Hub
    {
        public void Ping()
        {
            //ping for android long polling
        }
 }

ใน Android

private final int PING_INTERVAL = 10 * 1000;

private boolean isConnected = false;
private HubConnection connection;
private ClientTransport transport;
private HubProxy hubProxy;

private Handler handler = new Handler();
private Runnable ping = new Runnable() {
    @Override
    public void run() {
        if (isConnected) {
            hubProxy.invoke("ping");
            handler.postDelayed(ping, PING_INTERVAL);
        }
    }
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    System.setProperty("http.keepAlive", "false");

    .....
    .....

    connection.connected(new Runnable() {
        @Override
        public void run() {
            System.out.println("Connected");
            handler.postDelayed(ping, PING_INTERVAL);
    });
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.