Mam problem z utworzeniem interfejsu sieciowego dla bardzo prostej gry, którą utworzyłem w Xna. Po prostu musiałbym wysłać obiekty za pośrednictwem klienta/gniazda TCP. Np .: Mam klasę o nazwie "Gracz". W każdym odtwarzaczu jest nazwa pola "Informacje" typu "PlayerInfo". W kliencie/serwerze muszę przesłać informacje o każdym graczu każdemu klientowi oprócz tego, który go wysłał (oczywiście). To jest prosty przykład, ale musiałbym to zrobić z około 5 - 10 obiektami, a także wysyłanie aktualizacji odtwarzacza (pozycje, akcje ...). Czy istnieje prosty sposób, aby to zrobić za pomocą TCP/Sock? Uwaga: Oceniłbym swoją wiedzę w języku C# i programowanie jako 6/10, więc nie musisz wyjaśniać wszystkiego, jeśli masz rozwiązanie (np. Jaka jest różnica pomiędzy zmienną a polem). Wiem również o interfejsach, bibliotekach i tak dalej ... Z góry dziękuję!Wysyłaj wpisane obiekty za pośrednictwem tcp lub gniazd
Odpowiedz
Mam jedno podejście, które polecam i dwa mniejsze, które są zależne od wielu rzeczy.
Pierwsza z nich oznacza, że już wiesz, jak używać klasy Socket, ale masz wiele klas, które musisz przesłać.
Z punktu widzenia transportu powinieneś utworzyć/wziąć pod uwagę tylko jedną bardzo prostą klasę. Nazwijmy tę klasę MyMessage:
public class MyMessage {
public byte[] Data { get; set; }
}
Ok. Z punktu widzenia TCP wszystko, co musisz zrobić, to upewnić się, że możesz przekazywać instancje tej klasy (od klientów do serwera iz powrotem). Nie będę zagłębiać się w szczegóły tego działania, ale zwrócę uwagę, że jeśli to zrobisz, przekształcisz naturę połączenia TCP/IP z "bajtu-strumienia" w "strumień wiadomości". Oznacza to, że zwykle protokół TCP/IP nie gwarantuje, że porcje danych wysyłanych przez połączenie docierają do miejsca docelowego w tych samych formacjach (mogą zostać połączone lub rozdzielone).Jedyne, co gwarantuje to, że bajty wszystkich fragmentów ostatecznie dotrą w tej samej kolejności na drugim końcu połączenia (zawsze).
Po uruchomieniu strumienia komunikatów można użyć starej dobrej serializacji .NET do enkapsulacji dowolnego wystąpienia klasy we właściwości Data. Dzieje się tak, ponieważ serializuje wykresy obiektów w bajtach i na odwrót.
Sposób to zrobić (najczęściej) jest użycie standardowej klasy Library: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter który można znaleźć w pliku mscorlib.dll tak:
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);
}
}
Klasa BinaryFormatter może przechodzić przez drzewa/wykresy obiektów rozpoczynając od głównego katalogu/sentinela podanego jako drugi argument metody Serialize (Stream, object) i zapisywać wszystkie pierwotne wartości oraz informacje o typie i względne informacje o pozycji do podany strumień. Jest również w stanie dokonać dokładnego odwrócenia i deserializacji całego wykresu obiektu, o ile podany strumień jest ustawiony zgodnie z miejscem serializacji byłych obiektów.
Jest tu jednak kilka chwytów: będziesz musiał opisać wszystkie klasy za pomocą [SerializableAttribute]. Jeśli zajęcia zawierają pola, które są z innych klas napisanych przez ciebie, a ty powiedziałeś robią:
[SerializableAttribute]
public class Player {
public PlayerInfo Info;
//... etc
to trzeba opisywanie tych z [SerializableAttribute] też:
[SerializableAttribute]
public class PlayerInfo { //... etc
Jeśli zajęcia zawierają pola należące do typów pisanych przez innych (np. Microsoft), to te lepiej już zostały opatrzone adnotacją z atrybutem. Większość z tych, które można już serializować, to. Rodzaje prymitywów są w naturalny sposób szeregowalne. Rzeczy, które nie powinny być szeregowane są: FileStreams, Nici, gniazda, itp
Po upewnieniu masz zajęcia SERIALIZABLE wszystko, co trzeba zrobić, to szeregować swoje wystąpienia, wysłać je odbierać je i deserializowania je:
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
}
Choć po stronie serwera:
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);
}
}
Potrzebny jest projekt wspólnego zespołu (biblioteka klasy), aby odwoływać się zarówno projekty wykonywalnych (klient i serwer).
Wszystkie klasy, które trzeba przekazać, będą musiały zostać zapisane w tym zestawie, tak aby zarówno serwer, jak i klient wiedzieli, jak rozumieć się nawzajem na tym bardzo szczegółowym poziomie.
Jeśli serwer nie musi rozumieć, co jest powiedziane między klientami, a jedynie przekazywać wiadomości (transmitując jedną wiadomość do innych klientów N-1), to zapomnij o tym, co powiedziałem o wspólnym zespole. W tym konkretnym przypadku serwer widzi tylko bajty, podczas gdy klienci mają głębsze zrozumienie faktycznych wiadomości wysyłanych tam iz powrotem.
Powiedziałem, że mam trzy podejścia.
Drugi z nich to .NET Remoting, który może wymagać dużego nakładu pracy, ale który jest trudny do przeżycia, jeśli nie rozumie się go w pełni.Możesz przeczytać o tym na MSDN, tutaj: http://msdn.microsoft.com/en-us/library/kwdt6w2k(v=vs.100).aspx
Trzeci byłby lepszy tylko wtedy, gdy (teraz lub w przyszłości) przez XNA masz na myśli Windows Phone lub inną implementację XNA, która nie obsługuje klasy BinaryFormatter (ExEn z MonoTouch lub innymi). W takim przypadku byłoby ci ciężko, gdybyś potrzebował swojego serwera (pełnej, staroświeckiej aplikacji .NET), aby odnieść się do wspólnego zgromadzenia, o którym mówiłem, a także mieć projekt gry (który nie byłby dobrym staromodnym Aplikacja .NET, ale ma raczej egzotyczną naturę) odwołuje się do tego samego zestawu.
W takim przypadku będziemy musieli użyć i na przemian formę serializacji i deserializacji obiektów. Konieczne jest również identyczne wdrożenie dwóch zestawów klas w dwóch światach (.NET i WP7 lub WP8). Możesz użyć jakiejś postaci serializatorów XML, które musiałbyś jawnie odwzorować na swoje klasy (nie tak potężne, jak klasa BinaryFormatter, ale bardziej uniwersalne w tym, co może być natura środowiska wykonawczego hostującego twoje klasy).
Można przeczytać o klasie XMLSerializer na MSDN, tutaj: http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx
Wszystkie te rozwiązania są bardzo interesujące, ale jedyny problem, jaki mogę wymyślić, to: Jak się dowiedzieć, jaki to jest typ po stronie odbiorcy? Jeśli deserializuję coś, czy będę mógł pobrać typ dla obsady? Dzięki za wszystkie odpowiedzi! Bardzo pomocne. –
Jeśli użyjesz podejścia BinaryFormatter, będzie to wyglądało tak, jakby dwa odpowiedniki były w tym samym procesie. W skrócie: masz podstawowy problem z rzuceniem grup przedmiotów na obiekt, a następnie próbujesz dowiedzieć się, czym naprawdę są przedmioty (gracz, ruch, interakcja?). Więc jeśli użyjesz czegoś takiego jak (obj to Player) {Player asPlayer = obj jako Player;/* wykonaj konkretne przetwarzanie odtwarzacza */rozwiążesz swoje pytanie. Metoda deserializacji już wie (w czasie wykonywania) to, czego potrzebujesz tylko do wysłania (to gracz, czy nie, it'a ruch, itp.). –
Po prostu logicznie rzecz biorąc, Microsoft nie mógł odgadnąć, że Ty, w lutym 2013 r., Napiszesz klasę gracza i będziesz potrzebował jej deserializacji. Tak więc na poziomie syntaktycznym, pisemnym, desingowym metoda ta zwraca obiekt. –
Można utworzyć własne rozwiązanie, korzystając z różnych klas dostępnych w ramach .net. Użytkownik chciałby sprawdzić nazwę usługi WCF lub nazwy gniazd, w szczególności klasy TcpClient i TcpListener, patrz: MSDN. Jest mnóstwo świetnych samouczków, jeśli wykonasz wyszukiwanie związane z ich użyciem. Trzeba również rozważyć, jak przekształcić wpisane obiekty w tablice bajtów, podobne do tego question.
Alternatywnym podejściem byłoby korzystanie z biblioteki sieciowej. Istnieją biblioteki niskiego poziomu i biblioteki wysokiego poziomu. Biorąc pod uwagę twój poziom doświadczenia w programowaniu i konkretny cel końcowy sugerowałbym bibliotekę wysokiego poziomu. Przykładem takiej biblioteki sieciowej może być lidgren. Jestem twórcą innej biblioteki sieciowej networkComms.net i szybki przykład jak można wysłać wpisane obiektów za pomocą tej biblioteki następująco:
Shared podstawa (definiuje przedmiot 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;
}
}
Client (wysyła pojedynczy celem gracza):
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();
}
}
}
Serwer:
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();
}
}
}
Powyższe jest zmodyfikowaną wersją tego tutorial. Oczywiście będziesz musiał pobrać bibliotekę DLL NetworkCommsDotNet ze strony internetowej, aby można było dodać ją do referencji "using NetworkCommsDotNet". Zobacz również adres IP serwera w przykładzie klienta jest obecnie "127.0.0.1", powinno to działać, jeśli uruchomisz zarówno serwer jak i klienta na tym samym komputerze.
Hmm. Ciekawy. –
moja osobista szybkiego i oczyszczone rozwiązania, korzystając 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>();
}
}
Teraz można szeregować swoje obiekty tak jak :
Player player = ...;
Enemy enemy = ...;
string data1 = JMessage.Serialize(JMessage.FromValue(player));
string data2 = JMessage.Serialize(JMessage.FromValue(enemy));
Wyślij tych danych w całej drutu, a następnie na drugim końcu można zrobić coś takiego:
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...
Wszystkie te rozwiązania są bardzo interesujące, ale jedyny problem, jaki mogę wymyślić, to: Jak się dowiedzieć, jaki jest typ po stronie odbiorcy? Jeśli deserializuję coś, czy będę mógł pobrać typ dla obsady? Dzięki za wszystkie odpowiedzi! Bardzo pomocne. –
Opisana przeze mnie metoda rozwiązuje dokładnie twój problem, a mianowicie, jak określić typ. Zobacz jak mówię 'message.Type == typeof (Player)' lub 'message.Type == typeof (Enemy)' w przykładzie? W ten sposób "pobierasz typ do obsady". –
Po ponad 2 latach znalazłem nowe sposoby rozwiązania tego problemu i pomyślałem, że udostępnienie go może być przydatne dla kogoś. Pamiętaj, że zaakceptowana odpowiedź jest nadal ważna.
Najprostszym sposobem serializacji wpisanych obiektów, które mogłem znaleźć, jest użycie konwertera json w Json.NET. Istnieje obiekt ustawień, który umożliwia zapisanie typu w jsonie jako wartości o nazwie $type
. Oto jak to zrobić i otrzymaną json:
JsonSerializerSettings settings = new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
};
JsonConvert.SerializeObject(myObject, settings);
Json wynik:
{
"$type" : "Testing.MyType, Testing",
"ExampleProperty" : "Hello world!"
}
Podczas deserializacji, jeśli stosuje to samo ustawienie, celem prawidłowego typu będą rozszeregować. Dokładnie to, czego potrzebowałem! Mam nadzieję że to pomoże.
- 1. Wysyłaj dane EXTRA za pośrednictwem problemu PendingIntent.
- 2. Łączność TCP/IP za pośrednictwem DataSnap
- 3. Różnice między gniazd TCP i gniazd internetowych, jeszcze raz
- 4. Wysyłaj i odbieraj dane TCP w ruby
- 5. Czas na Linuksa dla gniazd TCP
- 6. Wysyłaj i odbieraj dane NSData za pośrednictwem GameKit
- 7. Komunikaty o buforach protokołów za pośrednictwem Akka IO i gniazd
- 8. Wysyłaj binarny bufor do klienta za pośrednictwem http.ServerResponse w Node.js
- 9. Python: wysyłanie danych między dwoma komputerami za pośrednictwem gniazd
- 10. Jak mogę przesyłać dźwięk z mikrofonu iPhone'a na komputer Mac/PC za pośrednictwem gniazd lub architektury?
- 11. Czy istnieje lepszy sposób korzystania z asynchronicznych gniazd TCP w C++ zamiast ankiety lub wyboru?
- 12. Jakakolwiek różnica między połączeniem gniazd a połączeniem TCP?
- 13. Prosty i szybki asynchroniczny binarny serwer gniazd TCP?
- 14. Wysyłaj dane do wielu gniazd za pomocą rur, tee() i splice()
- 15. Wysyłaj lub wysyłaj wiadomość do pętli komunikatów Windows Forms
- 16. Kerberos za pośrednictwem javascript lub HTML 5
- 17. Wysyłaj wiadomości za pomocą whatsapi.net?
- 18. Jaki jest koszt nawiązania połączenia za pomocą gniazd domen uniksowych w porównaniu z gniazdami TCP?
- 19. Python - Wysyłaj wiadomość e-mail w formacie HTML za pośrednictwem programu Outlook 2007/2010 i win32com
- 20. Wysyłanie wartości pustych za pośrednictwem AJAX
- 21. Wysyłaj ArrayBuffer z innym ciągiem w jednym wywołaniu Ajax za pośrednictwem jQuery
- 22. Jak kodować strukturę C/C++ delta do transmisji za pośrednictwem gniazd
- 23. Wysyłaj wiadomości EMS za pomocą smsj api
- 24. Dobry pulpit gniazd klienta
- 25. Wysyłanie Devise e-maili za pośrednictwem Resque
- 26. Wysyłaj wiadomości e-mail za pośrednictwem interfejsu API Mandrill zawierającego wiele adresów BCC.
- 27. Wysyłaj strumień zdjęć za pomocą ImageIO?
- 28. Podłącz klienta mqtt za pośrednictwem gniazd internetowych z HTTPS z przeglądarki
- 29. Jak oglądać transakcje bitcoinem za pośrednictwem blockchain za pośrednictwem nodejs?
Powiedziałbym, że trzeba stworzyć własne rozwiązanie, mechanizm serializacji o wysokiej wydajności. Coś na wzór pakowania bitowego byłoby wystarczająco szybkie dla małych informacji. – Machinarius
Czy XNA obsługuje WCF? Jeśli tak, to byłaby to właściwa droga. –