Analyzing Disconnects

當您建立一個在線多人遊戲時,您必須意識到,有時客戶端和伺服器之間的連接會失敗。

斷線可能是由軟件或硬件造成的。 如果連接的任何一個環節出現故障,信息會被延遲、丟失或損壞,連接需要被關閉。

如果這種情況經常發生,您可以做一些處理。

Contents

斷開連接的原因

還有一些情況是客戶端根本無法連接(例如,伺服器無法索取,伺服器地址不適當,沒有DNS可用,自主持的伺服器沒有啟動,等等)。 在這種情況下,這些不被視為斷開連接,而是 '(初始)連接失敗'。

客戶端SDK提供斷開連接的回調和斷開連接的原因。 使用這些來調查您所遇到的意外斷開連接的情況。 這裡我們列出了主要的斷開原因,以及它們是在客戶端還是在伺服器端造成。

Back To Top

由客戶端引起的斷開連接

  • 客戶端超時:沒有/過晚收到伺服器的ACK。詳情見"超時斷連"。
  • 客戶端套接字異常(連接遺失)。
  • 客戶端連接在接收時失敗(緩沖區滿,連接遺失)。參見"流量問題和緩沖區滿"。
  • 客戶端連接在發送時失敗(緩沖區滿,連接遺失)。參見"流量問題和緩沖區滿"。

Back To Top

由伺服器斷開連接

  • 伺服器端超時:沒有/過晚收到客戶端的ACK。詳情見"超時斷開連接"。
  • 伺服器發送的緩沖區滿了(訊息過多)。參見"流量問題和緩沖區滿"。
  • 許可証或訂閱CCU限制被擊中。

Back To Top

超時斷開連接

Unlike plain UDP, Photon's reliable UDP protocol establishes a connection between server and clients: Commands within a UDP package have sequence numbers and a flag if they are reliable. If so, the receiving end has to acknowledge the command. Reliable commands are repeated in short intervals until an acknowledgement arrives. If it does not arrive, the connection is timed out.

Both sides monitor this connection independently from their perspective. Both sides have their rules to decide if the other is still available.

If a timeout is detected, a disconnect happens on that side of the connection. As soon as one side thinks the other side does not respond anymore, no message is sent to it. This is why timeout disconnects are one sided and not synchronous.

  • 檢查您所發送的數據量。 如果出現峰值或您的信息/秒率非常高,這可能會影響連接質量。 閱讀"少發送"
  • 檢查您是否能在其他硬件和其他網絡上重現這個問題。 請看"嘗試另一個連接"。
  • 您可以調整重發的數量和時間。 見"調整重發"。 。
  • 如果您正在制作一個移動應用程序,請閱讀移動背景應用程序

  • 如果您想用斷點和所有的東西來調試您的遊戲,閱讀這個

Back To Top

流量問題和緩沖區已滿

Photon伺服器和客戶端通常會在一些命令真正被放入軟件包並通過互聯網發送之前進行緩沖。 這使得我們可以將多個命令匯總到(較少的)包中。

如果某些方面產生了大量的命令(例如,通過發送大量的大事件),那麼緩沖區可能會用完。

填充緩沖區也會造成額外的滯後。 您會注意到,事件需要更長的時間才能索取另一方。 操作響應沒有平時那麼快。

閱讀"Send Less"。

Back To Top

第一項援助

檢查日誌

這是您需要做的第一個檢查。

所有的客戶端都有一些回調,以提供關於內部狀態變化和問題的日誌信息。 您應該記錄這些信息,並在出現問題時訪問它們。

如果沒有什麼有用的東西出現,您通常可以在一定程度上增加日誌記錄。 查閱API參考資料,了解如何做到這一點。

如果您定制了伺服器,檢查那裡的日誌。

Back To Top

啟用記錄器

The SupportLogger is a tool that logs the most commonly needed info to debug problems with Photon, like the (abbreviated) AppId, version, region, server IPs and some callbacks.

{% if PUN_v1 %} In PUN Classic, the SupportLogger is a component. Create an empty GameObject in a scene that's enabled before you connect. Add the SupportLogger component and you're done. The console or log file will contain the new log entries.

For Unity, the SupportLogger is a MonoBehaviour. When not using PUN, you can add this component to any GameObject and set the LoadBalancingClient for it. Call DontDestroyOnLoad() for that GameObject.

Outside of Unity, the SupportLogger is a regular class. Instantiate it and set the LoadBalancingClient, to make it register for callbacks. The Debug.Log method(s) get mapped to System.Diagnostics.Debug respectively.

Back To Top

嘗試另一個項目

所有Photon的客戶端SDK都包括一些範例程序。 在您的目標平台上使用其中的一個。 如果範例也失敗了,那麼更可能是連接的問題。

Back To Top

嘗試其他伺服器或地區

使用Photon Cloud,您也可以輕鬆使用另一個地區。

自己主持?比起虛擬機,更喜歡物理機。 用靠近伺服器的客戶(但不在同一台機器或網絡上)測試最小滯留(往返時間)。 考慮在靠近客戶的地方增加伺服器。

Back To Top

嘗試另一種連接

在某些情況下,特定的硬件會使連接失敗。 嘗試另一個WiFi、路由器等。 檢查另一個設備是否運行得更好。

Back To Top

嘗試其他端口

從2018年初開始,我們在所有Photon Cloud部署中支援一個新的端口范圍。 不再使用5055到5058,而是從27000開始。

改變端口聽起來不應該有什麼不同,但它可以產生非常正向的影響。 到目前為止,反饋皆很正向。

在某些客戶端SDK中,您可能需要替換來自伺服器的地址字符串中的數字。 名稱伺服器的端口為27000(原為5058),主伺服器為27001(原為5055),遊戲伺服器為27002(原為5056)。 這可以通過簡單的字符串替換來完成。

Back To Top

啟用 CRC 檢測

有時,軟件包在客戶端和伺服器之間的路上會被損壞。 當路由器或網絡特別繁忙時,這種情況更容易發生。 有些硬件或軟件有明顯的錯誤,損壞可能隨時發生。

Photon有一個可選的CRC檢測。 由於這需要一定的性能,我們沒有默認激活。

您在客戶端啟用CRC檢測,但是當您這樣做的時候,伺服器也會發送一個CRC。

{% endif %}

csharp
loadBalancingClient.LoadBalancingPeer.CrcEnabled = true

Photon客戶端會追蹤有多少包裹因啟用CRC檢測而被丟棄。

檢測。

csharp
LoadBalancingPeer.PacketLossByCrc

Back To Top

微調

檢測流量統計

在一些客戶端平台上,您可以直接在Photon中啟用 Traffic Statistics。 這些數據可以跟蹤各種重要的性能指標,並且可以很容易地記錄下來。

在C#中,流量統計可以在LoadBalancingPeer類中作為 TrafficStatsGameLevel 屬性使用。 這提供了一個最有趣值的概述。

例如,使用TrafficStatsGameLevel.LongestDeltaBetweenDispatching來檢測連續的DispatchIncomginCommands調用之間最長的時間。 如果這個時間超過幾毫秒,您可能有一些局部滯留。 檢測LongestDeltaBetweenSending以確保您的客戶端頻繁發送。

TrafficStatsIncomingTrafficStatsOutgoing屬性提供了更多的輸入和輸出字節、命令和整包的統計數據。

Back To Top

調整再發送

C#/.Net Photon資料庫有兩個屬性,允許您調整重發的時間:

Back To Top

PhotonPeer.QuickResendAttempts

LoadBalancingPeer.QuickResendAttempts 加速了沒有被接收端確認的重復的可靠命令。 其結果是,如果某些消息被丟棄,則會有更多的流量,但延遲時間更短。

Back To Top

PhotonPeer.SentCountAllowance

默認情況下,Photon客戶端最多發送6次可靠命令。 如果在第5次重發後沒有任何ACK,連接將被關閉。

LoadBalancingPeer.SentCountAllowance 定義了客戶端重復單個可靠信息的頻率。 如果客戶端重復的速度更快,它也應該重復的更頻繁。

在某些情況下,當設置QuickResendAttempts為3,SentCountAllowance為7時,您會看到一個好的效果。

{% if PUN%}}PUN有兩個屬性,可以讓您對其進行管理。 PUN有兩個屬性,允許您調整重發時間:

Back To Top

QuickResends

PhotonNetwork.QuickResends可以加速重復那些沒有被接收端確認的可靠命令。 其結果是,如果某些信息被丟棄,則會有更多的流量,但延遲時間更短。

Back To Top

MaxResendsBeforeDisconnect

PhotonNetwork.MaxResendsBeforeDisconnect定義了客戶端重復單個可靠信息的頻率。 如果客戶端重復的速度更快,它也應該重復的更頻繁。

在某些情況下,當設置PhotonNetwork.QuickResends為3,PhotonNetwork.MaxResendsBeforeDisconnect為7時,您會看到更好的效果。 {% endif %}

不過更多的重復並不能保証更好的連接,但會允許更長的延遲。

Back To Top

檢測重發的可靠命令

您應該開始監控ResentReliableCommands。 這個計數器在每次重發可靠命令時都會上升(因為伺服器的確認沒有及時到達)。

LoadBalancingPeer.ResentReliableCommands

如果這個值過高,說明連接不穩定,UDP數據包不能正常通過(任何來源)。

Back To Top

少發送

You can usually send less to avoid traffic issues. Doing so has a lot of different approaches:

Back To Top

Don't Send More Than What's Needed

Exchange only what's totally necessary. Send only relevant values and derive as much as you can from them. Optimize what you send based on the context. Try to think about what you send and how often. Non critical data should be either recomputed on the receiving side based on the data synchronized or with what's happening in game instead of forced via synchronization.

Examples:

  • In an RTS, you could send "orders" for a bunch of units when they happen. This is much leaner than sending position, rotation and velocity for each unit ten times a second. Good read: 1500 archers.

  • In a shooter, send a shot as position and direction. Bullets generally fly in a straight line, so you don't have to send individual positions every 100 ms. You can clean up a bullet when it hits anything or after it travelled "so many" units.

  • Don't send animations. Usually you can derive all animations from input and actions a player does. There is a good chance that a sent animation gets delayed and playing it too late usually looks awkward anyways.

  • Use delta compression. Send only values when they changes since last time they were sent. Use interpolation of data to smooth values on the receiving side. It's preferable over brute force synchronization and will save traffic.

Back To Top

Don't Send Too Much

Optimize exchanged types and data structures.

Examples:

  • Make use of bytes instead of ints for small ints, make use of ints instead of floats where possible.
  • Avoid exchanging strings at all costs and prefer enums/bytes instead.
  • Avoid exchanging custom types unless you are totally sure about what get sent.

Use another service to download static or bigger data (e.g. maps). Photon is not built as content delivery system. It's often cheaper and easier to maintain to use HTTP-based content systems. Anything that's bigger than the Maximum Transfer Unit (MTU) will be fragmented and sent as multiple reliable packages (they have to arrive to assemble the full message again).

Back To Top

Don't Send Too Often

  • Lower the send rate, you should go under 10 if possible. This depends on your gameplay of course. This has a major impact on traffic. You can also use adaptive or dynamic send rate based on the user's activity or the exchanged data, this is also helping a lot.

  • Send unreliable when possible. You can use unreliable messages in most cases if you have to send another update as soon as possible. Unreliable messages never cause a repeat. Example: In an FPS, player position can usually be sent unreliable.

Back To Top

嘗試降低MTU

通過在客戶端的設置,您可以強迫伺服器和客戶端使用比平時更小的最大包。 降低MTU意味著您需要更多的包來發送一些信息,但如果沒有其他幫助的話,試試這個是有意義的。

這樣做的結果是未經驗証的,我們希望聽到您的意見,如果這能改善情況。

loadBalancingClient.LoadBalancingPeer.MaximumTransferUnit = 520;

Back To Top

工具

Wireshark

這個網絡協議分析器和記錄器對於發現您的遊戲的網絡層到底發生事件非常有用。 有了這個工具,我們可以看清事狀況(網絡方面的)。

Wireshark可能有點嚇人,但當我們要求您記錄我們遊戲的流量時,您只需要做一些設置。

安裝並開始。 第一個工具欄的圖標將打開(網絡)接口的列表。

PhotonServerSettings in Inspector
Wireshark Toolbar

您可以勾選有流量的接口旁邊的方框。 如有疑問,可記錄一個以上的接口。 接下來,點擊 "選項"。

PhotonServerSettings in Inspector
Wireshark - Capture Interfaces

我們不想要您所有的網絡流量,所以您必須為每個被選中的接口設置一個過濾器。 在下一個對話框("捕獲選項")中,找到選中的接口並雙擊它。 這將打開另一個對話框 "接口設置"。 在這裡您可以設置一個過濾器。

PhotonServerSettings in Inspector
Wireshark - Interface Settings

記錄任何與Photon有關的內容的過濾器看起來像:

(udp || tcp) && (port 5055 || port 5056 || port 5057 || port 5058 || port 843 || port 943 || port 4530 || port 4531 || port 4532 || port 4533 || port 9090 || port 9091 || port 9092 || port 9093 || port 19090 || port 19091 || port 19093 || port 27000 || port 27001 || port 27002)

當您按下 "開始 "時,當您連接時就會開始記錄。 在您重現一個問題後,停止日誌記錄(第三個工具欄按鈕)並保存它。

在最好的情況下,您還包括對您所做工作的描述,如果錯誤經常發生,在這種情況下發生的頻率和時間(日誌中有時間戳)。 也要附上客戶控制台的日誌。

.pcap和其他文件郵寄給我們,我們會查看。

Back To Top

平台特定信息

Unity

PUN在間隔時間內為您實現了Service調用。

然而,Unity不會在加載場景和資產時或在您拖動一個獨立玩家的窗口時調用Update

為了在加載場景時保持連接,您應該設置PhotonNetwork.IsMessageQueenRunning = false

暫停消息列有兩個效果。

  • 一個後台線程將被用來調用SendOutgoingCommands,而Update不被調用。 這將保持連接的活力,只發送確認,但不發送事件或操作(RPC或同步更新)。 傳入的數據不會被這個線程執行。
  • 所有傳入的更新都是排隊的。既不調用RPC,也不更新觀察對象。 當您改變級別時,這就避免了在前一個級別中調用RPC。

如果您使用我們的Photon Unity SDK,您可能在一些MonoBehaviour Update方法中進行Service調用。

為了確保Photon客戶端的SendOutgoingCommands在您加載場景時被調用,請實現一個後台線程。 這個線程應該在每次調用之間暫停100或200毫秒,所以它不會奪走所有的性能。

Back To Top

從意外的斷開連接中恢復過來

Disconnects will happen, they can be reduced but they can't be avoided. So it's better to implement a recovery routine for when those unexpected disconnects occur especially mid-game.

Back To Top

When To Reconnect

First you need to make sure that the disconnect cause can be recovered from. Some disconnects may be due to issues that cannot be resolved or bypassed by a simple reconnect. Instead those cases should be treated separately and handled case by case.

Back To Top

Quick Rejoin (ReconnectAndRejoin)

Photon client SDKs offer a way to rejoin rooms as soon as possible after being disconnected while joined to a room. This is called "Quick Rejoin". Photon client locally caches the authentication token, the room name and the game server address. So when disconnected mid-game, the client can do a shortcut: connect directly to the game server, authenticate using the saved token and rejoin the room.

In C# SDKs, this is done using LoadBalancingClient.ReconnectAndRejoin(). Check the return value of this method to make sure the quick rejoin process is initiated.

In order for the reconnect and rejoin to succeed, the room needs to have PlayerTTL != 0. But this is not a guarantee that the rejoin will work. If the reconnection and authentication is successful, rejoin can fail with one of the following errors:

  • GameDoesNotExist (32758): the room was removed from the server while disconnected. This probably means that you were the last actor leaving the room when disconnected and that 0 <= EmptyRoomTTL < PlayerTTL or PlayerTTL < 0 <= EmptyRoomTTL.
  • JoinFailedWithRejoinerNotFound (32748): the actor was removed from the room while disconnected. This probably means that PlayerTTL is too short and expired, we suggest at least a value of 12000 milliseconds to allow a quick rejoin.
  • PluginReportedError (32752): this probably means that you use webhooks and that PathCreate returns ResultCode other than 0.
  • JoinFailedFoundActiveJoiner (32746): this is very unlikely to happen but it may. It means that another client using the same UserId managed to rejoin the room while you were disconnected.

You can catch these in the OnJoinRoomFailed callback.

Back To Top

Reconnect

If the client got disconnected outside of a room or if quick rejoin failed (ReconnectAndRejoin returned false) you could still do a Reconnect only. The client will reconnect to the master server and reuse the cached authentication token there.

In C# SDKs, this is done using LoadBalancingClient.ReconnectToMaster(). Check the return value of this method to make sure the quick rejoin process is initiated.

It could be useful in some cases to add:

  • check if connectivity is working as expected (internet connection available, servers/network reachable, services status)
  • reconnect attempts counter: max. retries
  • backoff timer between retries

Back To Top

Sample (C#)

using System;
using Photon.Realtime;

public class RecoverFromUnexpectedDisconnectSample : IConnectionCallbacks
{
    private LoadBalancingClient loadBalancingClient;
    private AppSettings appSettings;

    public RecoverFromUnexpectedDisconnectSample(LoadBalancingClient loadBalancingClient, AppSettings appSettings)
    {
        this.loadBalancingClient = loadBalancingClient;
        this.appSettings = appSettings;
        this.loadBalancingClient.AddCallbackTarget(this);
    }

    ~RecoverFromUnexpectedDisconnectSample()
    {
        this.loadBalancingClient.RemoveCallbackTarget(this);
    }

    void IConnectionCallbacks.OnDisconnected(DisconnectCause cause)
    {
        if (this.CanRecoverFromDisconnect(cause))
        {
            this.Recover();
        }
    }

    private bool CanRecoverFromDisconnect(DisconnectCause cause)
    {
        switch (cause)
        {
            // the list here may be non exhaustive and is subject to review
            case DisconnectCause.Exception:
            case DisconnectCause.ServerTimeout:
            case DisconnectCause.ClientTimeout:
            case DisconnectCause.DisconnectByServerLogic:
            case DisconnectCause.DisconnectByServerReasonUnknown:
                return true;
        }
        return false;
    }

    private void Recover()
    {
        if (!loadBalancingClient.ReconnectAndRejoin())
        {
            Debug.LogError("ReconnectAndRejoin failed, trying Reconnect");
            if (!loadBalancingClient.ReconnectToMaster())
            {
                Debug.LogError("Reconnect failed, trying ConnectUsingSettings");
                if (!loadBalancingClient.ConnectUsingSettings(appSettings))
                {
                    Debug.LogError("ConnectUsingSettings failed");
                }
            }
        }
    }

    #region Unused Methods

    void IConnectionCallbacks.OnConnected()
    {
    }

    void IConnectionCallbacks.OnConnectedToMaster()
    {
    }

    void IConnectionCallbacks.OnRegionListReceived(RegionHandler regionHandler)
    {
    }

    void IConnectionCallbacks.OnCustomAuthenticationResponse(Dictionary<string, object> data)
    {
    }

    void IConnectionCallbacks.OnCustomAuthenticationFailed(string debugMessage)
    {
    }

    #endregion
}


To Document Top