VorwortEs ist relativ einfach, in Cocos Creator eine HTTP-Anfrage zu initiieren, aber viele Spiele möchten eine lange Verbindung zum Server aufrechterhalten, damit der Server aktiv Nachrichten an den Client senden kann, anstatt immer Anfragen vom Client zu initiieren. Dies gilt insbesondere für Spiele mit hohen Echtzeitanforderungen. Hier entwerfen wir ein allgemeines Netzwerk-Framework, das leicht auf unsere Projekte angewendet werden kann. WebSocket verwendenBevor wir dieses Netzwerk-Framework implementieren, wollen wir uns zunächst mit WebSocket vertraut machen. Websocket ist ein auf TCP basierendes Vollduplex-Netzwerkprotokoll, das es Webseiten ermöglicht, dauerhafte Verbindungen herzustellen und eine bidirektionale Kommunikation durchzuführen. Die Verwendung von WebSocket in Cocos Creator kann in H5-Webspielen verwendet werden und unterstützt auch die nativen Plattformen Android und iOS. Erstellen eines WebSocket-ObjektsBei der Verwendung von WebSocket sollte der erste Schritt darin bestehen, ein WebSocket-Objekt zu erstellen. Der Konstruktor des WebSocket-Objekts kann zwei Parameter übergeben, der erste ist die URL-Zeichenfolge und der zweite ist die Protokollzeichenfolge oder das Zeichenfolgenarray, das die zulässigen Unterprotokolle angibt. Der Server muss eines davon auswählen, um es zurückzugeben, bevor eine Verbindung hergestellt wird, aber wir verwenden es im Allgemeinen nicht. Der URL-Parameter ist sehr wichtig und besteht hauptsächlich aus vier Teilen: Protokoll, Adresse, Port und Ressource. Beispiel: ws://echo.websocket.org:
Websocket-StatusWebsocket hat 4 Zustände, die über die Eigenschaft readyState abgefragt werden können:
WebSocket-APIWebsocket hat nur zwei APIs, void send(data) zum Senden von Daten und void close(code, reason) zum Schließen der Verbindung. Die Sendemethode empfängt nur einen Parameter – die zu sendenden Daten, die einen der folgenden vier Typen haben können: string | ArrayBufferLike | Blob | ArrayBufferView.
Beim Senden von Daten hat der Beamte 2 Vorschläge:
Die Methode „close“ akzeptiert zwei optionale Parameter. „Code“ stellt den Fehlercode dar. Wir sollten 1000 oder eine Ganzzahl zwischen 3000 und 4999 übergeben. „Reason“ kann verwendet werden, um den Grund für das Schließen anzugeben. Die Länge darf 123 Bytes nicht überschreiten. WebSocket-RückrufWebsocket stellt uns 4 Rückruffunktionen zum Binden zur Verfügung:
Echo-BeispielNachfolgend sehen Sie den Code der Echo-Demo auf der offiziellen WebSocket-Website. Sie können ihn in eine HTML-Datei schreiben und mit einem Browser öffnen. Nach dem Öffnen wird automatisch eine WebSocket-Verbindung hergestellt. Wenn die Verbindung hergestellt ist, wird aktiv die Nachricht „WebSocket rockt“ gesendet. Der Server gibt die Nachricht zurück, löst onMessage aus, druckt die Informationen auf dem Bildschirm aus und schließt dann die Verbindung. Weitere Einzelheiten finden Sie unter: http://www.websocket.org/echo.html17
EntwurfsrahmenEin allgemeines Netzwerk-Framework muss in der Lage sein, die unterschiedlichen Anforderungen verschiedener Projekte zu unterstützen, vorausgesetzt, es ist universell. Erfahrungsgemäß lauten die gemeinsamen Anforderungen wie folgt:
Basierend auf den oben genannten Anforderungen teilen wir die Funktionsmodule auf, um eine hohe Kohäsion und geringe Kopplung der Module sicherzustellen. ProtocolHelper-Protokollverarbeitungsmodul - Wenn wir einen Puffer erhalten, müssen wir möglicherweise das Protokoll oder die ID kennen, die diesem Puffer entspricht. Wenn wir beispielsweise während der Anforderung den Rückruf zur Antwortverarbeitung übergeben, besteht die gängige Vorgehensweise möglicherweise darin, eine automatisch inkrementierende ID zu verwenden, um jede Anforderung zu unterscheiden, oder die Protokollnummer zu verwenden, um verschiedene Anforderungen zu unterscheiden. Dies müssen Entwickler implementieren. Müssen wir auch die Länge des Pakets aus dem Puffer abrufen? Welche Paketlänge ist sinnvoll? Wie sieht ein Heartbeat-Paket aus usw. Socket-Modul - implementiert die grundlegendste Kommunikationsfunktion. Definieren Sie zunächst die Socket-Schnittstellenklasse ISocket, definieren Sie Schnittstellen wie Verbinden, Schließen, Datenempfangen und -senden und erben und implementieren Sie diese Schnittstellen anschließend an Unterklassen. NetworkTips-Netzwerkanzeigemodul – realisiert die Anzeige von Status wie Verbinden, Wiederverbinden, Laden, Netzwerktrennung usw. sowie die Abschirmung der Benutzeroberfläche. NetNode-Netzwerkknoten – der sogenannte Netzwerkknoten, dessen Hauptaufgabe darin besteht, die oben genannten Funktionen in Reihe zu schalten und den Benutzern eine benutzerfreundliche Schnittstelle bereitzustellen. NetManager verwaltet einen Singleton von Netzwerkknoten . Wir haben möglicherweise mehrere Netzwerkknoten (mehrere Verbindungen), daher wird hier ein Singleton zur Verwaltung verwendet. Es ist auch bequemer, einen Singleton zum Bedienen von Netzwerkknoten zu verwenden. ProtokollHelferEine einfache IProtocolHelper-Schnittstelle wird hier wie folgt definiert: Exporttyp NetData = (Zeichenfolge | ArrayBufferLike | Blob | ArrayBufferView); // Protokoll-Hilfsschnittstelle Exportschnittstelle IProtocolHelper { getHeadlen(): Zahl; // Gibt die Länge des Paketheaders zurück getHearbeat(): NetData; // Gibt ein Heartbeat-Paket zurück getPackageLen(msg: NetData): Zahl; // Gibt die Länge des gesamten Pakets zurück checkPackage(msg: NetData): boolean; // Überprüft, ob die Paketdaten zulässig sind getPackageId(msg: NetData): Zahl; // Gibt die Paket-ID oder den Protokolltyp zurück } BuchseHier wird eine einfache ISocket-Schnittstelle definiert, wie unten gezeigt: //Socket-Schnittstelle exportiere Schnittstelle ISocket { onConnected: (Ereignis) => void; //Verbindung callbackonMessage: (msg: NetData) => void; //Nachricht callbackonError: (Ereignis) => void; //Fehler callbackonClosed: (Ereignis) => void; //Schließen callbackconnect(ip: Zeichenfolge, Port: Nummer); //Verbindung interfacesend(Puffer: NetData); //Daten werden gesendet interfaceclose(Code?: Nummer, Grund?: Zeichenfolge); //Schnittstelle schließen} Als nächstes implementieren wir einen WebSock, der von ISocket erbt. Wir müssen nur die Schnittstellen „Connect“, „Send“ und „Close“ implementieren. „Senden“ und „Schließen“ sind beides einfache Kapselungen von WebSockets, während „Verbinden“ eine URL entsprechend der übergebenen IP-Adresse, des Ports und anderer Parameter erstellen muss, um einen WebSocket zu erstellen und den Rückruf des WebSockets zu binden. Exportklasse WebSock implementiert ISocket { private _ws: WebSocket = null; // WebSocket-Objekt onConnected: (Ereignis) => void = null; beiNachricht: (msg) => void = null; bei Fehler: (Ereignis) => void = null; onClosed: (Ereignis) => void = null; verbinden(Optionen: beliebig) { wenn (diese._ws) { wenn (this._ws.readyState === WebSocket.CONNECTING) { console.log("WebSocket-Verbindung wird hergestellt, warten Sie einen Moment …") gibt false zurück; } } lass url = null; wenn(optionen.url) { url = Optionen.url; } anders { lass ip = options.ip; let port = Optionen.port; let Protokoll = Optionen.Protokoll; URL = `${Protokoll}://${IP}:${Port}`; } this._ws = neuer WebSocket(URL); this._ws.binaryType = options.binaryType ? options.binaryType : "Array-Puffer"; this._ws.onmessage = (Ereignis) => { dies.onMessage(Ereignis.Daten); }; dies._ws.onopen = dies.onConnected; dies._ws.onerror = dies.onError; dies._ws.onclose = dies.onClosed; gibt true zurück; } sende(Puffer: NetData) { wenn (this._ws.readyState == WebSocket.OPEN) { this._ws.send(Puffer); gibt true zurück; } gibt false zurück; } schließen(Code?: Nummer, Grund?: Zeichenfolge) { dies._ws.close(); } } NetzwerkTippsINetworkTips bietet eine sehr nützliche Schnittstelle sowie Wiederverbindungs- und Anforderungsschalter. Das Framework ruft sie zum richtigen Zeitpunkt auf. Wir können INetworkTips erben und unsere netzwerkbezogenen Eingabeaufforderungsinformationen anpassen. Es ist zu beachten, dass diese Schnittstellen **mehrmals** aufgerufen werden können. // Netzwerktipps-Schnittstelle Exportschnittstelle INetworkTips { connectTips(isShow: boolean): void; Tipps erneut verbinden(isShow: boolean): void; requestTips(isShow: boolean): void; } NetNodeNetNode ist der kritischste Teil des gesamten Netzwerk-Frameworks. Eine NetNode-Instanz stellt ein vollständiges Verbindungsobjekt dar. Basierend auf NetNode können wir es problemlos erweitern. Seine Hauptaufgaben sind: Verbindungswartung
Datenübertragung
Datenempfang
Schnittstellenanzeige
Als nächstes stellen wir die netzwerkbezogenen Mitgliedsfunktionen vor. Schauen wir uns zunächst die Initialisierung an und:
Die Methode onConnected wird aufgerufen, nachdem die Netzwerkverbindung erfolgreich hergestellt wurde, und der Authentifizierungsprozess beginnt automatisch (wenn _connectedCallback festgelegt ist). Nachdem die Authentifizierung abgeschlossen ist, muss die Methode onChecked aufgerufen werden, damit NetNode in einen Kommunikationszustand wechselt. Im Falle einer Nichtauthentifizierung sollten wir keine Geschäftsanforderungen senden, aber Anforderungen wie die Anmeldeüberprüfung sollten an den Server gesendet werden. Mit dem Parameter force kann das Senden solcher Anforderungen an den Server erzwungen werden. Der Empfang einer beliebigen Nachricht löst onMessage aus. Zuerst wird das Datenpaket überprüft. Die Überprüfungsregeln können in Ihrem eigenen ProtocolHelper implementiert werden. Wenn es sich um ein gültiges Datenpaket handelt, aktualisieren wir die Heartbeat- und Timeout-Timer – re-timen und finden schließlich die Verarbeitungsfunktion der Nachricht in _requests und _listener. Hier durchsuchen wir rspCmd. rspCmd wird aus getPackageId von ProtocolHelper entnommen. Wir können den Befehl oder die Sequenznummer des Protokolls zurückgeben und entscheiden, wie Anfrage und Antwort übereinstimmen. onError und onClosed werden aufgerufen, wenn das Netzwerk ausfällt und geschlossen wird. Unabhängig davon, ob ein Fehler vorliegt oder nicht, wird onClosed letztendlich aufgerufen. Hier führen wir den Trennungs-Callback aus und führen die automatische Wiederverbindungsverarbeitung durch. Natürlich können Sie auch „close“ aufrufen, um den Socket zu schließen. Der Unterschied zwischen close und closeSocket besteht darin, dass closeSocket lediglich den Socket schließt – ich möchte den aktuellen NetNode weiterhin verwenden und möglicherweise beim nächsten Connect das Netzwerk wiederherstellen. Und mit „Schließen“ werden alle Zustände gelöscht. Es gibt drei Möglichkeiten , eine Netzwerkanfrage zu initiieren : Die Sendemethode sendet einfach Daten. Wenn das Netzwerk derzeit getrennt ist oder eine Überprüfung ausgeführt wird, werden die Daten in die Warteschlange _request eingetragen. Die Anforderungsmethode übergibt den Rückruf beim Stellen einer Anforderung in Form eines Abschlusses. Der Rückruf wird ausgeführt, wenn die Antwort auf die Anforderung zurückkommt. Wenn mehrere identische Anforderungen gleichzeitig vorliegen, werden die Antworten dieser N Anforderungen nacheinander an den Client zurückgegeben, und die Antwortrückrufe werden ebenfalls nacheinander ausgeführt (es wird jeweils nur ein Rückruf ausgeführt). Wenn wir nicht mehrere identische Anfragen haben möchten, können wir mit der Methode requestUnique sicherstellen, dass von jedem Typ gleichzeitig nur eine Anfrage vorhanden ist. Der Grund, warum wir Traversal-_Requests verwenden, um sicherzustellen, dass es hier keine Duplikate gibt, besteht darin, dass wir in _Requests keine große Anzahl von Anfragen ansammeln und Timeouts oder abnormale Neuübertragungen keinen Rückstau von _Requests verursachen, da die Neuübertragungslogik von NetNode gesteuert wird und wir Benutzer daran hindern sollten, Anfragen zu initiieren, wenn die Netzwerkverbindung getrennt wird. Zu diesem Zeitpunkt wird im Allgemeinen eine Vollbildmaske angezeigt – eine Eingabeaufforderung wie Netzwerkschwankungen. Wir haben zwei Arten von Rückrufen. Einer ist der oben erwähnte Anforderungsrückruf. Dieser Rückruf ist temporär und wird normalerweise sofort mit der Anforderungs-Antwort-Ausführung bereinigt. Der _listener-Rückruf ist permanent und muss manuell verwaltet werden, z. B. das Abhören beim Öffnen einer bestimmten Schnittstelle, das Schließen beim Verlassen oder das Abhören zu Beginn des Spiels. Geeignet für die Verarbeitung aktiver Push-Nachrichten vom Server. Schließlich gibt es Timer im Zusammenhang mit Heartbeat und Timeout. Wir senden jedes _heartTime ein Heartbeat-Paket, und wenn wir nicht jedes _receiveTime ein vom Server zurückgegebenes Paket erhalten, stellen wir fest, dass das Netzwerk getrennt ist. Um den vollständigen Code anzuzeigen, können Sie den Quellcode eingeben! NetManagerNetManager wird zur Verwaltung von NetNode verwendet. Dies liegt daran, dass wir möglicherweise mehrere verschiedene Verbindungsobjekte unterstützen müssen, sodass wir einen NetManager zur Verwaltung von NetNode benötigen. Gleichzeitig kann uns NetManager als Singleton auch das Aufrufen des Netzwerks erleichtern. Exportklasse NetManager { private statische _Instanz: NetManager = null; geschützte _Kanäle: { [Schlüssel: Nummer]: NetNode } = {}; öffentliche statische getInstance(): NetManager { wenn (diese._instanz == null) { this._instance = neuer NetManager(); } gib diese._Instanz zurück; } // Knoten hinzufügen und ChannelID zurückgeben öffentliche setNetNode(neuerNode: NetNode, channelId: Nummer = 0) { this._channels[channelId] = neuer Knoten; } // Knoten entfernen öffentliche removeNetNode(channelId: Nummer) { lösche dies._channels[channelId]; } // Knotenverbindung aufrufen public connect(options: NetConnectOptions, channelId: number = 0): boolean { wenn (this._channels[channelId]) { gib dies zurück._channels[channelId].connect(Optionen); } gibt false zurück; } // Rufen Sie den Knoten auf, um public send(buf: NetData, force: boolean = false, channelId: number = 0) zu senden: boolean { lass Knoten = this._channels[channelId]; wenn (Knoten) { gibt node.send(buf, force) zurück; } gibt false zurück; } // Eine Anfrage einleiten und die angegebene Callback-Funktion aufrufen, wenn das Ergebnis zurückgegeben wird public request(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, Kanal-ID: Nummer = 0) { lass Knoten = this._channels[channelId]; wenn (Knoten) { node.request(buf, rspCmd, rspObject, Tipps anzeigen, erzwingen); } } // Wie bei der Anfrage, aber vor der Anfrage wird zunächst ermittelt, ob sich bereits rspCmd in der Warteschlange befindet. Wenn ein Duplikat vorhanden ist, wird es direkt zurückgegeben public requestUnique(buf: NetData, rspCmd: number, rspObject: CallbackObject, showTips: boolean = true, force: boolean = false, channelId: Nummer = 0): boolean { lass Knoten = this._channels[channelId]; wenn (Knoten) { gibt node.requestUnique(buf, rspCmd, rspObject, showTips, force) zurück; } gibt false zurück; } // Rufen Sie den Knoten zum Schließen auf. public close(code ? : number, reason ? : string, channelId: number = 0) { wenn (this._channels[channelId]) { gib dies zurück._channels[channelId].closeSocket(Code, Grund); } } TestbeispieleAls Nächstes verwenden wir ein einfaches Beispiel, um die grundlegende Verwendung des Netzwerk-Frameworks zu demonstrieren. Zunächst müssen wir eine einfache Schnittstelle für die Anzeige, 3 Schaltflächen (Verbinden, Senden, Schließen), 2 Eingabefelder (URL eingeben, zu sendenden Inhalt eingeben) und ein Textfeld (vom Server empfangene Daten anzeigen) zusammenstellen, wie in der folgenden Abbildung dargestellt.
Als nächstes implementieren Sie eine einfache Komponente. Hier wird eine neue NetExample.ts-Datei erstellt. Die Aufgabe ist sehr einfach. Während der Initialisierung wird NetNode erstellt und der standardmäßige Empfangs-Callback gebunden. Im Empfangs-Callback wird der vom Server zurückgegebene Text in msgLabel angezeigt. Als nächstes folgt die Implementierung mehrerer Schnittstellen zum Verbinden, Senden und Schließen: // Unkritischer Code weggelassen @ccclassexport Standardklasse NetExample erweitert cc.Component { @Eigenschaft(cc.Label) Textbezeichnung: cc.Label = null; @Eigenschaft(cc.Label) URL-Label: cc.Label = null; @Eigenschaft(cc.RichText) msgLabel: cc.RichText = null; private lineCount: Zahl = 0; beim Laden() { let Node = neuer NetNode(); Node.init(neues WebSock(), neues DefStringProtocol()); Node.setResponeHandler(0, (cmd: Zahl, Daten: NetData) => { wenn (this.lineCount > 5) { Lassen Sie idx = this.msgLabel.string.search("\n"); this.msgLabel.string = this.msgLabel.string.substr(idx + 1); } this.msgLabel.string += `${data}\n`; ++diese.Zeilenanzahl; }); NetManager.getInstance().setNetNode(Knoten); } beiVerbindenKlick() { NetManager.getInstance().verbinden({ URL: diese.URL-Bezeichnung.Zeichenfolge }); } beiSendenKlick() { NetManager.getInstance().send(this.textLabel.string); } beiKlickTrennenderVerbindung() { NetManager.getInstance().close(); } } Nachdem der Code abgeschlossen ist, mounten Sie ihn unter dem Canvas-Knoten der Szene (andere Knoten sind auch OK) und ziehen Sie dann das Label und den RichText in der Szene in das Eigenschaftenfenster unseres NetExample: Der Laufeffekt ist wie folgt: ZusammenfassungWie Sie sehen, ist die Verwendung von Websocket sehr einfach. Während des Entwicklungsprozesses werden wir auf verschiedene Anforderungen und Probleme stoßen. Wir müssen ein gutes Design implementieren und die Probleme schnell lösen. Einerseits müssen wir ein tiefes Verständnis der von uns verwendeten Technologie haben. Wie ist die zugrunde liegende Protokollübertragung von WebSocket implementiert? Was ist der Unterschied zwischen TCP und HTTP? Kann UDP für websocket-basierte Übertragungen verwendet werden? Muss ich beim Senden von Daten mithilfe von WebSocket den Datenstrom selbst in Unterpakete aufteilen (das WebSocket-Protokoll stellt die Integrität des Pakets sicher)? Kommt es beim Senden von Daten zu einer Ansammlung von Sendepuffer (überprüfen Sie bufferedAmount)? Darüber hinaus müssen wir unsere Nutzungsszenarien und Bedürfnisse verstehen. Je gründlicher wir die Bedürfnisse verstehen, desto besser ist das Design. Welche Anforderungen sind projektspezifisch und welche allgemein? Sind gemeinsame Anforderungen obligatorisch oder optional? Sollten wir verschiedene Änderungen in Klassen oder Schnittstellen kapseln und sie mithilfe von Polymorphismus implementieren? Oder Konfiguration bereitstellen? Rückrufbindung? Veranstaltungsbenachrichtigung? Wir müssen ein gutes Framework entwerfen, das zum nächsten Projekt passt, und die Iterationen in jedem Projekt optimieren, damit wir umfassende Erfahrungen sammeln und die Effizienz verbessern können. Oben finden Sie den detaillierten Inhalt des Netzwerks des allgemeinen Framework-Designs von CocosCreator. Weitere Informationen zum Netzwerk des Framework-Designs von CocosCreator finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Detaillierte Erläuterung des zeitaufwändigen SQL-Beispiels für MySQL-Datensätze
>>: Schritte zur Verwendung von autoconf zum Generieren von Makefile und Kompilieren des Projekts
MySQL-Abfrage für mehrere Tabellen Hinzufügen ein...
Einführung Derzeit ist k8s sehr beliebt und ich h...
MySQL-Batch löschen großer Datenmengen Angenommen...
Lassen Sie mich Ihnen zunächst den fertigen Effek...
Inhaltsverzeichnis So setzen Sie Cookies Nachteil...
Inhaltsverzeichnis 01 Problembeschreibung 02 Lösu...
Ich bin sehr glücklich. Wenn ich auf dieses Probl...
In einer Front-End-Technologiegruppe sagte ein Gr...
Inhaltsverzeichnis Einführung in Arrays Array-Lit...
Inhaltsverzeichnis Überblick Formularvalidierung ...
In diesem Artikel wird der spezifische JavaScript...
Inhaltsverzeichnis 1. Beschreibung der Funktionen...
Vorwort Bei der Entwicklung eines Miniprogramms b...
Um MySQL-Abfrageergebnisse in CSV zu exportieren,...
1. CSS alphabetisch ordnen Nicht in alphabetischer...