This document is about: SERVER 5
SWITCH TO

LoadBalancing Application

這篇文章解釋了LoadBalancing應用程序的伺服器端執行。

概念

LoadBalancing應用程序擴展了Hive框架,執行了Rooms、Events、Properties等功能,並增加了一層可擴展性,使你可以在多個伺服器上運行該應用程序。

基本設置很簡單。
總是有1個主伺服器和1...N個遊戲伺服器。

LoadBalancing還增加了大廳支持和匹配能力。

Photon Server Concept: LoadBalancing Setup
Photon Server Concept: LoadBalancing Setup

主伺服器處理這些任務:

  • 追蹤當前在遊戲伺服器上打開的遊戲。
  • 跟蹤連接的遊戲伺服器的工作量,並將對等人分配到適當的遊戲伺服器上。
  • 保持並更新 "大廳 "中可供客戶使用的房間列表。
  • 為客戶找到房間(隨機或按名稱),並將遊戲伺服器地址轉發給他們。

遊戲伺服器處理這些任務:

  • 主持遊戲房間。
  • 定期向主伺服器報告他們當前的工作負荷和遊戲列表。

與Photon Cloud的差異

負載平衡應用程序提供的邏輯與Photon實時雲端服務幾乎相同。
一些作為Cloud服務運行的要求並不適用於具有自定義邏輯的特殊伺服器,所以它們被移除。
這也大大簡化了代碼。

  • 沒有虛擬應用程序。每個LB實例只有一個遊戲邏輯在運行。操作中的AppId參數Authenticate被忽略。
  • 沒有通過AppVersion參數的玩家分離。這也是虛擬應用程序的一部分。

你可以閱讀更多的差異點此

基本工作流程

從客戶端的角度來看,工作流程也很簡單。

客戶端連接到主伺服器,在那裡他們可以加入大廳並檢索開放遊戲的列表。

當他們在主伺服器上調用CreateGame操作時,遊戲實際上並沒有被創建-主伺服器只是確定工作量最小的遊戲伺服器並將其IP返回給客戶端。

當客戶端在主伺服器上調用JoinGameJoinRandomGame操作時,主伺服器會查找正在運行遊戲的遊戲伺服器,並將其IP返回給客戶端。

客戶端斷開與主伺服器的連接,用它剛收到的IP連接到遊戲伺服器,並再次調用CreateGameJoinGame操作。

Photon Server: LoadBalancing 順序圖
Photon Server: LoadBalancing 順序圖

主伺服器

本節解釋了主伺服器的執行-見"\src-server\Loadbalancing\Loadbalancing.sln "解決方案中的LoadBalancing.MasterServer命名空間。

MasterApplication 決定傳入的連接是由遊戲客戶端(在 "客戶端端口")還是由遊戲伺服器(在 "遊戲伺服器端口")發起的。

主伺服器: 處理客戶端同伴

MasterClientPeer代表與主伺服器的客戶端連接。
下列操作對MasterClientPeer是可用的:

  • Authenticate
    Authenticate操作只有一個假的執行。
    開發者應該把它作為一個起點來執行他自己的認証機制:

C#

    // MasterClientPeer.cs: 
    private OperationResponse HandleAuthenticate(OperationRequest operationRequest)
    {
        OperationResponse response;

        var request = new AuthenticateRequest(this.Protocol, operationRequest);
        if (!OperationHelper.ValidateOperation(request, log, out response))
        {
            return response;
        }
        
        this.UserId = request.UserId;

        // publish operation response
        var responseObject = new AuthenticateResponse { QueuePosition = 0 };
        return new OperationResponse(operationRequest.OperationCode, responseObject);
    }
  • JoinLobby
    JoinLobby操作用於將MasterClientPeer添加到AppLobby中,AppLobby包含一個GameList-任何遊戲伺服器上所有開放遊戲的列表。
    對象收到一個初始的GameListEvent,其中包含GameList中的當前遊戲列表(由JoinLobby操作的可選屬性過濾)。
    之後,定期向客戶端發送GameListUpdateEvent,其中包含變化的遊戲列表(同樣由JoinLobby操作的可選屬性過濾)。
    只要客戶端處於連接狀態,它就會收到更新事件。

    C#

    // AppLobby.cs: 
    protected virtual OperationResponse HandleJoinLobby(MasterClientPeer peer, OperationRequest operationRequest, SendParameters sendParameters)
    {
        // validate operation
        var operation = new JoinLobbyRequest(peer.Protocol, operationRequest);
        OperationResponse response;
        if (OperationHelper.ValidateOperation(operation, log, out response) == false)
        {
            return response;
        }
    
        peer.GameChannelSubscription = null;
    
        var subscription = this.GameList.AddSubscription(peer, operation.GameProperties, operation.GameListCount);
        peer.GameChannelSubscription = subscription; 
        peer.SendOperationResponse(new OperationResponse(operationRequest.OperationCode), sendParameters);
    
        // publish game list to peer after the response has been sent
        var gameList = subscription.GetGameList();
        var e = new GameListEvent { Data = gameList };
        var eventData = new EventData((byte)EventCode.GameList, e);
        peer.SendEvent(eventData, new SendParameters());
    
        return null;
    }
    
  • JoinGame / JoinRandomGame
    當客戶端想加入AppLobby的GameList中列出的現有遊戲時,JoinGame操作被調用,該遊戲由一個唯一的GameId指定。
    如果遊戲存在並且允許對象加入,主伺服器會將遊戲伺服器的IP返回給客戶端,該遊戲在該伺服器上實際正在運行。

    主伺服器還更新遊戲狀態,並將對象添加到其 "加入的對象 "列表中。
    一旦它加入了遊戲伺服器上的遊戲(或經過一定的超時),它將被刪除。
    這樣,主伺服器就可以跟蹤那些在主伺服器和遊戲伺服器之間過渡的對象。

    JoinRandomGame的工作方式類似,只是遊戲是由主伺服器隨機選擇的,GameId會返回給客戶端。

  • CreateGame
    當客戶端想要創建一個新遊戲時,CreateGame操作被調用。
    主伺服器確定一個遊戲伺服器,新遊戲將在這個伺服器上創建,並將遊戲伺服器的IP返回給客戶端。
    詳情見"LoadBalancing Algorithm "部分。

    此外,一個GameState對象被創建並添加到GameList中,該對象被存儲為 "加入對象"。
    注意,這個GameState僅用於追蹤遊戲-遊戲本身只存在於遊戲伺服器上。

處理遊戲伺服器對象

主伺服器總是知道哪些遊戲伺服器是可用的,它們承載了多少遊戲以及當前的工作量如何。

為了達到這個目的,每個遊戲伺服器在啟動時都會連接到主伺服器。
MasterApplication 維護一個 GameServerCollection,其中存儲有 IncomingGameServerPeers

遊戲伺服器只能調用一個操作:

  • RegisterGameServer
    遊戲伺服器在連接到主伺服器後調用一次RegisterGameServer操作。
    遊戲伺服器會被添加到主伺服器的 GameServerCollection和它的負載平衡器中(見"負載平衡算法")。
    它將在斷開連接時從 GameServerCollection中刪除。

檢查"遊戲伺服器 "部分,看看遊戲伺服器如何向主伺服器發送有關其遊戲和工作量的進一步更新。

遊戲伺服器

本節描述了遊戲伺服器的執行。
見"\src-server\Loadbalancing\Loadbalancing.sln "解決方案中的LoadBalancing.GameServer命名空間。

遊戲伺服器:處理客戶端的對象

一旦客戶端從主控端獲得了遊戲伺服器的地址,客戶端就可以調用Hive中可用的遊戲伺服器上的任何操作(同時加入房間)。
唯一不同的是,我們為遊戲伺服器上的JoinGameCreateGame設置了單獨的操作代碼。

向主伺服器報告遊戲狀態

與主伺服器的連接在遊戲伺服器中被表示為 OutgoingMasterServerPeer
一旦連接建立,遊戲伺服器就會在主伺服器上調用一個Register操作。
之後,遊戲伺服器將所有現有的遊戲狀態發布到主伺服器上:

C#

// OutgoingMasterServerPeer.cs: 
protected virtual void HandleRegisterGameServerResponse(OperationResponse operationResponse)
{
    // [...]
    
    switch (operationResponse.ReturnCode)
    {
    case (short)ErrorCode.Ok:
        {
            log.InfoFormat("Successfully registered at master server: serverId={0}", GameApplication.ServerId);
            this.IsRegistered = true;
            this.UpdateAllGameStates();
            this.StartUpdateLoop();
            break;
        }
    }
}

這是通過向每個遊戲發送一個消息,告訴它將其遊戲狀態發送給主伺服器來執行:

C#

// OutgoingMasterServerPeer.cs:
public virtual void UpdateAllGameStates()
{
    // [...]
    
    foreach (var gameId in GameCache.Instance.GetRoomNames())
    {
        Room room; 
        if (GameCache.Instance.TryGetRoomWithoutReference(gameId, out room))
        {
            room.EnqueueMessage(new RoomMessage((byte)GameMessageCodes.ReinitializeGameStateOnMaster));
        }                
    }
}

遊戲在 ProcessMessage方法中處理,並調用 UpdateGameStateOnMaster 方法,向主伺服器發送UpdateGameEvent

C#

 protected virtual void UpdateGameStateOnMaster(
            byte? newMaxPlayer = null, 
            bool? newIsOpen = null,
            bool? newIsVisble = null,
            object[] lobbyPropertyFilter = null,
            Hashtable gameProperties = null, 
            string newPeerId = null, 
            string removedPeerId = null, 
            bool reinitialize = false)
        {            
            // [...]
            
            var e = this.CreateUpdateGameEvent();
            e.Reinitialize = reinitialize;
            e.MaxPlayers = newMaxPlayer;
            // [ ... more event data is set here ... ]
            
            var eventData = new EventData((byte)ServerEventCode.UpdateGameState, e);
            GameApplication.Instance.MasterPeer.SendEvent(eventData, new SendParameters());
        }
}

每當一個遊戲被客戶端創建、加入或離開,或其屬性被改變時,遊戲狀態也會在主控端上更新。

負載平衡的執行

下一節描述了遊戲伺服器如何向主伺服器報告它們當前的工作負載,以及主伺服器如何確定最適合處理新的CreateGame 請求的遊戲伺服器-實際的負載平衡算法。

概念

LoadBalancer的操作有三個基本要素:伺服器負載水平、伺服器權重和伺服器本身。

伺服器

一些由LoadBalancer操作的對象。
它們有與之相關的負載級別和權重。
權重是由LoadBalancer提供的,LoadLevel是由一些外部邏輯提供給LoadBalancer。

伺服器負載級別

一組定義伺服器負載水平的常數。
伺服器測量/計算其負載水平,並將每次變化通知LoadBalancer。
Photon Server v4的第一個版本只包含5個級別。
Photon Server v5包含10個。

伺服器權重

伺服器權重是LoadBalancer給負載級別附加的一些數字。
我們從配置的 "WorkloadConfigFile "中獲取這些數字,如果沒有配置或沒有提供該文件,則採用一些默認值。
這些數字定義了由LoadBalancer返回的伺服器的概率。
所以,如果負載級別越高,那麼它的權重就應該越小,以減少概率。
最高級別的權重為0。
這樣的負載水平的伺服器根本就不被使用。
我們使用所謂的幾何概率。
我們給每個伺服器分配一段。
這個段的長度由伺服器的權重決定。
然後,我們將所有段連接起來,形成一個大的段。
伺服器按照負載水平進行排序。
負載較低的伺服器排在前面。
然後我們得到一些隨機數(隨機數取自范圍[0 ... 所有伺服器權重的總和])並檢查哪個伺服器段包含它。

Example
例如,我們有3個伺服器 "A"、"B "和 "C",其權重分別為 3(|___A|), 1(|_B|) and 5 (|_____C|)。
大段會看起來像 5, 3, 1 (|_____C|___A|_B|).
然後我們從0到8中取一個隨機數 ( 5 + 3 + 1)。
[0, 4] -> 伺服器 C, [5, 7] 伺服器 A, [8] - 伺服器 B.

Note:開放和封閉區間:

  • [a, b]:包括兩端
  • (a, b):包括左邊,但不包括右邊
  • (a, b):不包括a和b。

伺服器狀態

有三個常數定義遊戲伺服器狀態:'正常'、'離線'和'輪替'。
LoadBalancer的基本執行並不關心伺服器狀態。
所有伺服器都被認為處於'正常'狀態。
當狀態從 "正常' 變為 '離線'或 '輪替'時,伺服器會被LoadBalancer的外部邏輯移除。
在Photon Server v5中,LoadBalancer是知道伺服器狀態的。
因此,如果伺服器的狀態不是 '正常',那麼LoadBalancer就不會返回該伺服器。

伺服器狀態可以通過文本文件設置。
文件名通過 "deploy\LoadBalancing\GameServer\bin\GameServer.xml.config "中的 "ServerState "設置進行配置。
該路徑是相對於部署文件夾的。
默認情況下,它是 "ServerState.txt",因為它是相對於部署文件夾的,所以路徑是 "deploy\ServerState.txt"。

優先級

有時您需要快速添加一個伺服器到集群中。
通常情況下,提供快速部署新伺服器方式的供應商都很昂貴。
這就是為什麼最好是盡快把這些伺服器拿下來(一旦我們不需要它們)。
這就是Photon 5中的LoadBalancer優先級發揮作用的地方。
這背後的想法是這樣的:

一旦主伺服器空間的負載達到某個閾值,LoadBalancer就會從空間子裡取出具有更高優先級的伺服器。
反之亦然。
一旦集群負載達到某個低值,具有最高優先級的遊戲伺服器就會被排除,如此循環。

您必須在 "deploy\LoadBalancer.config "中為LoadBalancer設置兩個屬性。

  • PriorityDownThreshold:定義了優先級較高的伺服器被排除在可用伺服器空間之外時的負載值。
  • PriorityUpThreshold:定義了具有較高優先級的伺服器被納入可用伺服器空間時的負載值。

您必須注意這些值,所以當您添加新的伺服器時,負載不會低於PriorityDownThreashold,當您刪除優先級伺服器時,剩余伺服器的負載不應該達到PriorityUpThreshold。

在GameSever方面,您必須在GameServer.xml.config中設置伺服器的優先級。
在GameServer部分添加LoadBalancerPriority設置。
0是默認值。
它適用於第一級的伺服器。
您可以使用其他優先級:1、2、3等等。

在開始時,我們使用優先級最低的伺服器。
通常情況下,它是0。
當所有這些伺服器都達到了配置級別預定義的水平時,我們就包括下一個優先級。
比方說優先級'1'。
當負載水平下降並達到另一個由配置預定義的閾值時,我們就把所有優先級為 '1'的伺服器從LoadBalancer中移除,這樣它們就不再被使用了。

儲備

這背後的想法很簡單,如下所示:
集群的負載在24小時內是不固定的。
在白天,我們有更多的負載,在晚上則更少。
我們應該有足夠的伺服器在任何情況下都能很好地處理它。
但有時我們需要快速地將一台伺服器從一個集群轉移到另一個集群,以減少那裡的負載。
出於這個原因,我們引入了ReserveRatio
在 "deploy\LoadBalancer.config "中配置的LoadBalancer中,我們可以定義一個ReserveRatio
它的默認值是 "0.0",這意味著沒有保留伺服器。
如果該值被設置為0.2,那麼對於10個伺服器,LoadBalancer將保留2個伺服器。
對於9個伺服器,我們將只保留一個伺服器。
這個邏輯類似於優先級的工作方式。
在達到某個預定的伺服器負載值後,我們只從備用伺服器中抽取一台。
如果這還不夠,我們再取一個。
然後我們將伺服器從使用中逐一返回到儲備中。

儲備對每個優先級都是獨立工作的。
每個優先級都有自己的儲備。
只有當所有當前優先級的伺服器(包括儲備的伺服器)達到預定的負載水平時,才會使用下一個優先級。

決定工作負載

請參閱 Photon.Common.LoadBalancer.LoadShedding 命名空間中的"\src-server\LoadBalancing\LoadBalancing.sln "解決方案,了解執行細節。
相關的源文件位於"\src-server\Photon.Common\LoadBalancer\LoadShedding "下。

遊戲伺服器會定期向主伺服器報告它們當前的工作負荷。
工作負載包括,例如:

  • CPU使用率
  • 流量
  • 一些Photon的特定值,如ENet+隊列長度,伺服器在每個請求上花費的平均時間,等等。
  • 延遲(向自己發送請求時)

最重要的(也是最容易理解的)因素是CPU負載,所以我們在本文檔中主要討論CPU負載。

所有這些因素都歸納為一個單一的數值-遊戲伺服器的 "負載水平",它被報告給主伺服器。

負載水平越低,遊戲伺服器就越適合主持新遊戲。

執行細節

遊戲伺服器收集關於上述因素的 "反饋"。
每個因素都有一個FeedbackController對象-它由一個FeedbackName和一個FeedbackLevel組成:

C#

namespace Photon.Common.LoadBalancer.LoadShedding
{
    public enum FeedbackName
    {
        CpuUsage, 
        PeerCount, 
        Bandwidth,
        OutOfRotation
    }
}

C#

namespace Photon.Common.LoadBalancer.LoadShedding
{
    public enum FeedbackLevel
    {
        Lowest = 0,
        Level0 = Lowest,
        Level1 = 1,
        Level2 = 2,
        Level3 = 3,
        Level4 = 4,
        Level5 = 5,
        Level6 = 6,
        Level7 = 7,
        Level8 = 8,
        Level9 = 9,
        Highest = Level9,
        LEVELS_COUNT
    }
}

DefaultConfiguration類定義了每個值的閾值。
例如,一台伺服器在CPU使用率達到10%時有 "最低 "的FeedbackLevel,在CPU達到90%時達到 "最高 "的FeedbackLevel,以此類推。

C#

// src-server\Photon.Common\LoadBalancer\LoadShedding\Configuration\DefaultConfiguration.cs: 
internal class DefaultConfiguration
{
    internal static List<FeedbackController> GetDefaultControllers()
    {
        var cpuController = new FeedbackController(
        FeedbackName.CpuUsage,
        new SortedDictionary<FeedbackLevel, FeedbackLevelData>
                {
                    { FeedbackLevel.Level0, new FeedbackLevelData(10, 0) },
                    { FeedbackLevel.Level1, new FeedbackLevelData(20, 9 ) },
                    { FeedbackLevel.Level2, new FeedbackLevelData(30, 19) },
                    { FeedbackLevel.Level3, new FeedbackLevelData(40, 29) },
                    { FeedbackLevel.Level4, new FeedbackLevelData(50, 38) },
                    { FeedbackLevel.Level5, new FeedbackLevelData(60, 48) },
                    { FeedbackLevel.Level6, new FeedbackLevelData(70, 57) },
                    { FeedbackLevel.Level7, new FeedbackLevelData(80, 67) },
                    { FeedbackLevel.Level8, new FeedbackLevelData(90, 77) },
                    { FeedbackLevel.Level9, new FeedbackLevelData(int.MaxValue, 77) }
                },
        0,
        FeedbackLevel.Lowest);

    // [...]
}

這些值也可以在 "workload.1Gps.config "文件中進行配置。
參見"workload.1Gps.config 範例"。
Photon.Common.LoadBalancer.LoadShedding.Configuration命名空間負責從配置文件中讀取數值,如果沒有配置文件,則應用DefaultConfiguration。

每隔一段時間,遊戲伺服器會檢查一些Windows性能計數器,為其所有的FeedbackControllers設置當前值,並計算新的 "整體反饋"。

這是在WorkloadController類別中完成的:

C#

// src-server\Photon.Common\LoadBalancer\LoadShedding\WorkloadController.cs
private void Update()
{
    if (!this.IsInitialized)
    {
        return;
    }

    FeedbackLevel oldValue = this.feedbackControlSystem.Output;

    if (this.cpuCounter.IsValid)
    {
        var cpuUsage = (int)this.cpuCounter.GetNextAverage();
        Counter.CpuAvg.RawValue = cpuUsage;

        WorkloadPerformanceCounters.WorkloadCPU.RawValue = cpuUsage;
        FeedbackLevel level;
        this.feedbackControlSystem.SetCpuUsage(cpuUsage, out level);
        WorkloadPerformanceCounters.WorkloadLevelCPU.RawValue = (byte)level;
    }

    if (this.bytesInOutCounters.IsValid)
    {
        int bytes = (int)this.bytesInOutCounters.GetNextAverage();
        Counter.BytesInAndOutAvg.RawValue = bytes;

        WorkloadPerformanceCounters.WorkloadBandwidth.RawValue = bytes;
        FeedbackLevel level;
        this.feedbackControlSystem.SetBandwidthUsage(bytes, out level);
        WorkloadPerformanceCounters.WorkloadLevelBandwidth.RawValue = (byte)level;
    }

    this.FeedbackLevel = this.feedbackControlSystem.Output;
    Counter.LoadLevel.RawValue = (byte)this.FeedbackLevel; 

    if (oldValue != this.FeedbackLevel)
    {
        if (log.IsInfoEnabled)
        {
            log.InfoFormat("FeedbackLevel changed: old={0}, new={1}", oldValue, this.FeedbackLevel);
        }

        this.RaiseFeedbacklevelChanged();
    }

    this.UpdateWindowsSpecificCounters();
}

如果整體反饋水平發生變化,RaiseFeedbacklevelChanged將向主伺服器報告新的伺服器狀態,這將觸發OutgoingMasterServerPeer類中的UpdateServerState

C#

// src-server\LoadBalancing\LoadBalancing\GameServer\OutgoingMasterServerPeer.cs
private void UpdateServerState()
{
    if (this.Connected == false)
    {
        return;
    }

    this.UpdateServerState(
        this.application.WorkloadController.FeedbackLevel,
        this.application.PeerCount,
        this.application.WorkloadController.ServerState);
}

workload.config 範例

XML

<?xml version="1.0" encoding="utf-8" ?>
<FeedbackControlSystem>
  <FeedbackControllers>
    <!-- CPU -->
    <add Name="CpuUsage" InitialInput="0" InitialLevel="Lowest">
      <FeedbackLevels>
        <add Level="Level0" Value="10"  ValueDown="0"/>
        <add Level="Level1" Value="20"  ValueDown="9"/>
        <add Level="Level2" Value="30"  ValueDown="19"/>
        <add Level="Level3" Value="40"  ValueDown="29"/>
        <add Level="Level4" Value="50"  ValueDown="39"/>
        <add Level="Level5" Value="60"  ValueDown="49"/>
        <add Level="Level6" Value="70"  ValueDown="58"/>
        <add Level="Level7" Value="80"  ValueDown="67"/>
        <add Level="Level8" Value="90"  ValueDown="77"/>
        <add Level="Level9" Value="110" ValueDown="77"/>
     </FeedbackLevels>
    </add>

    <!-- Bandwidth -->
    <!-- 10 / 30 / 40 / 80 / 100 MB -->
    <add Name="Bandwidth" InitialInput="0" InitialLevel="Lowest">
        <FeedbackLevels>        
            <add Level="Level0" Value="10485760"  ValueDown="0" />
            <add Level="Level1" Value="20971520"  ValueDown="10480000" />
            <add Level="Level2" Value="31457280"  ValueDown="20968000" />
            <add Level="Level3" Value="41943040"  ValueDown="31450000" />
            <add Level="Level4" Value="52428800"  ValueDown="41940000" />
            <add Level="Level5" Value="62914560"  ValueDown="52420000" />
            <add Level="Level6" Value="73400320"  ValueDown="62910000" />
            <add Level="Level7" Value="83886080"  ValueDown="73390000" />
            <add Level="Level8" Value="94371840"  ValueDown="83880000" />
            <add Level="Level9" Value="115343360" ValueDown="83880000" /><!-- Level1*11 -->
        </FeedbackLevels>
    </add>
  </FeedbackControllers>

只有 "最高"會有後果,如果它的值被超過,那麼在這個遊戲伺服器上就不會再有遊戲被創建。
如果您想使用更高的限制,您應該測試應用程序是否能夠處理信息的數量。

負載平衡算法

參見"\src-server\Loadbalancing\Loadbalancing.sln "解決方案中的LoadBalancing.LoadBalancer類,以了解執行細節。

主伺服器在 LoadBalancer 類中存儲每個遊戲伺服器的LoadLevel。
它還持有一個額外的列表,其中包括所有目前擁有最低負載水平的伺服器。

每當客戶端調用CreateGame 操作時,主伺服器就會從LoadBalancer中獲取負載最低的伺服器的地址,並將其返回給客戶端,然後客戶端就會連接到該伺服器。

配置和部署

出於演示的目的,SDK在其部署目錄中包含了1個主伺服器和1個遊戲伺服器的設置:

  • "deploy\LoadBalancing\Master"
  • "deploy\LoadBalancing\GameServer"

這個設置只用於本地開發。

部署一個遊戲伺服器

當您把您的LoadBalancing項目部署到生產伺服器上時,您不應該在一台伺服器上主持兩個遊戲伺服器應用程序。

您需要確保這些遊戲伺服器能夠在主伺服器上注冊。

在 "Photon.LoadBalancing.dll.config "中設置主伺服器的公共IP。

將 "GameServer.xml.config "中的MasterIPAddress設置為主伺服器的公共IP。

您還需要確保遊戲客戶端能夠索取遊戲伺服器。
在每個遊戲伺服器上,您需要設置該遊戲伺服器的公共IP地址。
如果您把這個值留空,公共IP地址將被自動檢測。

XML

<GameServer>
      <MasterIPAddress>127.0.0.1</MasterIPAddress>      
      <PublicIPAddress>127.0.0.1</PublicIPAddress>
        <!-- use this to auto-detect the PublicIPAddress: -->
        <!-- <PublicIPAddress></PublicIPAddress> -->
      <!-- [...] -->
</GameServer>

您也可以用Photon Control來設置一個公共IP。

部署一個主伺服器

您需要確保您只有一個主伺服器:要麼從您的遊戲伺服器上的PhotonServer.config中刪除所有 "主 "應用程序的設置,要麼至少確保您的遊戲伺服器和客戶端都使用相同的IP連接到同一個單一的主伺服器。

否則,在主伺服器上無法特殊配置。

將遊戲伺服器從輪替中取出

正如在"Loadbalancing Implementation "一節中所討論的,主伺服器知道遊戲伺服器的狀態,正如 "ServerState "舉例中所指出的。

  • "在線"(這是默認的)。
  • "非輪替"(=開放的遊戲仍然列在大廳中,玩家可以加入該伺服器上的現有遊戲,但不會創建新遊戲)
  • "離線"(該伺服器上的現有遊戲不能被加入,並且沒有新的遊戲在該GS上創建)。

GS定期向主伺服器發送他們的伺服器狀態-見 OutgoingMasterServerPeer 類別:

C#

public void UpdateServerState()
{
  if (this.Connected == false)
  {
    return;
  }

  this.UpdateServerState(
                this.application.WorkloadController.FeedbackLevel,
                this.application.PeerCount,
                this.application.WorkloadController.ServerState);
}

如果您想以編程方式設置伺服器狀態,您需要做的是:

  • 修改WorkloadController類別,使其能夠確定當前的伺服器狀態
  • 例如,您可以添加一個 "文件檢視者",從一個文本文件中讀取伺服器狀態(0 / 1 / 2)。

您也可以建立一個從客戶端調用的操作,從數據庫或任何您想到的地方讀取。

端口配置

要改變應用程序的端口,請編輯 "PhotonServer.config "中配置的監聽節點中的 "端口 "屬性值。
改變您的客戶端以連接到新的主伺服器端口。
重新啟動Photon伺服器。

改變主伺服器端口

如果您碰巧改變了 "主 "應用程序的端口,您需要更新NameServer程序的配置。
這樣做的目的是為了改變與改變端口的監聽的協議相匹配的設置。
使用下面的列表來獲得監聽名稱和設置名稱之間的映射:

  • "deploy\bin_Win64\PhotonServer.config" 中的監聽名稱 -> "deploy\NameServer\bin\NameServer.xml.config"的設置名稱
  • "UDPListener" -> "MasterServerPortUdp"
  • "TCPListener" -> "MasterServerPortTcp"
  • "HTTPListener", 取決於 "PeerType "和 "Secure"(還有 "Url")值:
    • "Simple" -> "MasterServerPortHttp" / "MasterServerPortSecureHttp"
    • "Reliable" -> "MasterServerPortHttp" / "MasterServerPortSecureHttp"
    • "WebSocket" -> "MasterServerPortWebSocket" / "MasterServerPortSecureWebSocket" ("MasterServerWsPath" 也可以根據監聽的 "Url "來改變)

Example:

如果您在 "deploy\bin_Win64\PhotonServer.config "中把主伺服器的UDP端口從默認的5055改為5555。

XML

<UDPListener
    IPAddress="0.0.0.0"
    Port="5555"
    OverrideApplication="Master">
</UDPListener>

您需要編輯 "deploy\NameServer\bin\NameServer.xml.config "中的 "MasterServerPortUdp "值,以匹配UDPListener中設置的值:

XML

<NameServer>
  <MasterServerPortUdp>5555</MasterServerPortUdp>
</NameServer>

在 "deploy\NameServer\bin\NameServer.xml.config "中為 "NameServer "應用程序配置的 "MasterServerPortXXX "的值與 "NameServer "應用程序在驗証每個協議時與 "Master "地址一起返回給客戶端的端口號相對應。
如果某個協議的端口沒有配置(缺失)或沒有正確配置(不匹配),客戶可能會出現連接到主伺服器的問題。

改變遊戲伺服器端口

如果您碰巧改變了 "遊戲 "應用程序的端口,您需要更新該應用程序的配置。
我們的想法是,改變與改變端口的監聽的協議相匹配的設置。
使用下面的列表來獲得監聽名稱和設置名稱之間的映射:

  • "deploy\bin_Win64\PhotonServer.config" 中的監聽名稱-> "deploy\LoadBalancing\GameServer\bin\GameServer.xml.config"的設置名稱
  • "UDPListener" -> "GamingUdpPort"
  • "TCPListener" -> "GamingTcpPort"
  • "HTTPListener", 取決於 "PeerType "和 "Secure"(還有 "Url")值:
    • "Simple" -> "GamingHttpPort" / "GamingHttpsPort"
    • "Reliable" -> "GamingHttpPort" / "GamingHttpsPort"
    • "WebSocket" -> "GamingWebSocketPort" / "GamingSecureWebSocketPort" ("GamingWsPath" 也可以根據監聽的 "Url "來改變)

Example:

如果您在 "deploy\bin_Win64\PhotonServer.config "中把遊戲伺服器的UDP端口從默認的5056改為6666。

XML

<UDPListener
    IPAddress="0.0.0.0"
    Port="6666"
    OverrideApplication="Game">
</UDPListener>

您需要編輯 "deploy\LoadBalancing\GameServer\bin\GameServer.xml.config "中的 "GamingUdpPort "值,以匹配UDPListener中設置的值。

XML

<GameServer>
  <Master>
    <GamingUdpPort>6666</GamingUdpPort>
  </Master>
</GameServer>

在 "deploy\LoadBalancing\GameServer\bin\GameServer.xml.config "中為 "遊戲 "應用程序配置的 "GamingXXXPort "設置值與 "Master "應用程序在匹配中為每個協議返回給客戶端的 "遊戲 "地址的端口號相對應。
如果一個協議的端口沒有被配置(缺失),或者沒有正確配置(不匹配),客戶可能會有問題,使用該協議加入或創建房間。
事實上,他們將無法連接到 "遊戲 "伺服器應用程序。

添加端口

您可以為每個應用程序配置多個端口。
如果一些客戶在使用某種協議到達某個應用程序時遇到問題,而您又不能為所有客戶改變該協議,那麼這可能很有用。
客戶端在需要時需要明確地切換到這些端口,特別是在連接到 "遊戲 "伺服器應用程序時。

Example:

默認情況下,我們為每個應用程序提供備選的UDP端口。
客戶端可以使用5055端口連接到Master,也可以使用27001。
客戶端可以使用5056端口連接到Game,也可以選擇27002。
客戶端可以使用5058端口連接到NameServer,也可以選擇27000。

XML

    <UDPListeners>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="5055"
        OverrideApplication="Master">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="27001"
        OverrideApplication="Master">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="5056"
        OverrideApplication="Game">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="27002"
        OverrideApplication="Game">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="5058"
        OverrideApplication="NameServer">
      </UDPListener>
      <UDPListener
        IPAddress="0.0.0.0"
        Port="27000"
        OverrideApplication="NameServer">
      </UDPListener>
    </UDPListeners>
Back to top