socket.IO通訊

溜溜奇發表於2018-10-24
using BestHTTP.ServerSentEvents;
using BestHTTP.SignalR;
using BestHTTP.SignalR.Hubs;
using BestHTTP.SignalR.Messages;
using BestHTTP.SocketIO;
using LitJson;
using System;
using System.Collections.Generic;
using UnityEngine;

public class MySocketIO : MonoBehaviour
{

    void Start()
    {

    }
    void ConnectingToNamespaces()
    {
        SocketManager manager = new SocketManager(new Uri("http://chat.socket.io/socket.io/"));
        //預設情況下,SocketManager將在連線到伺服器時連線到根(“/”)名稱空間。 你可以通過SocketManager的Socket屬性來訪問它:
        Socket root = manager.Socket;
        //可以通過GetSocket(“/ nspName”)函式或通過管理器的索引器屬性訪問非預設名稱空間:
        Socket nsp1 = manager["/customNamespace"];
        Socket nsp2 = manager.GetSocket("/customNamespace");
        //首次訪問名稱空間將會啟動內部連線程式
    }
    //您可以訂閱預定義和自定義事件
    void SubscribingAndReceivingEvents()
    {
        SocketManager manager = new SocketManager(new Uri("http://chat.socket.io/socket.io/"));
        //預定義事件
        //connect:當名稱空間開啟時傳送
        //connecting:當SocketManager開始連線到socket.io伺服器時傳送
        //event:根據自定義(程式設計師定義的)事件傳送
        //disconnect:在傳輸斷開時,SocketManager關閉,Socket關閉或在握手資料中指定的給定時間內未收到伺服器的Pong訊息時傳送
        //reconnect:外掛成功重新連線到socket.io伺服器時傳送
        //reconnecting:當外掛嘗試重新連線到socket.io伺服器時傳送
        //reconnect_attempt:當外掛嘗試重新連線到socket.io伺服器時傳送
        //reconnect_failed:當重新連線嘗試無法連線到伺服器並且ReconnectAttempt達到選項'ReconnectionAttempts'值時傳送
        //error:傳送到伺服器或內部外掛錯誤。 該事件的唯一引數將是一個BestHTTP.SocketIO.Error物件

        //自定義事件
        //自定義事件是程式設計師定義的事件,您的伺服器將傳送給您的客戶端。 您可以通過呼叫套接字的On函式來訂閱事件
        manager.Socket.On("login", (socket, packet, args) => { });
        //socket引數將是伺服器傳送此事件的名稱空間套接字物件。 
        //packet引數包含事件的內部資料包資料。 該資料包可用於訪問伺服器傳送的二進位制資料,或使用定製的Json解析器庫來解碼有效載荷資料
        //args引數是一個可變長度陣列,它包含來自資料包有效負載資料的已解碼物件。
        //使用預設的Json編碼器,這些引數可以是物件的“原始”型別(int,double,string)或物件列表(List <object>)或Dictionary <string,object>

        //伺服器上發出的訊息
        //socket.emit('message', ‘MyNick’, ‘Msg to the client’); 
        //客戶端捕獲的訊息
        //訂閱message事件
        manager.Socket.On("message", (socket, packet, args) =>
        {
            Debug.Log(string.Format("Message from {0}: {1}", args[0], args[1]));
        });


        //其它與事件相關的功能
        //Once:你可以訂閱一個僅呼叫一次的事件
        manager.Socket.Once("connect", (socket, packet, args) => { });
        //off:取消訂閱
        manager.Socket.Off();//取消所有訂閱的事件
        manager.Socket.Off("connect");//對事件"connect"取消訂閱
                                      //manager.Socket.Off("connect", OnConnected); //從事件"connect"中移除OnConnected方法
    }
    //向伺服器傳送確認 
    void SendingAcknowledgement()
    {
        SocketManager manager = new SocketManager(new Uri(""));
        //可以通過呼叫套接字的EmitAck函式向伺服器發回確認
        //必須傳遞原始資料包和可選資料:
        manager["/customNamespace"].On("customEvent", (socket, packet, args) =>
        {
            socket.EmitAck(packet, "Event", "Received", "Successfully");
        });
    }
    void SendingBinaryData()
    {
        SocketManager manager = new SocketManager(new Uri(""));
        //有兩種方式傳送二進位制資料
        //一.
        //通過Emit函式傳遞,外掛將掃描引數,如果它找到一個引數,它會將其轉換為二進位制附件(如在Socket.IO 1.0中介紹的)。
        //這是最有效的方法,因為它不會將位元組陣列轉換為客戶端上的Base64編碼字串,並且在伺服器端將其轉換為二進位制。
        byte[] data = new byte[10];
        manager.Socket.Emit("eventWithBinary", "textual param", data);
        //二.
        //如果二進位制資料作為欄位或屬性嵌入物件中,則Json編碼器必須支援該轉換。 
        //預設的Json編碼器不能將嵌入的二進位制資料轉換為Json,你必須使用更高階的Json解析器庫(比如“JSON .NET for Unity” - http://u3d.as/5q2)
    }
    void ReceivingBinaryData()
    {
        SocketManager manager = new SocketManager(new Uri(""));
        //在Socket.IO伺服器中,當二進位制資料傳送到客戶端時,它將用Json物件({'_ placeholder':true,'num':xyz})替換資料,並將二進位制資料傳送到另一個資料包中
        //在客戶端,這些資料包將被收集併合併為一個資料包
        //二進位制資料將位於資料包的Attachments屬性中
        //在這裡你將有一些使用這個資料包的選擇:
        //一、
        //在事件處理函式中,您可以通過資料包的Attachments屬性訪問所有二進位制資料
        manager.Socket.On("frame", (socket, packet, args) =>
        {
            Texture2D texture2D = new Texture2D(0, 0);
            texture2D.LoadImage(packet.Attachments[0]);
        });
        //二、
        //第二個選項與前一個選項幾乎相同,但有一點改進:我們不會將傳送的Json字串解碼為c#物件
        //我們可以這樣做,因為我們知道伺服器只傳送二進位制資料,這個事件沒有其他資訊。 所以我們會讓外掛知道不要解碼有效載荷:
        manager.Socket.On("frame", (socket, packet, args) =>
        {
            Texture2D texture2D = new Texture2D(0, 0);
            texture2D.LoadImage(packet.Attachments[0]);
        }, false);
        //三、
        //我們可以將“{'_placeholder':true,'num':xyz}”字串替換為Attachments列表中Attachment的索引
        manager.Socket.On("frame", (socket, packet, args) =>
        {
            //用索引替換Json物件
            packet.ReconstructAttachmentAsIndex();
            //解碼有效載荷到object[]
            args = packet.Decode(socket.Manager.Encoder);
            //現在args只包含一個索引號(可能是0)
            byte[] data = packet.Attachments[Convert.ToInt32(args[0])];

            Texture2D texture2D = new Texture2D(0, 0);
            texture2D.LoadImage(data);
        }, false);
    }
    //設定預設的Json編碼器
    //如果由於各種原因想要更改預設的Json編碼器,首先必須編寫一個新的Json編碼器
    //為此,必須編寫一個新的類,該類可以從BestHTTP.SocketIO.JsonEncoders名稱空間實現IJsonEncoder
    //剝離的IJsonEncoder非常小,你只需要實現兩個功能:
    public interface IJsonEncoder
    {
        List<object> Decode(string json);
        string Encode(List<object> obj);
    }
    //Decode函式必須將給定的json字串解碼為物件列表。 由於Socket.IO協議的性質,傳送的json是一個陣列,第一個元素是事件的名稱。

    //編碼功能用於對客戶想要傳送到伺服器的資料進行編碼
    //該列表的結構與Decode相同:列表的第一個元素是事件的名稱,其他任何元素都是使用者傳送的引數。

    //下面是使用示例資料夾中的LitJson庫的完整示例:
    public sealed class LitJsonEncoder : IJsonEncoder
    {
        public List<object> Decode(string json)
        {
            JsonReader reader = new JsonReader(json);
            return JsonMapper.ToObject<List<object>>(reader);
        }

        public string Encode(List<object> obj)
        {
            JsonWriter writer = new JsonWriter();
            JsonMapper.ToJson(obj, writer); return writer.ToString();
        }
    }
    //發生伺服器端或客戶端錯誤時發生的“error”事件
    //事件的第一個引數將是一個Error物件。 這將包含Code屬性中的錯誤程式碼和Message屬性中的字串訊息
    //ToString()函式在這個類中被覆蓋,你可以使用這個函式寫出它的內容。
    void ErrorHandling()
    {
        SocketManager manager = new SocketManager(new Uri(""));
        manager.Socket.On(SocketIOEventTypes.Error, (socket, packet, args) =>
        {
            Error error = args[0] as Error;

            switch (error.Code)
            {
                case SocketIOErrors.User:
                    Debug.Log("Exception in an event handler!");
                    break;
                case SocketIOErrors.Internal:
                    Debug.Log("Internal error!");
                    break;
                default:
                    Debug.Log("Server error!");
                    break;
            }

            Debug.Log(error.ToString());
        });
    }
    //可以將SocketOptions例項傳遞給SocketManager的建構函式
    //可以更改以下選項: 
    //Reconnection:斷開連線後是否自動重新連線。它的預設值是true。 
    //ReconnectionAttempts:放棄之前的嘗試次數。它的預設值是Int.MaxValue
    //ReconnectionDelay:嘗試重新連線之前最初等待多久。受+/- RandomizationFactor影響。例如,預設的初始延遲將在500毫秒到1500毫秒之間。其預設值是10000ms
    //ReconnectionDelayMax:重新連線之間等待的最長時間。如上所述,每次嘗試都會增加重新連線延遲以及隨機化。其預設值是5000ms
    //RandomizationFactor:它可以用來控制ReconnectionDelay範圍。它的預設值是0.5,可以在0..1的值之間進行設定。 
    //Timeout:發出“connect_error”和“connect_timeout”事件之前的連線超時。它不是底層的tcp套接字的連線超時,而是socket.io協議。它的預設值是20000ms
    //AutoConnect:通過將此設定為false,只要您決定適當,就必須呼叫SocketManager的Open()。 
    //當你建立一個新的SocketOptions物件時,它的屬性被設定為它們的預設值。
    //BestHTTP.SignalR名稱空間中的Connection類管理到SignalR伺服器的抽象連線
    //連線到SignalR伺服器從建立Connection物件開始
    //該類將跟蹤協議的當前狀態並將觸發事件
    void TheConnectionClass()
    {
        //你有多個途徑建立一個連線物件
        Uri uri = new Uri("");
        //通過僅將伺服器的URI傳遞給建構函式來建立連線,而不使用集線器
        Connection signlRConnection1 = new Connection(uri);
        //通過將集線器名稱也傳遞給建構函式來建立連線,並使用集線器
        Connection signlRConnection2 = new Connection(uri, "hub1", "hub2", "hubN");
        //通過將Hub物件傳遞給建構函式來建立連線,並使用Hub
        //using BestHTTP.SignalR.Hubs;
        Hub hub1 = new Hub("hub1");
        Hub hub2 = new Hub("hub2");
        Hub hubN = new Hub("hubN");
        Connection signlRConnection3 = new Connection(uri, hub1, hub2, hubN);
        //在建立Connection之後,我們可以通過呼叫Open()函式來開始連線到伺服器
        signlRConnection1.Open();
    }
    void HandlingGeneralEvents()
    {
        Uri uri = new Uri("");
        Connection signlRConnection = new Connection(uri);
        //Connection類將允許您訂閱多個事件,這些事件如下:
        //OnConnected:當connection類成功連線並且SignalR協議用於通訊時,會觸發此事件
        signlRConnection.OnConnected += (con) => Debug.Log("Connected to the SignalR server!");
        //OnClosed:當SignalR協議關閉時會觸發此事件,並且不再傳送或接收更多訊息
        signlRConnection.OnClosed += (con) => Debug.Log("Connection Closed");
        //OnError:發生錯誤時呼叫。 如果連線已經開啟,外掛將嘗試重新連線,否則連線將被關閉
        signlRConnection.OnError += (con, err) => Debug.Log("Error: " + err);
        //OnReconnecting:當重新連線嘗試開始時,將觸發此事件
        //在此事件之後,將呼叫OnError或OnReconnected事件
        //在OnReconnected / OnClosed事件之前可以觸發多個OnReconnecting-OnError事件對,因為外掛會在給定的時間內嘗試重新連線多次。
        signlRConnection.OnReconnecting += (con) => Debug.Log("Reconnecting");
        //OnReconnected:重新連線嘗試成功時觸發
        signlRConnection.OnReconnecting += (con) => Debug.Log("Reconnected");
        //OnStateChnaged:連線狀態改變時觸發,事件處理程式將收到舊狀態和新狀態
        signlRConnection.OnStateChanged += (conn, oldState, newState) => Debug.Log(string.Format("State Changed {0} -> {1}", oldState, newState));
        //OnNonHubMessage:伺服器向客戶端傳送非集線器訊息時觸發
        //客戶端應該知道伺服器期望的訊息型別,並且應該相應地轉換接收到的物件
        signlRConnection.OnNonHubMessage += (con, data) => Debug.Log("Message from server: " + data.ToString());
        //RequestPreparator:每個HTTPRequest都會呼叫這個委託並將傳送到伺服器, 它可以用來進一步自定義請求
        signlRConnection.RequestPreparator = (con, req, type) => req.Timeout = TimeSpan.FromSeconds(30);
    }
    void SendingNonHubMessages()
    {
        Uri uri = new Uri("");
        Connection signlRConnection = new Connection(uri);
        //傳送非集線器訊息到伺服器像在connection物件上呼叫一個函式一樣容易:
        signlRConnection.Send(new { Type = "Broadcast", Value = "Hello SignalR World!" });
        //此函式將使用Connection的JsonEncoder將給定物件編碼為Json字串,並將其傳送到伺服器
        //已編碼的Json字串可以使用SendJson函式傳送:
        signlRConnection.SendJson("{ Type: ‘Broadcast’, Value: ‘Hello SignalR World!’ }");
    }
    //為了定義客戶端上Hub可以從伺服器呼叫的方法,並在伺服器上呼叫Hub上的方法,必須將Hubs新增到Connection物件中
    //這可以通過將集線器名稱或集線器例項新增到Connection建構函式中完成,在Connection Class部分中已進行演示
    //using BestHTTP.SignalR.Messages;
    void Hubs()
    {
        Uri uri = new Uri("");
        Connection signalRConnection = new Connection(uri);
        //Accessing hubs (訪問集線器)
        Hub hub1 = signalRConnection[0];
        Hub hub2 = signalRConnection["hubName"];
        //Register server callable methods(註冊伺服器可呼叫方法)
        //要處理伺服器可呼叫方法的呼叫,我們必須呼叫集線器的On函式:
        signalRConnection["hubName"].On("joined", (hub, msg) =>
        {
            Debug.Log(string.Format("{0} joined at {1}", msg.Arguments[0], msg.Arguments[1]));
        });

        //On的第二個引數型別是MethodCallMessage
        //MethodCallMessage是一個伺服器傳送的物件,它包含以下屬性:
        //Hub:包含該方法必須呼叫的集線器名稱的字串
        //Method:包含方法名稱的字串
        //Arguments:包含方法呼叫引數的物件陣列。 它可以是一個空陣列
        //State:包含其他自定義資料的字典。

        //該外掛將使用Hub和Method屬性將訊息傳送到正確的集線器和事件處理程式。 處理方法呼叫的函式只能使用引數和狀態屬性

        //Call server-side methods (呼叫服務端方法)
        //呼叫伺服器端方法可以通過呼叫Hub的Call函式來完成
        //過載的呼叫函式能夠滿足每個需求
        //Call函式是非阻塞函式,它們不會阻塞,直到伺服器傳送有關該呼叫的任何訊息
        //過載如下:
        //Call(string method, params object[] args): 
        //這可用於以fireand -forget樣式呼叫伺服器端函式
        //我們不會收到有關方法呼叫成功或失敗的訊息。 這個函式可以在沒有任何'args'引數的情況下呼叫,以呼叫無引數方法
        signalRConnection["hubName"].Call("Ping");
        signalRConnection["hubName"].Call("Message", "param1", "param2");
        //Call(string method, OnMethodResultDelegate onResult, params object[] args): 
        //該函式可以作為前一個函式使用,但函式可以傳遞第二個引數,該函式將在伺服器端函式成功呼叫時呼叫。
        signalRConnection["hubName"].Call("GetValue", OnGetValueDone);
        //此回撥函式接收呼叫此函式的Hub,傳送給伺服器的原始ClientMessage訊息以及由伺服器作為該方法呼叫結果傳送的ResultMessage例項
        //ResultMessage物件包含一個ReturnValue和一個State屬性。

        //如果方法的返回型別為void,則ReturnValue為null
        //Call(string method, OnMethodResultDelegate onResult, OnMethodFailedDelegate onError, params object[] args):
        //此函式可指定當方法在伺服器上執行失敗時呼叫的回撥。 沒有找到方法,引數錯誤或未處理的異常,在方法呼叫時都可能會引發故障
        signalRConnection["hubName"].Call("GetValue",
        OnGetValueDone,
        OnGetValueFailed);
        //FailureMessage包含以下屬性:
        //IsHubError:如果是Hub錯誤,則為True
        //ErrorMessage:關於錯誤本身的簡短訊息
        //StackTrace:如果伺服器上開啟了詳細的錯誤報告,那麼它將包含錯誤的堆疊跟蹤
        //AdditionalData:如果不為空,則它包含有關錯誤的其他資訊


        // Call(string method, OnMethodResultDelegate onResult, OnMethodFailedDelegate onError, OnMethodProgressDelegate onProgress, params object[] args): 
        //該函式可用於向伺服器端方法呼叫新增額外的進度訊息處理程式。 對於長時間執行的作業,伺服器可以向客戶端傳送進度訊息
        signalRConnection["hubName"].Call("GetValue", OnGetValueDone, OnGetValueFailed, OnGetValueProgress);
    }
    void OnGetValueDone(Hub hub, ClientMessage originalMessage, ResultMessage result)
    {
        Debug.Log("GetValue executed on the server. Return value of the function:" + result.ReturnValue.ToString());
    }
    void OnGetValueFailed(Hub hub, ClientMessage originalMessage, FailureMessage error)
    {
        Debug.Log("GetValue failed. Error message from the server: " + error.ErrorMessage);
    }
    void OnGetValueProgress(Hub hub, ClientMessage originalMessage, ProgressMessage progress)
    {
        Debug.Log(string.Format("GetValue progressed: {0}%", progress.Progress));
    }
    void EventSourceClass()
    {
        EventSource eventSource = new EventSource(new Uri("http://server.com"));

        //Properties 
        //這些是EventSource類公開公開的屬性:
        //Uri:這是協議嘗試連線的端點。 它通過建構函式設定。 
        //State:EventSource物件的當前狀態。 
        //ReconnectionTime:嘗試執行重新連線嘗試需要等待多少時間。 它的預設值是2秒。
        //LastEventId:最近收到的事件的ID。 如果沒有接收到事件ID,它將為空。
        //InternalRequest:將在Open函式中發出的內部HTTPRequest物件。

        //Events 
        //OnOpen:它在協議成功升級時呼叫
        eventSource.OnOpen += (source) => Debug.Log("EventSource Opened!");
        //OnMessage:它在客戶端收到來自伺服器的新訊息時呼叫
        //該函式將接收一個Message物件,該物件包含Data屬性中訊息的有效內容
        //每次客戶端收到訊息時都會呼叫此事件,即使訊息具有有效的事件名稱,並且我們為此事件分配了事件處理程式!
        eventSource.OnError += (source, error) => Debug.Log("Error: " + error);
        //OnRetry:在外掛嘗試重新連線到伺服器之前呼叫此函式。 如果函式返回false,則不會進行嘗試,並關閉EventSource。
        eventSource.OnRetry += (source) => false;
        //OnClosed:EventSource關閉時會呼叫此事件
        eventSource.OnClosed += (source) => Debug.Log("EventSource Closed!");
        //OnStateChanged:當State屬性變化時呼叫
        eventSource.OnStateChanged += (source, oldState, newState) => Debug.Log(string.Format("State Changed {0} => {1}", oldState, newState));

        //Functions 
        //這些是EventSource物件的公有方法
        // Open: 呼叫此函式後,外掛將開始連線到伺服器並升級到ServerSent Events協議
        eventSource.Open();
        // On: 使用這個功能,客戶端可以訂閱事件
        eventSource.On("userLogon", (source, msg) => { Debug.Log(msg.Data); });
        // Off: 它可以用來取消訂閱活動
        eventSource.Close();

    }
    //    Message類是一個邏輯單元,它包含伺服器可以傳送的所有資訊。 
    //屬性
    //Id:傳送事件的ID。 如果沒有ID傳送,可以為空。 它由外掛使用。 
    //Event:事件的名稱。 如果沒有傳送事件名稱,則可以為null。 
    //Data:訊息的實際有效負載。 
    //Retry:伺服器傳送外掛在嘗試重新連線之前等待的時間。 它由外掛使用

    void SampleHubImplement()
    {
        Uri uri = new Uri("");
        SampleHub sampleHub = new SampleHub();
        Connection signalRConnection = new Connection(uri, sampleHub);
    }
}

class SampleHub : Hub
{
    // Default constructor. Every hubs have to have a valid name.  
    public SampleHub() : base("SampleHub")
    {
        // 註冊一個伺服器可呼叫函式  
        base.On("ClientFunction", ClientFunctionImplementation);
    }

    // 實現伺服器可呼叫函式的私有函式  
    private void ClientFunctionImplementation(Hub hub, MethodCallMessage msg)
    {
        // 待辦事項:實現  
    }

    // 包裝器函式呼叫伺服器端函式。  
    public void ServerFunction(string argument)
    {
        base.Call("ServerFunction", argument);
    }
}

 

相關文章