Xnaで作成した非常に単純なゲームのネットワークインターフェイスを作成するのに問題があります。TCPクライアント/ソケットを介してオブジェクトを送信する必要があります。例:「Player」という名前のクラスがあります。すべてのプレーヤーには、「PlayerInfo」タイプの「Info」というフィールド名があります。クライアント/サーバーでは、(明らかに)送信したクライアントを除くすべてのクライアントにすべてのプレーヤーの情報を送信する必要があります。これは単純な例ですが、約5〜10個のオブジェクトを使用してこれを行う必要があります。さらに、プレーヤーの更新(位置、アクションなど)を送信する必要があります。TCP/ Sockを使用してこれを行う簡単な方法はありますか?注:C#とプログラミングに関する知識を6/10と評価するので、解決策がある場合はすべてを説明する必要はありません(例:変数とフィールドの違いは何ですか)。インターフェイスやライブラリなどについても知っています…よろしくお願いします!
4 に答える
私がお勧めする1つのアプローチと、多くのものに依存する2つのより少ないアプローチがあります。
最初の例は、Socketクラスの使用方法をすでに知っているが、それを介して送信する必要のあるクラスがたくさんあることを意味します。
トランスポートの観点からは、非常に単純なクラスを1つだけ作成/考慮する必要があります。このクラスをMyMessageと呼びましょう。
public class MyMessage {
public byte[] Data { get; set; }
}
Ok。TCPの観点から、あなたがする必要があるのは、このクラスのインスタンスを(クライアントからサーバーへ、そしてその逆へ)渡すことができることを確認することだけです。これについては詳しく説明しませんが、これを行うことができれば、TCP/IP接続の性質を「バイトストリーム」から「メッセージストリーム」に変換できることを指摘しておきます。つまり、通常、TCP / IPは、接続を介して送信するデータのチャンクが同じフォーメーションで宛先に到着することを保証しません(それらは一緒に結合されたり分割されたりする可能性があります)。それが保証する唯一のことは、すべてのチャンクのバイトが最終的に接続のもう一方の端に同じ順序で到着することです(常に)。
メッセージストリームが稼働しているので、.NETの古き良きシリアル化を使用して、Dataプロパティ内の任意のクラスインスタンスをカプセル化できます。それは、オブジェクトグラフをバイトに、またはその逆にシリアル化することです。
これを行う方法(最も一般的には)は、次のようにmscorlib.dllにある標準ライブラリクラスSystem.Runtime.Serialization.Formatters.Binary.BinaryFormatterを使用することです。
public static class Foo {
public static Message Serialize(object anySerializableObject) {
using (var memoryStream = new MemoryStream()) {
(new BinaryFormatter()).Serialize(memoryStream, anySerializableObject);
return new Message { Data = memoryStream.ToArray() };
}
}
public static object Deserialize(Message message) {
using (var memoryStream = new MemoryStream(message.Data))
return (new BinaryFormatter()).Deserialize(memoryStream);
}
}
BinaryFormatterクラスは、Serialize(Stream、object)メソッドの2番目の引数として提供されたルート/センチネルから始まるオブジェクトのツリー/グラフをトラバースし、すべてのプリミティブ値に加えて、タイプ情報と相対位置情報を提供されたものに書き込むことができます。ストリーム。提供されたストリームが以前のオブジェクトグラフのシリアル化の場所に応じて配置されている限り、オブジェクトグラフ全体を正確に逆にして逆シリアル化することもできます。
ただし、ここにはいくつかの落とし穴があります。すべてのクラスに[SerializableAttribute]で注釈を付ける必要があります。あなたのクラスにあなたが書いた他のクラスのフィールドが含まれていて、あなたがそう言った場合:
[SerializableAttribute]
public class Player {
public PlayerInfo Info;
//... etc
次に、[SerializableAttribute]でそれらに注釈を付ける必要があります。
[SerializableAttribute]
public class PlayerInfo { //... etc
クラスに他の人(たとえばMicrosoft)によって作成されたタイプのフィールドが含まれている場合、それらにはすでに属性の注釈が付けられている方がよいでしょう。すでにシリアル化できるもののほとんどはそうです。プリミティブ型は自然にシリアル化できます。シリアル化してはいけないものは、FileStreams、Threads、Socketsなどです。
シリアル化可能なクラスがあることを確認したら、インスタンスをシリアル化し、送信し、受信し、逆シリアル化するだけです。
class Client {
public static void SendMovement(Movement movement) {
Message message = Foo.Serialize(movement);
socketHelper.SendMessage(message);
}
public static void SendPlayer(Player player) {
Message message = Foo.Serialize(player);
socketHelper.SendMessage(message);
}
// .. etc
public static void OnMessageReceivedFromServer(Message message) {
object obj = Foo.Deserialize(message);
if (obj is Movement)
Client.ProcessOtherPlayersMovement(obj as Movement);
else if (obj is Player)
Client.ProcessOtherPlayersStatusUpdates(obj as Player);
// .. etc
}
public static void ProcessOtherPlayersMovement(Movement movement) {
//...
}
// .. etc
}
サーバー側にいる間:
class Server {
public static void OnMessageReceived(Message message, SocketHelper from, SocketHelper[] all) {
object obj = Foo.Deserialize( message );
if (obj is Movement)
Server.ProcessMovement( obj as Movement );
else if (obj is Player)
Server.ProcessPlayer( obj as Player );
// .. etc
foreach (var socketHelper in all)
if (socketHelper != from)
socketHelper.SendMessage( message );
}
}
両方の実行可能プロジェクト(クライアントとサーバー)によって参照される共通のアセンブリプロジェクト(クラスライブラリ)が必要になります。
サーバーとクライアントの両方がこの非常に詳細なレベルで相互に理解する方法を理解できるように、渡す必要のあるすべてのクラスをそのアセンブリに書き込む必要があります。
サーバーがクライアント間で何が言われているのかを理解する必要がなく、メッセージを渡す(1つのメッセージを他のN-1クライアントにブロードキャストする)だけの場合は、共通のアセンブリについて私が言ったことを忘れてください。その特定のケースでは、サーバーはバイトのみを認識しますが、クライアントは実際に送受信されるメッセージをより深く理解しています。
私は3つのアプローチがあると言いました。
2つ目は、.NET Remotingです。これは、多くの作業を肩から外すことができますが、完全に理解していないと、一緒に暮らすのは困難です。これについては、MSDNのhttp://msdn.microsoft.com/en-us/library/kwdt6w2k(v=vs.100).aspxで読むことができます。
3つ目は、(現在または将来)XNAが、Windows PhoneまたはBinaryFormatterクラスをサポートしないXNAの別の実装(MonoTouchを使用したExEnなど)を意味する場合にのみ適しています。その場合、私が話した共通のアセンブリを参照するためにサーバー(本格的な古き良き.NETアプリケーション)が必要であり、ゲームプロジェクト(古き良き時代ではない)も必要な場合は、苦労するでしょう。 NETアプリですが、かなりエキゾチックな性質を持っています)まったく同じアセンブリを参照します。
その場合、オブジェクトのシリアル化と逆シリアル化の代替形式を使用する必要があります。また、2つの世界(.NETとWP7またはWP8)に2セットのクラスを同じように実装する必要があります。クラスに明示的にマップする必要がある何らかの形式のXMLシリアライザーを使用できます(BinaryFormatterクラスほど強力ではありませんが、クラスをホストするランタイムの性質がより用途が広い)。
XmlSerializerクラスについては、MSDNのhttp://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspxで読むことができます。
JSON.NETを使用した私の個人的な迅速でクリーンなソリューション:
class JMessage
{
public Type Type { get; set; }
public JToken Value { get; set; }
public static JMessage FromValue<T>(T value)
{
return new JMessage { Type = typeof(T), Value = JToken.FromObject(value) };
}
public static string Serialize(JMessage message)
{
return JToken.FromObject(message).ToString();
}
public static JMessage Deserialize(string data)
{
return JToken.Parse(data).ToObject<JMessage>();
}
}
これで、次のようにオブジェクトをシリアル化できます。
Player player = ...;
Enemy enemy = ...;
string data1 = JMessage.Serialize(JMessage.FromValue(player));
string data2 = JMessage.Serialize(JMessage.FromValue(enemy));
そのデータをネットワーク経由で送信すると、もう一方の端で次のようなことができます。
string data = ...;
JMessage message = JMessage.Deserialize(data);
if (message.Type == typeof(Player))
{
Player player = message.Value.ToObject<Player>();
}
else if (message.Type == typeof(Enemy))
{
Enemy enemy = message.Value.ToObject<Enemy>();
}
//etc...
.net Frameworkで提供されるさまざまなクラスを使用して、独自のソリューションを作成できます。WCFまたはSocketsnamepsace、特にTcpClientクラスとTcpListenerクラスをチェックアウトする必要があります。MSDNを参照してください。これらの使用に関連する検索を行う場合は、すばらしいチュートリアルがたくさんあります。この質問と同様に、型指定されたオブジェクトをバイト配列に変換する方法も検討する必要があります。
別のアプローチは、ネットワークライブラリを使用することです。低レベルのライブラリと高レベルのライブラリがあります。あなたのプログラミング経験のレベルと特定の最終目標を考えると、私は高レベルのライブラリを提案します。このようなネットワークライブラリの例は、lidgrenです。私は別のネットワークライブラリnetworkComms.netの開発者であり、このライブラリを使用して型付きオブジェクトを送信する方法の簡単な例を次に示します。
共有ベース(Playerオブジェクトを定義):
[ProtoContract]
class Player
{
[ProtoMember(1)]
public string Name { get; private set; }
[ProtoMember(2)]
public int Ammo { get; private set; }
[ProtoMember(3)]
public string Position { get; private set; }
private Player() { }
public Player(string name, int ammo, string position)
{
this.Name = name;
this.Ammo = ammo;
this.Position = position;
}
}
クライアント(単一のPlayerオブジェクトを送信します):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using NetworkCommsDotNet;
using ProtoBuf;
namespace Client
{
class Program
{
static void Main(string[] args)
{
Player player = new Player("MarcF", 100, "09.09N,21.12W");
//Could also use UDPConnection.GetConnection...
TCPConnection.GetConnection(new ConnectionInfo("127.0.0.1", 10000)).SendObject("PlayerData", player);
Console.WriteLine("Send completed. Press any key to exit client.");
Console.ReadKey(true);
NetworkComms.Shutdown();
}
}
}
サーバ:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using NetworkCommsDotNet;
using ProtoBuf;
namespace Server
{
class Program
{
static void Main(string[] args)
{
// Convert incoming data to a <Player> object and run this method when an incoming packet is received.
NetworkComms.AppendGlobalIncomingPacketHandler<Player>("PlayerData", (packetHeader, connection, incomingPlayer) =>
{
Console.WriteLine("Received player data. Player name was " + incomingPlayer.Name);
//Do anything else with the player object here
//e.g. UpdatePlayerPosition(incomingPlayer);
});
//Listen for incoming connections
TCPConnection.StartListening(true);
Console.WriteLine("Server ready. Press any key to shutdown server.");
Console.ReadKey(true);
NetworkComms.Shutdown();
}
}
}
上記は、このチュートリアルの修正版です。明らかに、WebサイトからNetworkCommsDotNet DLLをダウンロードして、「usingNetworkCommsDotNet」リファレンスに追加できるようにする必要があります。また、クライアントの例のサーバーIPアドレスは現在「127.0.0.1」です。これは、サーバーとクライアントの両方を同じマシンで実行している場合に機能するはずです。
2年以上経ち、この問題を解決する新しい方法を見つけました。それを共有することは誰かに役立つかもしれないと思いました。受け入れられた回答は引き続き有効であることに注意してください。
私が見つけた型付きオブジェクトをシリアル化する最も簡単な方法は、Json.NETのjsonコンバーターを使用することです。タイプをjsonに。という名前の値として保存できる設定オブジェクトがあります$type
。これを行う方法と結果のjsonは次のとおりです。
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
JsonConvert.SerializeObject(myObject, settings);
Jsonの結果:
{
"$type" : "Testing.MyType, Testing",
"ExampleProperty" : "Hello world!"
}
デシリアライズ時に同じ設定を使用すると、正しいタイプのオブジェクトがデシリアライズされます。まさに私が必要としていたものです!お役に立てれば。